use std::collections::HashSet;
use std::hash::Hash;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(transparent)]
pub struct MonotonicSet<T: Eq + Hash> {
inner: HashSet<T>,
}
impl<T: Eq + Hash> MonotonicSet<T> {
pub fn new() -> Self {
Self {
inner: HashSet::new(),
}
}
pub fn from_set(set: HashSet<T>) -> Self {
Self { inner: set }
}
pub fn insert(&mut self, value: T) -> bool {
self.inner.insert(value)
}
pub fn contains(&self, value: &T) -> bool {
self.inner.contains(value)
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.inner.iter()
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn is_superset(&self, other: &MonotonicSet<T>) -> bool {
self.inner.is_superset(&other.inner)
}
pub fn as_set(&self) -> &HashSet<T> {
&self.inner
}
pub fn remove_with_declassifier(&mut self, value: &T, _token: &DeclassifierToken) -> bool {
self.inner.remove(value)
}
}
impl<T: Eq + Hash> Default for MonotonicSet<T> {
fn default() -> Self {
Self::new()
}
}
pub struct DeclassifierToken {
_private: (),
}
impl DeclassifierToken {
#[allow(dead_code)]
pub(crate) fn new() -> Self {
Self { _private: () }
}
}
impl MonotonicSet<String> {
pub fn has_label(&self, label: &str) -> bool {
let lower = label.to_lowercase();
self.inner.iter().any(|l| l.to_lowercase() == lower)
}
pub fn add_label(&mut self, label: impl Into<String>) {
self.inner.insert(label.into());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_monotonic_insert_only() {
let mut set = MonotonicSet::new();
set.insert("PII".to_string());
set.insert("CONFIDENTIAL".to_string());
assert!(set.contains(&"PII".to_string()));
assert_eq!(set.len(), 2);
}
#[test]
fn test_monotonic_superset() {
let mut before = MonotonicSet::new();
before.insert("PII".to_string());
let mut after = before.clone();
after.insert("HIPAA".to_string());
assert!(after.is_superset(&before));
assert!(!before.is_superset(&after));
}
#[test]
fn test_monotonic_declassifier() {
let mut set = MonotonicSet::new();
set.insert("PII".to_string());
let token = DeclassifierToken::new();
assert!(set.remove_with_declassifier(&"PII".to_string(), &token));
assert!(!set.contains(&"PII".to_string()));
}
#[test]
fn test_monotonic_has_label_case_insensitive() {
let mut set = MonotonicSet::new();
set.add_label("PII");
assert!(set.has_label("pii"));
assert!(set.has_label("PII"));
assert!(set.has_label("Pii"));
}
#[test]
fn test_monotonic_serde_roundtrip() {
let mut set = MonotonicSet::new();
set.insert("PII".to_string());
set.insert("HIPAA".to_string());
let json = serde_json::to_string(&set).unwrap();
let deserialized: MonotonicSet<String> = serde_json::from_str(&json).unwrap();
assert!(deserialized.contains(&"PII".to_string()));
assert!(deserialized.contains(&"HIPAA".to_string()));
}
}