icydb_core/traits/
sanitize.rs

1use std::collections::{HashMap, HashSet};
2
3///
4/// Sanitize
5///
6
7pub trait Sanitize: SanitizeAuto + SanitizeCustom {}
8
9impl<T> Sanitize for T where T: SanitizeAuto + SanitizeCustom {}
10
11///
12/// SanitizeContext
13///
14/// Context that can be provided during sanitization.
15/// This may include runtime or request-specific data (timestamp, mode, actor, etc.).
16///
17/// NOTE: SanitizeContext is reserved for future context-aware sanitization.
18/// The *_with() methods are currently thin wrappers that delegate to the
19/// stateless versions. In the future, we may pass runtime data (e.g. now, is_new,
20/// actor) here so sanitizers can behave contextually without changing the trait shape.
21///
22
23#[derive(Clone, Debug, Default)]
24pub struct SanitizeContext {
25    pub now: Option<u64>,
26    pub is_new: Option<bool>,
27}
28
29impl SanitizeContext {
30    #[must_use]
31    pub const fn new(now: Option<u64>, is_new: Option<bool>) -> Self {
32        Self { now, is_new }
33    }
34}
35
36///
37/// SanitizeAuto
38///
39/// Derived code that is used to generate sanitization rules for a type and
40/// its children, via schema sanitizers.
41///
42/// This shouldn’t be used with primitive types directly, only for generated code.
43///
44
45pub trait SanitizeAuto {
46    fn sanitize_self(&mut self) {}
47    fn sanitize_children(&mut self) {}
48
49    fn sanitize_self_with(&mut self, _ctx: &SanitizeContext) {
50        self.sanitize_self();
51    }
52
53    fn sanitize_children_with(&mut self, _ctx: &SanitizeContext) {
54        self.sanitize_children();
55    }
56}
57
58impl<T: SanitizeAuto> SanitizeAuto for Box<T> {
59    fn sanitize_self(&mut self) {
60        (**self).sanitize_self();
61    }
62
63    fn sanitize_children(&mut self) {
64        (**self).sanitize_children();
65    }
66}
67
68impl<T: SanitizeAuto> SanitizeAuto for Option<T> {
69    fn sanitize_self(&mut self) {
70        if let Some(inner) = self.as_mut() {
71            inner.sanitize_self();
72        }
73    }
74
75    fn sanitize_children(&mut self) {
76        if let Some(inner) = self.as_mut() {
77            inner.sanitize_children();
78        }
79    }
80}
81
82impl<T: SanitizeAuto> SanitizeAuto for Vec<T> {
83    fn sanitize_self(&mut self) {
84        for v in self {
85            v.sanitize_self();
86        }
87    }
88
89    fn sanitize_children(&mut self) {
90        for v in self {
91            v.sanitize_children();
92        }
93    }
94}
95
96impl<T: SanitizeAuto, S> SanitizeAuto for HashSet<T, S> {
97    fn sanitize_self(&mut self) {
98        // keys must not change
99    }
100
101    fn sanitize_children(&mut self) {
102        // keys must not change
103    }
104}
105
106impl<K: SanitizeAuto, V: SanitizeAuto, S> SanitizeAuto for HashMap<K, V, S> {
107    fn sanitize_self(&mut self) {
108        for v in self.values_mut() {
109            v.sanitize_self();
110        }
111    }
112
113    fn sanitize_children(&mut self) {
114        // keys must not change
115        for v in self.values_mut() {
116            v.sanitize_children();
117        }
118    }
119}
120
121impl_primitive!(SanitizeAuto);
122
123///
124/// SanitizeCustom
125///
126/// Custom sanitization behaviour that can be added to any type.
127///
128
129pub trait SanitizeCustom {
130    fn sanitize_custom(&mut self) {}
131
132    fn sanitize_custom_with(&mut self, _ctx: &SanitizeContext) {
133        self.sanitize_custom();
134    }
135}
136
137impl<T: SanitizeCustom> SanitizeCustom for Box<T> {
138    fn sanitize_custom(&mut self) {
139        (**self).sanitize_custom();
140    }
141}
142
143impl<T: SanitizeCustom> SanitizeCustom for Option<T> {
144    fn sanitize_custom(&mut self) {
145        if let Some(inner) = self.as_mut() {
146            inner.sanitize_custom();
147        }
148    }
149}
150
151impl<T: SanitizeCustom> SanitizeCustom for Vec<T> {
152    fn sanitize_custom(&mut self) {
153        for v in self {
154            v.sanitize_custom();
155        }
156    }
157}
158
159impl<T: SanitizeCustom, S> SanitizeCustom for HashSet<T, S> {
160    // keys must not change
161}
162
163impl<K: SanitizeCustom, V: SanitizeCustom, S> SanitizeCustom for HashMap<K, V, S> {
164    fn sanitize_custom(&mut self) {
165        // keys must not change
166        for v in self.values_mut() {
167            v.sanitize_custom();
168        }
169    }
170}
171
172impl_primitive!(SanitizeCustom);
173
174///
175/// TESTS
176///
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    /// Dummy type that mutates itself during sanitization
183    #[derive(Debug, Eq, Hash, PartialEq)]
184    struct NeedsSanitizing(i32);
185
186    impl SanitizeAuto for NeedsSanitizing {
187        fn sanitize_self(&mut self) {
188            if self.0 < 0 {
189                self.0 = 0;
190            }
191        }
192    }
193
194    impl SanitizeCustom for NeedsSanitizing {
195        fn sanitize_custom(&mut self) {
196            if self.0 > 100 {
197                self.0 = 100;
198            }
199        }
200    }
201
202    #[test]
203    fn test_sanitize_auto_and_custom() {
204        let mut x = NeedsSanitizing(-5);
205        x.sanitize_self();
206        assert_eq!(x.0, 0);
207
208        let mut y = NeedsSanitizing(200);
209        y.sanitize_custom();
210        assert_eq!(y.0, 100);
211    }
212
213    #[test]
214    fn test_vec_sanitization() {
215        let mut v = vec![NeedsSanitizing(-1), NeedsSanitizing(150)];
216        v.sanitize_self();
217        v.sanitize_custom();
218        assert_eq!(v[0].0, 0);
219        assert_eq!(v[1].0, 100);
220    }
221}