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(T::type_id());
17        Ok(())
18    }
19    fn get_typelist(&self) -> Vec<TypeId> {
20        vec![T::type_id()]
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(T~N::type_id());
44                    )*
45                    Ok(())
46                }
47
48                fn get_typelist(&self) -> Vec<TypeId> {
49                    vec![
50                    #(
51                        T~N::type_id(),
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_person_property, define_person_property_with_default, Context, ContextPeopleExt,
97        HashSet, HashSetExt, PersonProperty,
98    };
99    use std::cell::RefCell;
100
101    define_person_property!(Age, u8);
102    type RiskCategoryValue = u8;
103    define_person_property!(RiskCategory, RiskCategoryValue);
104    define_person_property_with_default!(IsRunner, bool, false);
105    define_person_property_with_default!(IsSwimmer, bool, false);
106
107    #[test]
108    fn test_tabulator() {
109        let cols = (Age, RiskCategory);
110        assert_eq!(
111            cols.get_typelist(),
112            vec![Age::type_id(), RiskCategory::type_id()]
113        );
114        assert_eq!(cols.get_columns(), vec!["Age", "RiskCategory"]);
115    }
116
117    fn tabulate_properties_test_setup<T: Tabulator>(
118        tabulator: &T,
119        setup: impl FnOnce(&mut Context),
120        expected_values: &HashSet<(Vec<String>, usize)>,
121    ) {
122        let mut context = Context::new();
123        setup(&mut context);
124
125        let results: RefCell<HashSet<(Vec<String>, usize)>> = RefCell::new(HashSet::new());
126        context.tabulate_person_properties(tabulator, |_context, values, count| {
127            results.borrow_mut().insert((values.to_vec(), count));
128        });
129
130        let results = &*results.borrow();
131        assert_eq!(results, expected_values);
132    }
133
134    #[test]
135    fn test_periodic_report() {
136        let tabulator = (IsRunner,);
137        let mut expected = HashSet::new();
138        expected.insert((vec!["true".to_string()], 1));
139        expected.insert((vec!["false".to_string()], 1));
140        tabulate_properties_test_setup(
141            &tabulator,
142            |context| {
143                let bob = context.add_person(()).unwrap();
144                context.add_person(()).unwrap();
145                context.set_person_property(bob, IsRunner, true);
146            },
147            &expected,
148        );
149    }
150
151    #[test]
152    fn test_get_counts_multi() {
153        let tabulator = (IsRunner, IsSwimmer);
154        let mut expected = HashSet::new();
155        expected.insert((vec!["false".to_string(), "false".to_string()], 3));
156        expected.insert((vec!["false".to_string(), "true".to_string()], 1));
157        expected.insert((vec!["true".to_string(), "false".to_string()], 1));
158        expected.insert((vec!["true".to_string(), "true".to_string()], 1));
159
160        tabulate_properties_test_setup(
161            &tabulator,
162            |context| {
163                context.add_person(()).unwrap();
164                context.add_person(()).unwrap();
165                context.add_person(()).unwrap();
166                let bob = context.add_person(()).unwrap();
167                let anne = context.add_person(()).unwrap();
168                let charlie = context.add_person(()).unwrap();
169
170                context.set_person_property(bob, IsRunner, true);
171                context.set_person_property(charlie, IsRunner, true);
172                context.set_person_property(anne, IsSwimmer, true);
173                context.set_person_property(charlie, IsSwimmer, true);
174            },
175            &expected,
176        );
177    }
178}