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