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
72impl 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}