ixa/people/
property.rs

1//! Properties are the main way to store and access data about people.
2//!
3//! # Properties
4//!
5//! Properties are defined using the `define_person_property!` and
6//! `define_derived_property!` macros.
7//!
8//! # Multi-properties
9//!
10//! The `define_multi_property!` macro (defined in `property.rs`) takes a name and a tuple
11//! of property tags. It defines a derived property (via `define_derived_property`) with the
12//! provided name having the type of tuples of values corresponding to the provided tags.
13//!
14//! ```rust,ignore
15//! use ixa::people::{define_multi_property, define_person_property};
16//!
17//! define_person_property!(Name, &'static str);
18//! define_person_property!(Age, u8);
19//! define_person_property!(Weight, f64);
20//!
21//! define_multi_property!(Profile, (Name, Age, Weight));
22//! ```
23//!
24//! The new derived property is not automatically indexed. You can index it just
25//! like any other property:
26//!
27//! ```rust, ignore
28//! context.index_property(Profile);
29//! ```
30
31use crate::hashing::hash_serialized_128;
32use crate::people::data::PersonPropertyHolder;
33use crate::{Context, PersonId};
34use serde::Serialize;
35use std::any::TypeId;
36use std::fmt::Debug;
37
38/// We factor this out and provide a blanket implementation for all types that
39/// can be value types for properties. This makes it convenient to reference
40/// `PersonPropertyValue` trait constraints.
41pub trait PersonPropertyValue: Copy + Debug + PartialEq + Serialize {}
42impl<T> PersonPropertyValue for T where T: Copy + Debug + PartialEq + Serialize {}
43
44/// An individual characteristic or state related to a person, such as age or
45/// disease status.
46///
47/// Person properties should be defined with the [`define_person_property!()`],
48/// [`define_person_property_with_default!()`] and [`define_derived_property!()`]
49/// macros.
50pub trait PersonProperty: Copy + 'static {
51    /// The type of the property's values.
52    type Value: PersonPropertyValue;
53    /// Some properties might store a transformed version of the value in the index. This is the
54    /// type of the transformed value. For simple properties this will be the same as `Self::Value`.
55    type CanonicalValue: PersonPropertyValue;
56
57    #[must_use]
58    fn is_derived() -> bool {
59        false
60    }
61
62    #[must_use]
63    fn is_required() -> bool {
64        false
65    }
66
67    #[must_use]
68    fn dependencies() -> Vec<Box<dyn PersonPropertyHolder>> {
69        panic!("Dependencies not implemented");
70    }
71
72    fn register_dependencies(_: &Context) {
73        panic!("Dependencies not implemented");
74    }
75
76    fn compute(context: &Context, person_id: PersonId) -> Self::Value;
77
78    /// This transforms a `Self::Value` into a `Self::CanonicalValue`, e.g. for storage in an index.
79    /// For simple properties, this is the identity function.
80    #[must_use]
81    fn make_canonical(value: Self::Value) -> Self::CanonicalValue;
82
83    /// The inverse transform of `make_canonical`. For simple properties, this is the identity function.
84    #[must_use]
85    fn make_uncanonical(value: Self::CanonicalValue) -> Self::Value;
86    fn get_instance() -> Self;
87    fn name() -> &'static str;
88
89    /// Returns a string representation of the property value, e.g. for writing to a CSV file.
90    /// If `make_uncanonical` is nontrivial, this method usually transforms `value` into a
91    /// `Self::Value` first so that the value is formatted in a way the user expects.
92    #[must_use]
93    fn get_display(value: &Self::CanonicalValue) -> String;
94
95    /// For cases when the property's hash needs to be computed in a special way.
96    #[must_use]
97    fn hash_property_value(value: &Self::CanonicalValue) -> u128 {
98        hash_serialized_128(value)
99    }
100
101    /// Overridden by multi-properties, which use the `TypeId` of the ordered tuple so that tuples
102    /// with the same component types in a different order will have the same type ID.
103    #[must_use]
104    fn type_id() -> TypeId {
105        TypeId::of::<Self>()
106    }
107}
108
109#[macro_export]
110macro_rules! __define_person_property_common {
111    ($person_property:ident, $value:ty, $compute_fn:expr, $is_required:expr, $display_impl:expr) => {
112        #[derive(Debug, Copy, Clone, Eq, PartialEq)]
113        pub struct $person_property;
114        impl $crate::people::PersonProperty for $person_property {
115            type Value = $value;
116            type CanonicalValue = $value;
117            fn compute(
118                _context: &$crate::context::Context,
119                _person: $crate::people::PersonId,
120            ) -> Self::CanonicalValue {
121                $compute_fn(_context, _person)
122            }
123            fn make_canonical(value: Self::Value) -> Self::CanonicalValue {
124                value
125            }
126            fn make_uncanonical(value: Self::CanonicalValue) -> Self::Value {
127                value
128            }
129            fn is_required() -> bool {
130                $is_required
131            }
132            fn get_instance() -> Self {
133                $person_property
134            }
135            fn name() -> &'static str {
136                stringify!($person_property)
137            }
138            fn get_display(value: &Self::CanonicalValue) -> String {
139                $display_impl(value)
140            }
141        }
142    };
143}
144
145/// Defines a person property with the following parameters:
146/// * `$person_property`: A name for the identifier type of the property
147/// * `$value`: The type of the property's value
148/// * `$initialize`: (Optional) A function that takes a `Context` and `PersonId` and
149///   returns the initial value. If it is not defined, calling `get_person_property`
150///   on the property without explicitly setting a value first will panic.
151#[macro_export]
152macro_rules! define_person_property {
153    // Option<T> with initializer
154    ($person_property:ident, Option<$value:ty>, $initialize:expr) => {
155        $crate::__define_person_property_common!(
156            $person_property,
157            Option<$value>,
158            $initialize,
159            false,
160            |&value| {
161                match value {
162                    Some(v) => format!("{:?}", v),
163                    None => "None".to_string(),
164                }
165            }
166        );
167    };
168    // T with initializer
169    ($person_property:ident, $value:ty, $initialize:expr) => {
170        $crate::__define_person_property_common!(
171            $person_property,
172            $value,
173            $initialize,
174            false,
175            |&value| format!("{:?}", value)
176        );
177    };
178    // Option<T> without initializer
179    ($person_property:ident, Option<$value:ty>) => {
180        $crate::__define_person_property_common!(
181            $person_property,
182            Option<$value>,
183            |_, _| panic!("Property not initialized when person created."),
184            true,
185            |&value| {
186                match value {
187                    Some(v) => format!("{:?}", v),
188                    None => "None".to_string(),
189                }
190            }
191        );
192    };
193    // T without initializer
194    ($person_property:ident, $value:ty) => {
195        $crate::__define_person_property_common!(
196            $person_property,
197            $value,
198            |_, _| panic!("Property not initialized when person created."),
199            true,
200            |&value| format!("{:?}", value)
201        );
202    };
203}
204pub use define_person_property;
205
206/// Defines a person property with the following parameters:
207/// * `$person_property`: A name for the identifier type of the property
208/// * `$value`: The type of the property's value
209/// * `$default`: An initial value
210#[macro_export]
211macro_rules! define_person_property_with_default {
212    ($person_property:ident, Option<$value:ty>, $default:expr) => {
213        $crate::define_person_property!(
214            $person_property,
215            Option<$value>,
216            |_context, _person_id| { $default }
217        );
218    };
219    ($person_property:ident, $value:ty, $default:expr) => {
220        $crate::define_person_property!($person_property, $value, |_context, _person_id| {
221            $default
222        });
223    };
224}
225pub use define_person_property_with_default;
226
227/// Defines a derived person property with the following parameters:
228/// * `$person_property`: A name for the identifier type of the property
229/// * `$value`: The type of the property's value
230/// * `[$($dependency),+]`: A list of person properties the derived property depends on
231/// * `[$($dependency),*]`: A list of global properties the derived property depends on (optional)
232/// * `$calculate`: A closure that takes the values of each dependency and returns the derived value
233/// * `$display`: A closure that takes the value of the derived property and returns a string representation
234/// * `$hash_fn`: A function that can compute the hash of values of this property
235#[macro_export]
236macro_rules! __define_derived_property_common {
237    (
238        $derived_property:ident,
239        $value:ty,
240        $canonical_value:ty,
241        $compute_canonical_impl:expr,
242        $compute_uncanonical_impl:expr,
243        $at_dependency_registration:expr,
244        [$($dependency:ident),*],
245        [$($global_dependency:ident),*],
246        |$($param:ident),+| $derive_fn:expr,
247        $display_impl:expr,
248        $hash_fn:expr,
249        $type_id_impl:expr
250    ) => {
251        #[derive(Debug, Copy, Clone)]
252        pub struct $derived_property;
253
254        impl $crate::people::PersonProperty for $derived_property {
255            type Value = $value;
256            type CanonicalValue = $canonical_value;
257
258            fn compute(context: &$crate::context::Context, person_id: $crate::people::PersonId) -> Self::Value {
259                #[allow(unused_imports)]
260                use $crate::global_properties::ContextGlobalPropertiesExt;
261                #[allow(unused_parens)]
262                let ($($param,)*) = (
263                    $(context.get_person_property(person_id, $dependency)),*,
264                    $(
265                        context.get_global_property_value($global_dependency)
266                            .expect(&format!("Global property {} not initialized", stringify!($global_dependency)))
267                    ),*
268                );
269                #[allow(non_snake_case)]
270                (|$($param),+| $derive_fn)($($param),+)
271            }
272            fn make_canonical(value: Self::Value) -> Self::CanonicalValue {
273                ($compute_canonical_impl)(value)
274            }
275            fn make_uncanonical(value: Self::CanonicalValue) -> Self::Value {
276                ($compute_uncanonical_impl)(value)
277            }
278            fn is_derived() -> bool { true }
279            fn dependencies() -> Vec<Box<dyn $crate::people::PersonPropertyHolder>> {
280                vec![$(
281                    Box::new($dependency) as Box<dyn $crate::people::PersonPropertyHolder>
282                ),*]
283            }
284            fn register_dependencies(context: &$crate::context::Context) {
285                $at_dependency_registration
286                $(context.register_property::<$dependency>();)+
287            }
288            fn get_instance() -> Self {
289                $derived_property
290            }
291            fn name() -> &'static str {
292                stringify!($derived_property)
293            }
294            fn get_display(value: &Self::CanonicalValue) -> String {
295                $display_impl(value)
296            }
297            fn hash_property_value(value: &Self::CanonicalValue) -> u128 {
298                ($hash_fn)(value)
299            }
300            fn type_id() -> std::any::TypeId {
301                $type_id_impl
302            }
303        }
304    };
305}
306
307/// Defines a derived person property with the following parameters:
308/// * `$person_property`: A name for the identifier type of the property
309/// * `$value`: The type of the property's value
310/// * `[$($dependency),+]`: A list of person properties the derived property depends on
311/// * `[$($dependency),*]`: A list of global properties the derived property depends on (optional)
312/// * $calculate: A closure that takes the values of each dependency and returns the derived value
313#[macro_export]
314macro_rules! define_derived_property {
315    (
316        $derived_property:ident,
317        $value:ty,
318        [$($dependency:ident),*],
319        [$($global_dependency:ident),*],
320        |$($param:ident),+| $derive_fn:expr
321    ) => {
322        $crate::__define_derived_property_common!(
323            $derived_property,
324            $value,
325            $value,
326            |v| v,
327            |v| v,
328            {/* empty*/},
329            [$($dependency),*],
330            [$($global_dependency),*],
331            |$($param),+| $derive_fn,
332            |&value| format!("{:?}", value),
333            $crate::hashing::hash_serialized_128,
334            std::any::TypeId::of::<Self>()
335        );
336    };
337
338    // Empty global dependencies
339    (
340        $derived_property:ident,
341        $value:ty,
342        [$($dependency:ident),*],
343        |$($param:ident),+| $derive_fn:expr
344    ) => {
345        $crate::__define_derived_property_common!(
346            $derived_property,
347            $value,
348            $value,
349            |v| v,
350            |v| v,
351            {/* empty*/},
352            [$($dependency),*],
353            [],
354            |$($param),+| $derive_fn,
355            |&value| format!("{:?}", value),
356            $crate::hashing::hash_serialized_128,
357            std::any::TypeId::of::<Self>()
358        );
359    };
360}
361pub use define_derived_property;
362
363#[macro_export]
364macro_rules! define_multi_property {
365    (
366        $person_property:ident,
367        ( $($dependency:ident),+ )
368    ) => {
369        // $crate::sorted_property_impl!(( $($dependency),+ ));
370        $crate::paste::paste! {
371            $crate::__define_derived_property_common!(
372                // Name
373                $person_property,
374
375                // `PersonProperty::Value` type
376                ( $(<$dependency as $crate::people::PersonProperty>::Value),+ ),
377
378                // `PersonProperty::CanonicalValue` type
379                $crate::sorted_value_type!(( $($dependency),+ )),
380
381                // Function to transform a `PersonProperty::Value` to a `PersonProperty::CanonicalValue`
382                $person_property::reorder_by_tag,
383
384                // Function to transform a `PersonProperty::CanonicalValue` to a `PersonProperty::Value`
385                $person_property::unreorder_by_tag,
386
387                // Code that runs at dependency registration time
388                {
389                    let type_ids = &mut [$($dependency::type_id()),+ ];
390                    type_ids.sort();
391                    $crate::people::register_type_ids_to_muli_property_id(type_ids, Self::type_id());
392                },
393
394                // Property dependency list
395                [$($dependency),+],
396
397                // Global property dependency list
398                [],
399
400                // A function that takes the values of each dependency and returns the derived value
401                |$( [<_ $dependency:lower>] ),+| {
402                    ( $( [<_ $dependency:lower>] ),+ )
403                },
404
405                // A function that takes a canonical value and returns a string representation of it.
406                |values_tuple: &Self::CanonicalValue| {
407                    // ice tThe string representation uses the original (unsorted) ordering.
408                    let values_tuple: Self::Value = Self::unreorder_by_tag(*values_tuple);
409                    let mut displayed = String::from("(");
410                    let ( $( [<_ $dependency:lower>] ),+ ) = values_tuple;
411                    $(
412                        displayed.push_str(<$dependency as $crate::PersonProperty>::get_display(
413                            & <$dependency as $crate::PersonProperty>::make_canonical([<_ $dependency:lower>])
414                        ).as_str());
415                        displayed.push_str(", ");
416                    )+
417                    displayed.truncate(displayed.len() - 2);
418                    displayed.push_str(")");
419                    displayed
420                },
421
422                // A function that computes the hash of a value of this property
423                $crate::hashing::hash_serialized_128,
424
425                // The Type ID of the property.
426                // The type ID of a multi-property is the type ID of the SORTED tuple of its
427                // components. This is so that tuples with the same component types in a different
428                // order will have the same type ID.
429                std::any::TypeId::of::<$crate::sorted_tag!(( $($dependency),+ ))>()
430            );
431            $crate::impl_make_canonical!($person_property, ( $($dependency),+ ));
432        }
433    };
434}
435pub use define_multi_property;
436
437#[cfg(test)]
438mod tests {
439    use super::*;
440    use crate::people::{PeoplePlugin, Query};
441    use crate::prelude::*;
442    use crate::PersonProperty;
443
444    define_person_property!(Pu32, u32);
445    define_person_property!(POu32, Option<u32>);
446
447    define_person_property!(Name, &'static str);
448    define_person_property!(Age, u8);
449    define_person_property!(Weight, f64);
450
451    define_multi_property!(ProfileNAW, (Name, Age, Weight));
452    define_multi_property!(ProfileAWN, (Age, Weight, Name));
453    define_multi_property!(ProfileWAN, (Weight, Age, Name));
454
455    #[test]
456    fn test_multi_property_ordering() {
457        let a: <ProfileNAW as PersonProperty>::Value = ("Jane", 22, 180.5);
458        let b: <ProfileAWN as PersonProperty>::Value = (22, 180.5, "Jane");
459        let c: <ProfileWAN as PersonProperty>::Value = (180.5, 22, "Jane");
460
461        assert_eq!(ProfileNAW::type_id(), ProfileAWN::type_id());
462        assert_eq!(ProfileNAW::type_id(), ProfileWAN::type_id());
463
464        let a_canonical: <ProfileNAW as PersonProperty>::CanonicalValue =
465            ProfileNAW::make_canonical(a);
466        let b_canonical: <ProfileAWN as PersonProperty>::CanonicalValue =
467            ProfileAWN::make_canonical(b);
468        let c_canonical: <ProfileWAN as PersonProperty>::CanonicalValue =
469            ProfileWAN::make_canonical(c);
470
471        assert_eq!(a_canonical, b_canonical);
472        assert_eq!(a_canonical, c_canonical);
473
474        // Actually, all of the `Profile***::hash_property_value` methods should be the same,
475        // so we could use any single one.
476        assert_eq!(
477            ProfileNAW::hash_property_value(&a_canonical),
478            ProfileAWN::hash_property_value(&b_canonical)
479        );
480        assert_eq!(
481            ProfileNAW::hash_property_value(&a_canonical),
482            ProfileWAN::hash_property_value(&c_canonical)
483        );
484
485        // Since the canonical values are the same, we could have used any single one, but this
486        // demonstrates that we can convert from one order to another.
487        assert_eq!(ProfileNAW::make_uncanonical(b_canonical), a);
488        assert_eq!(ProfileAWN::make_uncanonical(c_canonical), b);
489        assert_eq!(ProfileWAN::make_uncanonical(a_canonical), c);
490    }
491
492    #[test]
493    fn test_multi_property_vs_property_query() {
494        let mut context = Context::new();
495
496        context
497            .add_person(((Name, "John"), (Age, 42), (Weight, 220.5)))
498            .unwrap();
499        context
500            .add_person(((Name, "Jane"), (Age, 22), (Weight, 180.5)))
501            .unwrap();
502        context
503            .add_person(((Name, "Bob"), (Age, 32), (Weight, 190.5)))
504            .unwrap();
505        context
506            .add_person(((Name, "Alice"), (Age, 22), (Weight, 170.5)))
507            .unwrap();
508
509        context.index_property(ProfileNAW);
510
511        {
512            let data = context.get_data(PeoplePlugin);
513            assert!(data
514                .property_indexes
515                .borrow()
516                .get(&ProfileNAW::type_id())
517                .is_some());
518        }
519
520        {
521            let example_query = ((Name, "Alice"), (Age, 22), (Weight, 170.5));
522            let query_multi_property_type_id = Query::multi_property_type_id(&example_query);
523            assert!(query_multi_property_type_id.is_some());
524            assert_eq!(ProfileNAW::type_id(), query_multi_property_type_id.unwrap());
525            assert_eq!(
526                Query::multi_property_value_hash(&example_query),
527                ProfileNAW::hash_property_value(&ProfileNAW::make_canonical(("Alice", 22, 170.5)))
528            );
529        }
530
531        context.with_query_results((ProfileNAW, ("John", 42, 220.5)), &mut |results| {
532            assert_eq!(results.len(), 1);
533        });
534    }
535
536    #[test]
537    fn test_get_display() {
538        let mut context = Context::new();
539        let person = context.add_person(((POu32, Some(42)), (Pu32, 22))).unwrap();
540        assert_eq!(
541            format!(
542                "{:}",
543                POu32::get_display(&context.get_person_property(person, POu32))
544            ),
545            "42"
546        );
547        assert_eq!(
548            format!(
549                "{:}",
550                Pu32::get_display(&context.get_person_property(person, Pu32))
551            ),
552            "22"
553        );
554        let person2 = context.add_person(((POu32, None), (Pu32, 11))).unwrap();
555        assert_eq!(
556            format!(
557                "{:}",
558                POu32::get_display(&context.get_person_property(person2, POu32))
559            ),
560            "None"
561        );
562    }
563
564    #[test]
565    fn test_debug_trait() {
566        let property = Pu32;
567        let debug_str = format!("{:?}", property);
568        assert_eq!(debug_str, "Pu32");
569
570        let property = POu32;
571        let debug_str = format!("{:?}", property);
572        assert_eq!(debug_str, "POu32");
573    }
574}