icydb_core/visitor/
traits.rs1use super::{
7 PathSegment, VisitorContext, VisitorCore, VisitorMutCore, perform_visit, perform_visit_mut,
8};
9
10pub trait Visitable: Sanitize + Validate {
23 fn drive(&self, _: &mut dyn VisitorCore) {}
24 fn drive_mut(&mut self, _: &mut dyn VisitorMutCore) {}
25}
26
27impl<T: Visitable> Visitable for Option<T> {
36 fn drive(&self, visitor: &mut dyn VisitorCore) {
37 if let Some(value) = self.as_ref() {
38 perform_visit(visitor, value, PathSegment::Empty);
39 }
40 }
41
42 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
43 if let Some(value) = self.as_mut() {
44 perform_visit_mut(visitor, value, PathSegment::Empty);
45 }
46 }
47}
48
49impl<T: Visitable> Visitable for Vec<T> {
50 fn drive(&self, visitor: &mut dyn VisitorCore) {
51 for (i, value) in self.iter().enumerate() {
52 perform_visit(visitor, value, i);
53 }
54 }
55
56 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
57 for (i, value) in self.iter_mut().enumerate() {
58 perform_visit_mut(visitor, value, i);
59 }
60 }
61}
62
63impl<T: Visitable + ?Sized> Visitable for Box<T> {
64 fn drive(&self, visitor: &mut dyn VisitorCore) {
65 (**self).drive(visitor);
66 }
67
68 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
69 (**self).drive_mut(visitor);
70 }
71}
72
73impl_primitive!(Visitable);
75
76pub trait Sanitize: SanitizeAuto + SanitizeCustom {}
84
85impl<T> Sanitize for T where T: SanitizeAuto + SanitizeCustom {}
86
87pub trait SanitizeAuto {
99 fn sanitize_self(&mut self, _ctx: &mut dyn VisitorContext) {}
100}
101
102impl<T: SanitizeAuto> SanitizeAuto for Option<T> {}
103
104impl<T: SanitizeAuto> SanitizeAuto for Vec<T> {}
105
106impl<T: SanitizeAuto + ?Sized> SanitizeAuto for Box<T> {
107 fn sanitize_self(&mut self, ctx: &mut dyn VisitorContext) {
108 (**self).sanitize_self(ctx);
109 }
110}
111
112impl_primitive!(SanitizeAuto);
113
114pub trait SanitizeCustom {
122 fn sanitize_custom(&mut self, _ctx: &mut dyn VisitorContext) {}
123}
124
125impl<T: SanitizeCustom> SanitizeCustom for Option<T> {}
126
127impl<T: SanitizeCustom> SanitizeCustom for Vec<T> {}
128
129impl<T: SanitizeCustom + ?Sized> SanitizeCustom for Box<T> {
130 fn sanitize_custom(&mut self, ctx: &mut dyn VisitorContext) {
131 (**self).sanitize_custom(ctx);
132 }
133}
134
135impl_primitive!(SanitizeCustom);
136
137pub trait Validate: ValidateAuto + ValidateCustom {}
145
146impl<T> Validate for T where T: ValidateAuto + ValidateCustom {}
147
148pub trait ValidateAuto {
160 fn validate_self(&self, _ctx: &mut dyn VisitorContext) {}
161}
162
163impl<T: ValidateAuto> ValidateAuto for Option<T> {}
164
165impl<T: ValidateAuto> ValidateAuto for Vec<T> {}
166
167impl<T: ValidateAuto + ?Sized> ValidateAuto for Box<T> {
168 fn validate_self(&self, ctx: &mut dyn VisitorContext) {
169 (**self).validate_self(ctx);
170 }
171}
172
173impl_primitive!(ValidateAuto);
174
175pub trait ValidateCustom {
183 fn validate_custom(&self, _ctx: &mut dyn VisitorContext) {}
184}
185
186impl<T: ValidateCustom> ValidateCustom for Option<T> {}
187
188impl<T: ValidateCustom> ValidateCustom for Vec<T> {}
189
190impl<T: ValidateCustom + ?Sized> ValidateCustom for Box<T> {
191 fn validate_custom(&self, ctx: &mut dyn VisitorContext) {
192 (**self).validate_custom(ctx);
193 }
194}
195
196impl_primitive!(ValidateCustom);
197
198pub trait Sanitizer<T> {
200 fn sanitize(&self, value: &mut T) -> Result<(), String>;
201
202 fn sanitize_with_context(
203 &self,
204 value: &mut T,
205 ctx: &mut dyn VisitorContext,
206 ) -> Result<(), String> {
207 let _ = ctx;
208
209 self.sanitize(value)
210 }
211}
212
213pub trait Validator<T: ?Sized> {
215 fn validate(&self, value: &T, ctx: &mut dyn VisitorContext);
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use crate::{
222 sanitize::sanitize,
223 validate::validate,
224 visitor::{Issue, VisitorError},
225 };
226 use std::cell::Cell;
227
228 const AUTO_SANITIZE_ISSUE: &str = "automatic sanitize";
229 const CUSTOM_SANITIZE_ISSUE: &str = "custom sanitize";
230 const AUTO_VALIDATE_ISSUE: &str = "automatic validate";
231 const CUSTOM_VALIDATE_ISSUE: &str = "custom validate";
232
233 #[derive(Default)]
234 struct HookProbe {
235 auto_sanitize: u32,
236 custom_sanitize: u32,
237 auto_validate: Cell<u32>,
238 custom_validate: Cell<u32>,
239 }
240
241 impl Visitable for HookProbe {}
242
243 impl SanitizeAuto for HookProbe {
244 fn sanitize_self(&mut self, ctx: &mut dyn VisitorContext) {
245 self.auto_sanitize += 1;
246 ctx.issue(AUTO_SANITIZE_ISSUE);
247 }
248 }
249
250 impl SanitizeCustom for HookProbe {
251 fn sanitize_custom(&mut self, ctx: &mut dyn VisitorContext) {
252 self.custom_sanitize += 1;
253 ctx.issue(CUSTOM_SANITIZE_ISSUE);
254 }
255 }
256
257 impl ValidateAuto for HookProbe {
258 fn validate_self(&self, ctx: &mut dyn VisitorContext) {
259 self.auto_validate.set(self.auto_validate.get() + 1);
260 ctx.issue(AUTO_VALIDATE_ISSUE);
261 }
262 }
263
264 impl ValidateCustom for HookProbe {
265 fn validate_custom(&self, ctx: &mut dyn VisitorContext) {
266 self.custom_validate.set(self.custom_validate.get() + 1);
267 ctx.issue(CUSTOM_VALIDATE_ISSUE);
268 }
269 }
270
271 fn assert_issues(error: &VisitorError, path: &str, expected: [&str; 2]) {
272 let issues = error
273 .issues()
274 .get(path)
275 .unwrap_or_else(|| panic!("expected visitor issues at {path}"));
276 let messages = issues.iter().map(Issue::message).collect::<Vec<_>>();
277 assert_eq!(messages, expected);
278 }
279
280 #[test]
281 fn option_vec_sanitize_hooks_run_once_at_each_indexed_path() {
282 let mut value = Some(vec![HookProbe::default(), HookProbe::default()]);
283
284 let error = sanitize(&mut value).expect_err("probe sanitizers should report issues");
285
286 let Some(probes) = value.as_ref() else {
287 panic!("sanitize should preserve the populated option");
288 };
289 for probe in probes {
290 assert_eq!(probe.auto_sanitize, 1);
291 assert_eq!(probe.custom_sanitize, 1);
292 }
293 assert!(error.issues().get("").is_none());
294 assert_issues(&error, "[0]", [AUTO_SANITIZE_ISSUE, CUSTOM_SANITIZE_ISSUE]);
295 assert_issues(&error, "[1]", [AUTO_SANITIZE_ISSUE, CUSTOM_SANITIZE_ISSUE]);
296 }
297
298 #[test]
299 fn option_vec_validate_hooks_run_once_at_each_indexed_path() {
300 let value = Some(vec![HookProbe::default(), HookProbe::default()]);
301
302 let error = validate(&value).expect_err("probe validators should report issues");
303
304 let Some(probes) = value.as_ref() else {
305 panic!("validate should preserve the populated option");
306 };
307 for probe in probes {
308 assert_eq!(probe.auto_validate.get(), 1);
309 assert_eq!(probe.custom_validate.get(), 1);
310 }
311 assert!(error.issues().get("").is_none());
312 assert_issues(&error, "[0]", [AUTO_VALIDATE_ISSUE, CUSTOM_VALIDATE_ISSUE]);
313 assert_issues(&error, "[1]", [AUTO_VALIDATE_ISSUE, CUSTOM_VALIDATE_ISSUE]);
314 }
315
316 #[test]
317 fn box_transparency_keeps_one_forwarded_hook_call() {
318 let mut sanitized = Box::new(HookProbe::default());
319 let _ = sanitize(&mut sanitized).expect_err("probe sanitizers should report issues");
320 assert_eq!(sanitized.auto_sanitize, 1);
321 assert_eq!(sanitized.custom_sanitize, 1);
322
323 let validated = Box::new(HookProbe::default());
324 let _ = validate(&validated).expect_err("probe validators should report issues");
325 assert_eq!(validated.auto_validate.get(), 1);
326 assert_eq!(validated.custom_validate.get(), 1);
327 }
328}