ixa/people/
property.rs

1use crate::people::data::PersonPropertyHolder;
2use crate::{Context, PersonId};
3use serde::Serialize;
4use std::fmt::Debug;
5
6/// An individual characteristic or state related to a person, such as age or
7/// disease status.
8///
9/// Person properties should be defined with the [`define_person_property!()`],
10/// [`define_person_property_with_default!()`] and [`define_derived_property!()`]
11/// macros.
12pub trait PersonProperty: Copy + 'static {
13    type Value: Copy + Debug + PartialEq + Serialize;
14    #[must_use]
15    fn is_derived() -> bool {
16        false
17    }
18    #[must_use]
19    fn is_required() -> bool {
20        false
21    }
22    #[must_use]
23    fn dependencies() -> Vec<Box<dyn PersonPropertyHolder>> {
24        panic!("Dependencies not implemented");
25    }
26    fn register_dependencies(_: &Context) {
27        panic!("Dependencies not implemented");
28    }
29    fn compute(context: &Context, person_id: PersonId) -> Self::Value;
30    fn get_instance() -> Self;
31    fn name() -> &'static str;
32    fn get_display(value: &Self::Value) -> String;
33}
34
35#[macro_export]
36macro_rules! __define_person_property_common {
37    ($person_property:ident, $value:ty, $compute_fn:expr, $is_required:expr, $display_impl:expr) => {
38        #[derive(Debug, Copy, Clone)]
39        pub struct $person_property;
40        impl $crate::people::PersonProperty for $person_property {
41            type Value = $value;
42            fn compute(
43                _context: &$crate::context::Context,
44                _person: $crate::people::PersonId,
45            ) -> Self::Value {
46                $compute_fn(_context, _person)
47            }
48            fn is_required() -> bool {
49                $is_required
50            }
51            fn get_instance() -> Self {
52                $person_property
53            }
54            fn name() -> &'static str {
55                stringify!($person_property)
56            }
57            fn get_display(value: &Self::Value) -> String {
58                $display_impl(value)
59            }
60        }
61    };
62}
63
64/// Defines a person property with the following parameters:
65/// * `$person_property`: A name for the identifier type of the property
66/// * `$value`: The type of the property's value
67/// * `$initialize`: (Optional) A function that takes a `Context` and `PersonId` and
68///   returns the initial value. If it is not defined, calling `get_person_property`
69///   on the property without explicitly setting a value first will panic.
70#[macro_export]
71macro_rules! define_person_property {
72    // Option<T> with initializer
73    ($person_property:ident, Option<$value:ty>, $initialize:expr) => {
74        $crate::__define_person_property_common!(
75            $person_property,
76            Option<$value>,
77            $initialize,
78            false,
79            |&value| {
80                match value {
81                    Some(v) => format!("{:?}", v),
82                    None => "None".to_string(),
83                }
84            }
85        );
86    };
87    // T with initializer
88    ($person_property:ident, $value:ty, $initialize:expr) => {
89        $crate::__define_person_property_common!(
90            $person_property,
91            $value,
92            $initialize,
93            false,
94            |&value| format!("{:?}", value)
95        );
96    };
97    // Option<T> without initializer
98    ($person_property:ident, Option<$value:ty>) => {
99        $crate::__define_person_property_common!(
100            $person_property,
101            Option<$value>,
102            |_, _| panic!("Property not initialized when person created."),
103            true,
104            |&value| {
105                match value {
106                    Some(v) => format!("{:?}", v),
107                    None => "None".to_string(),
108                }
109            }
110        );
111    };
112    // T without initializer
113    ($person_property:ident, $value:ty) => {
114        $crate::__define_person_property_common!(
115            $person_property,
116            $value,
117            |_, _| panic!("Property not initialized when person created."),
118            true,
119            |&value| format!("{:?}", value)
120        );
121    };
122}
123pub use define_person_property;
124
125/// Defines a person property with the following parameters:
126/// * `$person_property`: A name for the identifier type of the property
127/// * `$value`: The type of the property's value
128/// * `$default`: An initial value
129#[macro_export]
130macro_rules! define_person_property_with_default {
131    ($person_property:ident, Option<$value:ty>, $default:expr) => {
132        $crate::define_person_property!(
133            $person_property,
134            Option<$value>,
135            |_context, _person_id| { $default }
136        );
137    };
138    ($person_property:ident, $value:ty, $default:expr) => {
139        $crate::define_person_property!($person_property, $value, |_context, _person_id| {
140            $default
141        });
142    };
143}
144pub use define_person_property_with_default;
145
146/// Defines a derived person property with the following parameters:
147/// * `$person_property`: A name for the identifier type of the property
148/// * `$value`: The type of the property's value
149/// * `[$($dependency),+]`: A list of person properties the derived property depends on
150/// * `[$($dependency),*]`: A list of global properties the derived property depends on (optional)
151/// * $calculate: A closure that takes the values of each dependency and returns the derived value
152#[macro_export]
153macro_rules! define_derived_property {
154    (
155        $derived_property:ident,
156        $value:ty,
157        [$($dependency:ident),*],
158        [$($global_dependency:ident),*],
159        |$($param:ident),+| $derive_fn:expr
160    ) => {
161        #[derive(Debug, Copy, Clone)]
162        pub struct $derived_property;
163
164        impl $crate::people::PersonProperty for $derived_property {
165            type Value = $value;
166            fn compute(context: &$crate::context::Context, person_id: $crate::people::PersonId) -> Self::Value {
167                #[allow(unused_imports)]
168                use $crate::global_properties::ContextGlobalPropertiesExt;
169                #[allow(unused_parens)]
170                let ($($param,)*) = (
171                    $(context.get_person_property(person_id, $dependency)),*,
172                    $(
173                        *context.get_global_property_value($global_dependency)
174                            .expect(&format!("Global property {} not initialized", stringify!($global_dependency)))
175                    ),*
176                );
177                (|$($param),+| $derive_fn)($($param),+)
178            }
179            fn is_derived() -> bool { true }
180            fn dependencies() -> Vec<Box<dyn $crate::people::PersonPropertyHolder>> {
181                vec![$(Box::new($dependency)),+]
182            }
183            fn register_dependencies(context: &$crate::context::Context) {
184                $(context.register_property::<$dependency>();)+
185            }
186            fn get_instance() -> Self {
187                $derived_property
188            }
189            fn name() -> &'static str {
190                stringify!($derived_property)
191            }
192            fn get_display(value: &Self::Value) -> String {
193                format!("{:?}", value)
194            }
195        }
196    };
197    (
198        $derived_property:ident,
199        $value:ty,
200        [$($dependency:ident),*],
201        |$($param:ident),+| $derive_fn:expr
202    ) => {
203        define_derived_property!(
204            $derived_property,
205            $value,
206            [$($dependency),*],
207            [],
208            |$($param),+| $derive_fn
209        );
210    };
211}
212pub use define_derived_property;
213
214#[macro_export]
215macro_rules! define_multi_property_index {
216    (
217        $($dependency:ident),+
218    ) => {
219        $crate::paste::paste! {
220            define_derived_property!(
221                [< $($dependency)+ Query >],
222                $crate::people::index::IndexValue,
223                [$($dependency),+],
224                |$([< $dependency:lower >]),+| {
225                    let mut combined = vec!(
226                        $(
227                            (std::any::TypeId::of::<$dependency>(),
228                            $crate::people::index::IndexValue::compute(&[< $dependency:lower >]))
229                        ),*
230                    );
231                    combined.sort_by(|a, b| a.0.cmp(&b.0));
232                    let values = combined.iter().map(|x| x.1).collect::<Vec<_>>();
233                    $crate::people::index::IndexValue::compute(&values)
234                }
235            );
236
237            $crate::people::index::add_multi_property_index::<[< $($dependency)+ Query >]>(
238                #[allow(clippy::useless_vec)]
239                &vec![
240                    $(
241                        std::any::TypeId::of::<$dependency>(),
242                    )*
243                ],
244                std::any::TypeId::of::<[< $($dependency)+ Query >]>(),
245            );
246        }
247    };
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253    use crate::prelude::*;
254
255    define_person_property!(Pu32, u32);
256    define_person_property!(POu32, Option<u32>);
257
258    #[test]
259    fn test_get_display() {
260        let mut context = Context::new();
261        let person = context.add_person(((POu32, Some(42)), (Pu32, 22))).unwrap();
262        assert_eq!(
263            format!(
264                "{:}",
265                POu32::get_display(&context.get_person_property(person, POu32))
266            ),
267            "42"
268        );
269        assert_eq!(
270            format!(
271                "{:}",
272                Pu32::get_display(&context.get_person_property(person, Pu32))
273            ),
274            "22"
275        );
276        let person2 = context.add_person(((POu32, None), (Pu32, 11))).unwrap();
277        assert_eq!(
278            format!(
279                "{:}",
280                POu32::get_display(&context.get_person_property(person2, POu32))
281            ),
282            "None"
283        );
284    }
285
286    #[test]
287    fn test_debug_trait() {
288        let value = Pu32;
289        let debug_str = format!("{:?}", value);
290        // You can check for the struct name or any expected output
291        assert!(debug_str.contains("Pu32"));
292        let value = POu32;
293        let debug_str = format!("{:?}", value);
294        // You can check for the struct name or any expected output
295        assert!(debug_str.contains("POu32"));
296    }
297}