sea_core/policy/
three_valued.rs

1//! Three-valued boolean logic (SQL-like UNKNOWN) for SEA policy evaluation.
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4pub enum ThreeValuedBool {
5    True,
6    False,
7    Null,
8}
9
10impl ThreeValuedBool {
11    pub fn and(self, other: Self) -> Self {
12        use ThreeValuedBool::*;
13        match (self, other) {
14            (False, _) | (_, False) => False,
15            (True, True) => True,
16            (True, Null) | (Null, True) | (Null, Null) => Null,
17        }
18    }
19
20    pub fn or(self, other: Self) -> Self {
21        use ThreeValuedBool::*;
22        match (self, other) {
23            (True, _) | (_, True) => True,
24            (False, False) => False,
25            _ => Null,
26        }
27    }
28
29    #[allow(clippy::should_implement_trait)]
30    pub fn not(self) -> Self {
31        !self
32    }
33
34    pub fn implies(self, other: Self) -> Self {
35        // A -> B == (not A) or B
36        (!self).or(other)
37    }
38
39    pub fn from_option_bool(v: Option<bool>) -> Self {
40        match v {
41            Some(true) => ThreeValuedBool::True,
42            Some(false) => ThreeValuedBool::False,
43            None => ThreeValuedBool::Null,
44        }
45    }
46
47    pub fn into_option(self) -> Option<bool> {
48        match self {
49            ThreeValuedBool::True => Some(true),
50            ThreeValuedBool::False => Some(false),
51            ThreeValuedBool::Null => None,
52        }
53    }
54
55    pub fn fold_and<I: IntoIterator<Item = Self>>(iter: I) -> Self {
56        let mut seen_null = false;
57        for v in iter {
58            match v {
59                ThreeValuedBool::False => return ThreeValuedBool::False,
60                ThreeValuedBool::Null => seen_null = true,
61                ThreeValuedBool::True => {}
62            }
63        }
64        if seen_null {
65            ThreeValuedBool::Null
66        } else {
67            ThreeValuedBool::True
68        }
69    }
70
71    pub fn fold_or<I: IntoIterator<Item = Self>>(iter: I) -> Self {
72        let mut seen_null = false;
73        for v in iter {
74            match v {
75                ThreeValuedBool::True => return ThreeValuedBool::True,
76                ThreeValuedBool::Null => seen_null = true,
77                ThreeValuedBool::False => {}
78            }
79        }
80        if seen_null {
81            ThreeValuedBool::Null
82        } else {
83            ThreeValuedBool::False
84        }
85    }
86}
87
88impl std::ops::Not for ThreeValuedBool {
89    type Output = Self;
90
91    fn not(self) -> Self::Output {
92        use ThreeValuedBool::*;
93        match self {
94            True => False,
95            False => True,
96            Null => Null,
97        }
98    }
99}
100
101pub mod aggregators {
102    #![allow(dead_code)]
103    use rust_decimal::Decimal;
104
105    /// Sum that returns None (Null) if any element is missing.
106    pub fn sum_nullable(items: &[Option<Decimal>]) -> Option<Decimal> {
107        let mut total = Decimal::ZERO;
108        let mut any_null = false;
109        for item in items {
110            match item {
111                Some(v) => total += *v,
112                None => any_null = true,
113            }
114        }
115        if any_null {
116            None
117        } else {
118            Some(total)
119        }
120    }
121
122    /// Sum that ignores NULLs.
123    pub fn sum_nonnull(items: &[Option<Decimal>]) -> Decimal {
124        let mut total = Decimal::ZERO;
125        for v in items.iter().flatten() {
126            total += *v;
127        }
128        total
129    }
130
131    /// Count all items, including nulls.
132    pub fn count_all<T>(items: &[Option<T>]) -> usize {
133        items.len()
134    }
135
136    /// Count only non-null items.
137    pub fn count_nonnull<T>(items: &[Option<T>]) -> usize {
138        items.iter().filter(|x| x.is_some()).count()
139    }
140}