ixa/
tabulator.rs

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