confium_attributes/
evaluate.rs1use crate::ast::Predicate;
4use std::collections::{HashMap, HashSet};
5
6#[derive(Debug, Clone, Default)]
9pub struct SignerAttributes {
10 pub attrs: HashMap<String, HashSet<String>>,
11}
12
13impl SignerAttributes {
14 pub fn new() -> Self {
16 Self::default()
17 }
18
19 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 pub fn has(&self, key: &str) -> bool {
29 self.attrs.contains_key(key) && !self.attrs[key].is_empty()
30 }
31
32 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
41pub 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}