1use super::{
7 PathSegment, VisitorContext, VisitorCore, VisitorMutCore, perform_visit, perform_visit_mut,
8};
9
10pub trait Visitable: Sanitize + Validate {
23 #[doc(hidden)]
28 #[must_use]
29 fn requires_application_write_callbacks() -> bool
30 where
31 Self: Sized,
32 {
33 true
34 }
35
36 fn drive(&self, _: &mut dyn VisitorCore) {}
37 fn drive_mut(&mut self, _: &mut dyn VisitorMutCore) {}
38}
39
40impl<T: Visitable> Visitable for Option<T> {
49 fn requires_application_write_callbacks() -> bool {
50 T::requires_application_write_callbacks()
51 }
52
53 fn drive(&self, visitor: &mut dyn VisitorCore) {
54 if let Some(value) = self.as_ref() {
55 perform_visit(visitor, value, PathSegment::Empty);
56 }
57 }
58
59 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
60 if let Some(value) = self.as_mut() {
61 perform_visit_mut(visitor, value, PathSegment::Empty);
62 }
63 }
64}
65
66impl<T: Visitable> Visitable for Vec<T> {
67 fn requires_application_write_callbacks() -> bool {
68 T::requires_application_write_callbacks()
69 }
70
71 fn drive(&self, visitor: &mut dyn VisitorCore) {
72 for (i, value) in self.iter().enumerate() {
73 perform_visit(visitor, value, i);
74 }
75 }
76
77 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
78 for (i, value) in self.iter_mut().enumerate() {
79 perform_visit_mut(visitor, value, i);
80 }
81 }
82}
83
84impl<T: Visitable> Visitable for Box<T> {
85 fn requires_application_write_callbacks() -> bool {
86 T::requires_application_write_callbacks()
87 }
88
89 fn drive(&self, visitor: &mut dyn VisitorCore) {
90 (**self).drive(visitor);
91 }
92
93 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
94 (**self).drive_mut(visitor);
95 }
96}
97
98macro_rules! impl_primitive_visitable {
100 ($($ty:ty),* $(,)?) => {
101 $(impl Visitable for $ty {
102 fn requires_application_write_callbacks() -> bool {
103 false
104 }
105 })*
106 };
107}
108
109impl_primitive_visitable!(
110 i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64, bool, String,
111);
112
113pub trait Sanitize: SanitizeAuto + SanitizeCustom {}
121
122impl<T> Sanitize for T where T: SanitizeAuto + SanitizeCustom {}
123
124pub trait SanitizeAuto {
136 fn sanitize_self(&mut self, _ctx: &mut dyn VisitorContext) {}
137}
138
139impl<T: SanitizeAuto> SanitizeAuto for Option<T> {}
140
141impl<T: SanitizeAuto> SanitizeAuto for Vec<T> {}
142
143impl<T: SanitizeAuto + ?Sized> SanitizeAuto for Box<T> {
144 fn sanitize_self(&mut self, ctx: &mut dyn VisitorContext) {
145 (**self).sanitize_self(ctx);
146 }
147}
148
149impl_primitive!(SanitizeAuto);
150
151pub trait SanitizeCustom {
159 fn sanitize_custom(&mut self, _ctx: &mut dyn VisitorContext) {}
160}
161
162impl<T: SanitizeCustom> SanitizeCustom for Option<T> {}
163
164impl<T: SanitizeCustom> SanitizeCustom for Vec<T> {}
165
166impl<T: SanitizeCustom + ?Sized> SanitizeCustom for Box<T> {
167 fn sanitize_custom(&mut self, ctx: &mut dyn VisitorContext) {
168 (**self).sanitize_custom(ctx);
169 }
170}
171
172impl_primitive!(SanitizeCustom);
173
174pub trait Validate: ValidateAuto + ValidateCustom {}
182
183impl<T> Validate for T where T: ValidateAuto + ValidateCustom {}
184
185pub trait ValidateAuto {
197 fn validate_self(&self, _ctx: &mut dyn VisitorContext) {}
198}
199
200impl<T: ValidateAuto> ValidateAuto for Option<T> {}
201
202impl<T: ValidateAuto> ValidateAuto for Vec<T> {}
203
204impl<T: ValidateAuto + ?Sized> ValidateAuto for Box<T> {
205 fn validate_self(&self, ctx: &mut dyn VisitorContext) {
206 (**self).validate_self(ctx);
207 }
208}
209
210impl_primitive!(ValidateAuto);
211
212pub trait ValidateCustom {
220 fn validate_custom(&self, _ctx: &mut dyn VisitorContext) {}
221}
222
223impl<T: ValidateCustom> ValidateCustom for Option<T> {}
224
225impl<T: ValidateCustom> ValidateCustom for Vec<T> {}
226
227impl<T: ValidateCustom + ?Sized> ValidateCustom for Box<T> {
228 fn validate_custom(&self, ctx: &mut dyn VisitorContext) {
229 (**self).validate_custom(ctx);
230 }
231}
232
233impl_primitive!(ValidateCustom);
234
235pub trait Sanitizer<T> {
237 fn sanitize(&self, value: &mut T) -> Result<(), String>;
238
239 fn sanitize_with_context(
240 &self,
241 value: &mut T,
242 ctx: &mut dyn VisitorContext,
243 ) -> Result<(), String> {
244 let _ = ctx;
245
246 self.sanitize(value)
247 }
248}
249
250pub trait Validator<T: ?Sized> {
252 fn validate(&self, value: &T, ctx: &mut dyn VisitorContext);
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use crate::{
259 sanitize::sanitize,
260 validate::validate,
261 visitor::{Issue, VisitorError},
262 };
263 use std::cell::Cell;
264
265 const AUTO_SANITIZE_ISSUE: &str = "automatic sanitize";
266 const CUSTOM_SANITIZE_ISSUE: &str = "custom sanitize";
267 const AUTO_VALIDATE_ISSUE: &str = "automatic validate";
268 const CUSTOM_VALIDATE_ISSUE: &str = "custom validate";
269
270 #[derive(Default)]
271 struct HookProbe {
272 auto_sanitize: u32,
273 custom_sanitize: u32,
274 auto_validate: Cell<u32>,
275 custom_validate: Cell<u32>,
276 }
277
278 impl Visitable for HookProbe {}
279
280 impl SanitizeAuto for HookProbe {
281 fn sanitize_self(&mut self, ctx: &mut dyn VisitorContext) {
282 self.auto_sanitize += 1;
283 ctx.issue(AUTO_SANITIZE_ISSUE);
284 }
285 }
286
287 impl SanitizeCustom for HookProbe {
288 fn sanitize_custom(&mut self, ctx: &mut dyn VisitorContext) {
289 self.custom_sanitize += 1;
290 ctx.issue(CUSTOM_SANITIZE_ISSUE);
291 }
292 }
293
294 impl ValidateAuto for HookProbe {
295 fn validate_self(&self, ctx: &mut dyn VisitorContext) {
296 self.auto_validate.set(self.auto_validate.get() + 1);
297 ctx.issue(AUTO_VALIDATE_ISSUE);
298 }
299 }
300
301 impl ValidateCustom for HookProbe {
302 fn validate_custom(&self, ctx: &mut dyn VisitorContext) {
303 self.custom_validate.set(self.custom_validate.get() + 1);
304 ctx.issue(CUSTOM_VALIDATE_ISSUE);
305 }
306 }
307
308 fn assert_issues(error: &VisitorError, path: &str, expected: [&str; 2]) {
309 let issues = error
310 .issues()
311 .get(path)
312 .unwrap_or_else(|| panic!("expected visitor issues at {path}"));
313 let messages = issues.iter().map(Issue::message).collect::<Vec<_>>();
314 assert_eq!(messages, expected);
315 }
316
317 #[test]
318 fn option_vec_sanitize_hooks_run_once_at_each_indexed_path() {
319 let mut value = Some(vec![HookProbe::default(), HookProbe::default()]);
320
321 let error = sanitize(&mut value).expect_err("probe sanitizers should report issues");
322
323 let Some(probes) = value.as_ref() else {
324 panic!("sanitize should preserve the populated option");
325 };
326 for probe in probes {
327 assert_eq!(probe.auto_sanitize, 1);
328 assert_eq!(probe.custom_sanitize, 1);
329 }
330 assert!(error.issues().get("").is_none());
331 assert_issues(&error, "[0]", [AUTO_SANITIZE_ISSUE, CUSTOM_SANITIZE_ISSUE]);
332 assert_issues(&error, "[1]", [AUTO_SANITIZE_ISSUE, CUSTOM_SANITIZE_ISSUE]);
333 }
334
335 #[test]
336 fn option_vec_validate_hooks_run_once_at_each_indexed_path() {
337 let value = Some(vec![HookProbe::default(), HookProbe::default()]);
338
339 let error = validate(&value).expect_err("probe validators should report issues");
340
341 let Some(probes) = value.as_ref() else {
342 panic!("validate should preserve the populated option");
343 };
344 for probe in probes {
345 assert_eq!(probe.auto_validate.get(), 1);
346 assert_eq!(probe.custom_validate.get(), 1);
347 }
348 assert!(error.issues().get("").is_none());
349 assert_issues(&error, "[0]", [AUTO_VALIDATE_ISSUE, CUSTOM_VALIDATE_ISSUE]);
350 assert_issues(&error, "[1]", [AUTO_VALIDATE_ISSUE, CUSTOM_VALIDATE_ISSUE]);
351 }
352
353 #[test]
354 fn box_transparency_keeps_one_forwarded_hook_call() {
355 let mut sanitized = Box::new(HookProbe::default());
356 let _ = sanitize(&mut sanitized).expect_err("probe sanitizers should report issues");
357 assert_eq!(sanitized.auto_sanitize, 1);
358 assert_eq!(sanitized.custom_sanitize, 1);
359
360 let validated = Box::new(HookProbe::default());
361 let _ = validate(&validated).expect_err("probe validators should report issues");
362 assert_eq!(validated.auto_validate.get(), 1);
363 assert_eq!(validated.custom_validate.get(), 1);
364 }
365}