Skip to main content

fret_ui_headless/table/
aggregation.rs

1//! TanStack-inspired aggregation helpers.
2//!
3//! This is intentionally small and `u64`-focused for now. The UI layer can format `u64` results
4//! (e.g. currency/units) as needed.
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum Aggregation {
8    #[default]
9    None,
10    Count,
11    SumU64,
12    MinU64,
13    MaxU64,
14    MeanU64,
15}
16
17pub fn aggregate_u64(
18    agg: Aggregation,
19    mut values: impl Iterator<Item = u64>,
20    count: usize,
21) -> Option<u64> {
22    match agg {
23        Aggregation::None => None,
24        Aggregation::Count => Some(count as u64),
25        Aggregation::SumU64 => values.try_fold(0u64, |acc, v| acc.checked_add(v)),
26        Aggregation::MinU64 => values.min(),
27        Aggregation::MaxU64 => values.max(),
28        Aggregation::MeanU64 => {
29            if count == 0 {
30                return None;
31            }
32            let sum = values.try_fold(0u64, |acc, v| acc.checked_add(v))?;
33            Some(sum / (count as u64))
34        }
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn count_ignores_values() {
44        assert_eq!(
45            aggregate_u64(Aggregation::Count, [1, 2, 3].into_iter(), 10),
46            Some(10)
47        );
48    }
49
50    #[test]
51    fn sum_checked() {
52        assert_eq!(
53            aggregate_u64(Aggregation::SumU64, [1, 2, 3].into_iter(), 3),
54            Some(6)
55        );
56        assert_eq!(
57            aggregate_u64(Aggregation::SumU64, [u64::MAX, 1].into_iter(), 2),
58            None
59        );
60    }
61
62    #[test]
63    fn min_max() {
64        assert_eq!(
65            aggregate_u64(Aggregation::MinU64, [3, 2, 1].into_iter(), 3),
66            Some(1)
67        );
68        assert_eq!(
69            aggregate_u64(Aggregation::MaxU64, [3, 2, 1].into_iter(), 3),
70            Some(3)
71        );
72        assert_eq!(aggregate_u64(Aggregation::MinU64, [].into_iter(), 0), None);
73        assert_eq!(aggregate_u64(Aggregation::MaxU64, [].into_iter(), 0), None);
74    }
75
76    #[test]
77    fn mean_uses_count_and_is_checked() {
78        assert_eq!(
79            aggregate_u64(Aggregation::MeanU64, [10, 20].into_iter(), 2),
80            Some(15)
81        );
82        assert_eq!(
83            aggregate_u64(Aggregation::MeanU64, [10].into_iter(), 0),
84            None
85        );
86        assert_eq!(
87            aggregate_u64(Aggregation::MeanU64, [u64::MAX, 1].into_iter(), 2),
88            None
89        );
90    }
91}