Skip to main content

confium_attributes/
evaluate.rs

1//! Predicate evaluation.
2
3use crate::ast::Predicate;
4use std::collections::{HashMap, HashSet};
5
6/// A signer's attribute map. Keys are attribute names (e.g., "region",
7/// "role:director", "expertise:metrology"). Values are sets of strings.
8#[derive(Debug, Clone, Default)]
9pub struct SignerAttributes {
10    pub attrs: HashMap<String, HashSet<String>>,
11}
12
13impl SignerAttributes {
14    /// Construct empty.
15    pub fn new() -> Self {
16        Self::default()
17    }
18
19    /// Add a value to an attribute.
20    pub fn add(&mut self, key: impl Into<String>, value: impl Into<String>) {
21        self.attrs
22            .entry(key.into())
23            .or_default()
24            .insert(value.into());
25    }
26
27    /// Does this signer have attribute `key`?
28    pub fn has(&self, key: &str) -> bool {
29        self.attrs.contains_key(key) && !self.attrs[key].is_empty()
30    }
31
32    /// Get the values for `key`.
33    pub fn values(&self, key: &str) -> Vec<String> {
34        self.attrs
35            .get(key)
36            .map(|s| s.iter().cloned().collect())
37            .unwrap_or_default()
38    }
39}
40
41/// Evaluate `predicate` against a list of `signers`. Returns `true` iff satisfied.
42pub fn evaluate(predicate: &Predicate, signers: &[&SignerAttributes]) -> bool {
43    match predicate {
44        Predicate::MinCount { attribute, count } => {
45            let n = signers.iter().filter(|s| s.has(attribute)).count();
46            n >= *count
47        }
48        Predicate::MinDistinct { attribute, count } => {
49            let all_values: HashSet<&str> = signers
50                .iter()
51                .flat_map(|s| s.attrs.get(attribute).into_iter().flatten())
52                .map(|s| s.as_str())
53                .collect();
54            all_values.len() >= *count
55        }
56        Predicate::None { attribute } => !signers.iter().any(|s| s.has(attribute)),
57        Predicate::Any { attribute } => signers.iter().any(|s| s.has(attribute)),
58        Predicate::All { attribute } => {
59            !signers.is_empty() && signers.iter().all(|s| s.has(attribute))
60        }
61        Predicate::And(preds) => preds.iter().all(|p| evaluate(p, signers)),
62        Predicate::Or(preds) => preds.iter().any(|p| evaluate(p, signers)),
63        Predicate::Not(p) => !evaluate(p, signers),
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    fn alice() -> SignerAttributes {
72        let mut a = SignerAttributes::new();
73        a.add("role:director", "yes");
74        a.add("region", "europe");
75        a.add("expertise", "metrology");
76        a
77    }
78
79    fn bob() -> SignerAttributes {
80        let mut a = SignerAttributes::new();
81        a.add("role:director", "yes");
82        a.add("region", "americas");
83        a
84    }
85
86    fn carol() -> SignerAttributes {
87        let mut a = SignerAttributes::new();
88        a.add("role:director", "yes");
89        a.add("region", "asia-pacific");
90        a
91    }
92
93    #[test]
94    fn min_count_satisfied() {
95        let a = alice();
96        let b = bob();
97        let c = carol();
98        let signers = vec![&a, &b, &c];
99        let pred = Predicate::MinCount {
100            attribute: "role:director".into(),
101            count: 3,
102        };
103        assert!(evaluate(&pred, &signers));
104    }
105
106    #[test]
107    fn min_count_not_satisfied() {
108        let a = alice();
109        let b = bob();
110        let signers = vec![&a, &b];
111        let pred = Predicate::MinCount {
112            attribute: "role:director".into(),
113            count: 3,
114        };
115        assert!(!evaluate(&pred, &signers));
116    }
117
118    #[test]
119    fn min_distinct_geography() {
120        let a = alice();
121        let b = bob();
122        let c = carol();
123        let signers = vec![&a, &b, &c];
124        let pred = Predicate::MinDistinct {
125            attribute: "region".into(),
126            count: 3,
127        };
128        assert!(evaluate(&pred, &signers));
129    }
130
131    #[test]
132    fn none_predicate_blocks_signer() {
133        let a = alice();
134        let b = bob();
135        let signers = vec![&a, &b];
136        let pred = Predicate::None {
137            attribute: "nationality:cn".into(),
138        };
139        assert!(evaluate(&pred, &signers));
140    }
141
142    #[test]
143    fn boolean_composition() {
144        let a = alice();
145        let b = bob();
146        let c = carol();
147        let signers = vec![&a, &b, &c];
148        let pred = Predicate::And(vec![
149            Predicate::MinCount {
150                attribute: "role:director".into(),
151                count: 3,
152            },
153            Predicate::MinDistinct {
154                attribute: "region".into(),
155                count: 3,
156            },
157            Predicate::Any {
158                attribute: "expertise".into(),
159            },
160        ]);
161        assert!(evaluate(&pred, &signers));
162    }
163
164    #[test]
165    fn or_composition() {
166        let a = alice();
167        let signers = vec![&a];
168        let pred = Predicate::Or(vec![
169            Predicate::MinCount {
170                attribute: "role:director".into(),
171                count: 5,
172            },
173            Predicate::Any {
174                attribute: "expertise".into(),
175            },
176        ]);
177        assert!(evaluate(&pred, &signers));
178    }
179}