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
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_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}