ixa/
tabulator.rs

1use crate::people::external_api::ContextPeopleExtCrate;
2use crate::people::PersonProperty;
3use crate::{Context, ContextPeopleExt, IxaError};
4use seq_macro::seq;
5use std::any::TypeId;
6pub trait Tabulator {
7    #[allow(clippy::missing_errors_doc)]
8    fn setup(&self, context: &Context) -> Result<(), IxaError>;
9    fn get_typelist(&self) -> Vec<TypeId>;
10    fn get_columns(&self) -> Vec<String>;
11}
12
13impl<T: PersonProperty> Tabulator for (T,) {
14    fn setup(&self, context: &Context) -> Result<(), IxaError> {
15        context.register_property::<T>();
16        context.index_property_by_id(std::any::TypeId::of::<T>());
17        Ok(())
18    }
19    fn get_typelist(&self) -> Vec<TypeId> {
20        vec![std::any::TypeId::of::<T>()]
21    }
22    fn get_columns(&self) -> Vec<String> {
23        vec![String::from(T::name())]
24    }
25}
26
27macro_rules! impl_tabulator {
28    ($ct:expr) => {
29        seq!(N in 0..$ct {
30            impl<
31                #(
32                    T~N : PersonProperty,
33                )*
34            > Tabulator for (
35                #(
36                    T~N,
37                )*
38            )
39            {
40                fn setup(&self, context: &Context) -> Result<(), IxaError> {
41                    #(
42                        context.register_property::<T~N>();
43                        context.index_property_by_id(std::any::TypeId::of::<T~N>());
44                    )*
45                    Ok(())
46                }
47
48                fn get_typelist(&self) -> Vec<TypeId> {
49                    vec![
50                    #(
51                        std::any::TypeId::of::<T~N>(),
52                    )*
53                    ]
54                }
55
56                fn get_columns(&self) -> Vec<String> {
57                    vec![
58                    #(
59                        String::from(T~N::name()),
60                    )*
61                    ]
62                }
63            }
64        });
65    }
66}
67
68seq!(Z in 2..20 {
69    impl_tabulator!(Z);
70});
71
72// Implement Tabulator for the special case where we have type ids and not
73// types. Note that we can't register properties here, so this may fail.
74impl Tabulator for Vec<(TypeId, String)> {
75    #[allow(clippy::missing_errors_doc)]
76    fn setup(&self, context: &Context) -> Result<(), IxaError> {
77        for (t, _) in self {
78            context.index_property_by_id(*t);
79        }
80        Ok(())
81    }
82
83    fn get_typelist(&self) -> Vec<TypeId> {
84        self.iter().map(|a| a.0).collect()
85    }
86
87    fn get_columns(&self) -> Vec<String> {
88        self.iter().map(|a| a.1.clone()).collect()
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::Tabulator;
95    use crate::{
96        define_derived_property, define_person_property, define_person_property_with_default,
97        Context, ContextPeopleExt,
98    };
99    use crate::{HashSet, HashSetExt};
100    use std::any::TypeId;
101    use std::cell::RefCell;
102
103    define_person_property!(Age, u8);
104    type RiskCategoryValue = u8;
105    define_person_property!(RiskCategory, RiskCategoryValue);
106    define_person_property_with_default!(IsRunner, bool, false);
107    define_person_property_with_default!(IsSwimmer, bool, false);
108    define_derived_property!(AdultSwimmer, bool, [IsSwimmer, Age], |is_swimmer, age| {
109        is_swimmer && age >= 18
110    });
111
112    #[test]
113    fn test_tabulator() {
114        let cols = (Age, RiskCategory);
115        assert_eq!(
116            cols.get_typelist(),
117            vec![TypeId::of::<Age>(), TypeId::of::<RiskCategory>()]
118        );
119        assert_eq!(cols.get_columns(), vec!["Age", "RiskCategory"]);
120    }
121
122    fn tabulate_properties_test_setup<T: Tabulator>(
123        tabulator: &T,
124        setup: impl FnOnce(&mut Context),
125        expected_values: &HashSet<(Vec<String>, usize)>,
126    ) {
127        let mut context = Context::new();
128        setup(&mut context);
129
130        let results: RefCell<HashSet<(Vec<String>, usize)>> = RefCell::new(HashSet::new());
131        context.tabulate_person_properties(tabulator, |_context, values, count| {
132            results.borrow_mut().insert((values.to_vec(), count));
133        });
134
135        let results = &*results.borrow();
136        assert_eq!(results, expected_values);
137    }
138
139    #[test]
140    fn test_periodic_report() {
141        let tabulator = (IsRunner,);
142        let mut expected = HashSet::new();
143        expected.insert((vec!["true".to_string()], 1));
144        expected.insert((vec!["false".to_string()], 1));
145        tabulate_properties_test_setup(
146            &tabulator,
147            |context| {
148                let bob = context.add_person(()).unwrap();
149                context.add_person(()).unwrap();
150                context.set_person_property(bob, IsRunner, true);
151            },
152            &expected,
153        );
154    }
155
156    #[test]
157    fn test_get_counts_multi() {
158        let tabulator = (IsRunner, IsSwimmer);
159        let mut expected = HashSet::new();
160        expected.insert((vec!["false".to_string(), "false".to_string()], 3));
161        expected.insert((vec!["false".to_string(), "true".to_string()], 1));
162        expected.insert((vec!["true".to_string(), "false".to_string()], 1));
163        expected.insert((vec!["true".to_string(), "true".to_string()], 1));
164
165        tabulate_properties_test_setup(
166            &tabulator,
167            |context| {
168                context.add_person(()).unwrap();
169                context.add_person(()).unwrap();
170                context.add_person(()).unwrap();
171                let bob = context.add_person(()).unwrap();
172                let anne = context.add_person(()).unwrap();
173                let charlie = context.add_person(()).unwrap();
174
175                context.set_person_property(bob, IsRunner, true);
176                context.set_person_property(charlie, IsRunner, true);
177                context.set_person_property(anne, IsSwimmer, true);
178                context.set_person_property(charlie, IsSwimmer, true);
179            },
180            &expected,
181        );
182    }
183}