cpex_core/extensions/
monotonic.rs1use std::collections::HashSet;
15use std::hash::Hash;
16
17use serde::{Deserialize, Serialize};
18
19#[derive(Clone, Debug, Serialize, Deserialize)]
28#[serde(transparent)]
29pub struct MonotonicSet<T: Eq + Hash> {
30 inner: HashSet<T>,
31}
32
33impl<T: Eq + Hash> MonotonicSet<T> {
34 pub fn new() -> Self {
36 Self {
37 inner: HashSet::new(),
38 }
39 }
40
41 pub fn from_set(set: HashSet<T>) -> Self {
43 Self { inner: set }
44 }
45
46 pub fn insert(&mut self, value: T) -> bool {
48 self.inner.insert(value)
49 }
50
51 pub fn contains(&self, value: &T) -> bool {
53 self.inner.contains(value)
54 }
55
56 pub fn iter(&self) -> impl Iterator<Item = &T> {
58 self.inner.iter()
59 }
60
61 pub fn len(&self) -> usize {
63 self.inner.len()
64 }
65
66 pub fn is_empty(&self) -> bool {
68 self.inner.is_empty()
69 }
70
71 pub fn is_superset(&self, other: &MonotonicSet<T>) -> bool {
73 self.inner.is_superset(&other.inner)
74 }
75
76 pub fn as_set(&self) -> &HashSet<T> {
78 &self.inner
79 }
80
81 pub fn remove_with_declassifier(&mut self, value: &T, _token: &DeclassifierToken) -> bool {
84 self.inner.remove(value)
85 }
86}
87
88impl<T: Eq + Hash> Default for MonotonicSet<T> {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94pub struct DeclassifierToken {
97 _private: (),
98}
99
100impl DeclassifierToken {
101 #[allow(dead_code)]
103 pub(crate) fn new() -> Self {
104 Self { _private: () }
105 }
106}
107
108impl MonotonicSet<String> {
110 pub fn has_label(&self, label: &str) -> bool {
112 let lower = label.to_lowercase();
113 self.inner.iter().any(|l| l.to_lowercase() == lower)
114 }
115
116 pub fn add_label(&mut self, label: impl Into<String>) {
118 self.inner.insert(label.into());
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_monotonic_insert_only() {
128 let mut set = MonotonicSet::new();
129 set.insert("PII".to_string());
130 set.insert("CONFIDENTIAL".to_string());
131 assert!(set.contains(&"PII".to_string()));
132 assert_eq!(set.len(), 2);
133 }
135
136 #[test]
137 fn test_monotonic_superset() {
138 let mut before = MonotonicSet::new();
139 before.insert("PII".to_string());
140
141 let mut after = before.clone();
142 after.insert("HIPAA".to_string());
143
144 assert!(after.is_superset(&before));
145 assert!(!before.is_superset(&after));
146 }
147
148 #[test]
149 fn test_monotonic_declassifier() {
150 let mut set = MonotonicSet::new();
151 set.insert("PII".to_string());
152
153 let token = DeclassifierToken::new();
155 assert!(set.remove_with_declassifier(&"PII".to_string(), &token));
156 assert!(!set.contains(&"PII".to_string()));
157 }
158
159 #[test]
160 fn test_monotonic_has_label_case_insensitive() {
161 let mut set = MonotonicSet::new();
162 set.add_label("PII");
163 assert!(set.has_label("pii"));
164 assert!(set.has_label("PII"));
165 assert!(set.has_label("Pii"));
166 }
167
168 #[test]
169 fn test_monotonic_serde_roundtrip() {
170 let mut set = MonotonicSet::new();
171 set.insert("PII".to_string());
172 set.insert("HIPAA".to_string());
173
174 let json = serde_json::to_string(&set).unwrap();
175 let deserialized: MonotonicSet<String> = serde_json::from_str(&json).unwrap();
176 assert!(deserialized.contains(&"PII".to_string()));
177 assert!(deserialized.contains(&"HIPAA".to_string()));
178 }
179}