1use crate::visitor::{
7 PathSegment, VisitorContext, VisitorCore, VisitorMutCore, perform_visit, perform_visit_mut,
8};
9
10pub trait Visitable: Normalize + Validate {
23 fn type_identity(&self) -> &'static str {
26 std::any::type_name::<Self>()
27 }
28
29 fn drive(&self, _: &mut dyn VisitorCore) {}
30 fn drive_mut(&mut self, _: &mut dyn VisitorMutCore) {}
31}
32
33impl<T: Visitable> Visitable for Option<T> {
42 fn drive(&self, visitor: &mut dyn VisitorCore) {
43 if let Some(value) = self.as_ref() {
44 perform_visit(visitor, value, PathSegment::Empty);
45 }
46 }
47
48 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
49 if let Some(value) = self.as_mut() {
50 perform_visit_mut(visitor, value, PathSegment::Empty);
51 }
52 }
53}
54
55impl<T: Visitable> Visitable for Vec<T> {
56 fn drive(&self, visitor: &mut dyn VisitorCore) {
57 for (i, value) in self.iter().enumerate() {
58 perform_visit(visitor, value, i);
59 }
60 }
61
62 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
63 for (i, value) in self.iter_mut().enumerate() {
64 perform_visit_mut(visitor, value, i);
65 }
66 }
67}
68
69impl<T: Visitable> Visitable for Box<T> {
70 fn type_identity(&self) -> &'static str {
71 (**self).type_identity()
72 }
73
74 fn drive(&self, visitor: &mut dyn VisitorCore) {
75 (**self).drive(visitor);
76 }
77
78 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
79 (**self).drive_mut(visitor);
80 }
81}
82
83macro_rules! impl_primitive_visitable {
85 ($($ty:ty),* $(,)?) => {
86 $(impl Visitable for $ty {})*
87 };
88}
89
90impl_primitive_visitable!(
91 i8,
92 i16,
93 i32,
94 i64,
95 i128,
96 u8,
97 u16,
98 u32,
99 u64,
100 u128,
101 f32,
102 f64,
103 bool,
104 String,
105 crate::schema::Account,
106 crate::schema::Blob,
107 crate::schema::Date,
108 crate::schema::Decimal,
109 crate::schema::Duration,
110 crate::schema::Float32,
111 crate::schema::Float64,
112 crate::schema::IntBig,
113 crate::schema::NatBig,
114 crate::schema::Principal,
115 crate::schema::Subaccount,
116 crate::schema::Timestamp,
117 crate::schema::Ulid,
118 crate::schema::Unit,
119);
120
121pub trait Normalize: NormalizeAuto + NormalizeCustom {}
129
130impl<T> Normalize for T where T: NormalizeAuto + NormalizeCustom {}
131
132pub trait NormalizeAuto {
144 fn normalize_self(&mut self, _ctx: &mut dyn VisitorContext) {}
145}
146
147impl<T: NormalizeAuto> NormalizeAuto for Option<T> {}
148
149impl<T: NormalizeAuto> NormalizeAuto for Vec<T> {}
150
151impl<T: NormalizeAuto + ?Sized> NormalizeAuto for Box<T> {
152 fn normalize_self(&mut self, ctx: &mut dyn VisitorContext) {
153 (**self).normalize_self(ctx);
154 }
155}
156
157impl_primitive!(NormalizeAuto);
158
159pub trait NormalizeCustom {
167 fn normalize_custom(&mut self, _ctx: &mut dyn VisitorContext) {}
168}
169
170impl<T: NormalizeCustom> NormalizeCustom for Option<T> {}
171
172impl<T: NormalizeCustom> NormalizeCustom for Vec<T> {}
173
174impl<T: NormalizeCustom + ?Sized> NormalizeCustom for Box<T> {
175 fn normalize_custom(&mut self, ctx: &mut dyn VisitorContext) {
176 (**self).normalize_custom(ctx);
177 }
178}
179
180impl_primitive!(NormalizeCustom);
181
182pub trait Validate: ValidateAuto + ValidateCustom {}
190
191impl<T> Validate for T where T: ValidateAuto + ValidateCustom {}
192
193pub trait ValidateAuto {
205 fn validate_self(&self, _ctx: &mut dyn VisitorContext) {}
206}
207
208impl<T: ValidateAuto> ValidateAuto for Option<T> {}
209
210impl<T: ValidateAuto> ValidateAuto for Vec<T> {}
211
212impl<T: ValidateAuto + ?Sized> ValidateAuto for Box<T> {
213 fn validate_self(&self, ctx: &mut dyn VisitorContext) {
214 (**self).validate_self(ctx);
215 }
216}
217
218impl_primitive!(ValidateAuto);
219
220pub trait ValidateCustom {
228 fn validate_custom(&self, _ctx: &mut dyn VisitorContext) {}
229}
230
231impl<T: ValidateCustom> ValidateCustom for Option<T> {}
232
233impl<T: ValidateCustom> ValidateCustom for Vec<T> {}
234
235impl<T: ValidateCustom + ?Sized> ValidateCustom for Box<T> {
236 fn validate_custom(&self, ctx: &mut dyn VisitorContext) {
237 (**self).validate_custom(ctx);
238 }
239}
240
241impl_primitive!(ValidateCustom);
242
243pub trait Normalizer<T> {
245 fn normalize(&self, value: &mut T) -> Result<(), String>;
246
247 fn normalize_with_context(
248 &self,
249 value: &mut T,
250 ctx: &mut dyn VisitorContext,
251 ) -> Result<(), String> {
252 let _ = ctx;
253
254 self.normalize(value)
255 }
256}
257
258pub trait Validator<T: ?Sized> {
260 fn validate(&self, value: &T, ctx: &mut dyn VisitorContext);
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use crate::{
267 normalize::normalize,
268 validate::validate,
269 visitor::{ApplicationOperation, CallbackKind, Issue, VisitorError},
270 };
271 use std::{
272 cell::{Cell, RefCell},
273 rc::Rc,
274 };
275
276 const AUTO_NORMALIZE_ISSUE: &str = "automatic normalize";
277 const CUSTOM_NORMALIZE_ISSUE: &str = "custom normalize";
278 const AUTO_VALIDATE_ISSUE: &str = "automatic validate";
279 const CUSTOM_VALIDATE_ISSUE: &str = "custom validate";
280
281 #[derive(Default)]
282 struct HookProbe {
283 auto_normalize: u32,
284 custom_normalize: u32,
285 auto_validate: Cell<u32>,
286 custom_validate: Cell<u32>,
287 }
288
289 struct OrderedLeaf {
290 events: Rc<RefCell<Vec<&'static str>>>,
291 }
292
293 impl Visitable for OrderedLeaf {}
294
295 impl NormalizeAuto for OrderedLeaf {
296 fn normalize_self(&mut self, _ctx: &mut dyn VisitorContext) {
297 self.events.borrow_mut().push("leaf normalize auto");
298 }
299 }
300
301 impl NormalizeCustom for OrderedLeaf {
302 fn normalize_custom(&mut self, _ctx: &mut dyn VisitorContext) {
303 self.events.borrow_mut().push("leaf normalize custom");
304 }
305 }
306
307 impl ValidateAuto for OrderedLeaf {
308 fn validate_self(&self, _ctx: &mut dyn VisitorContext) {
309 self.events.borrow_mut().push("leaf validate auto");
310 }
311 }
312
313 impl ValidateCustom for OrderedLeaf {
314 fn validate_custom(&self, _ctx: &mut dyn VisitorContext) {
315 self.events.borrow_mut().push("leaf validate custom");
316 }
317 }
318
319 struct OrderedParent {
320 events: Rc<RefCell<Vec<&'static str>>>,
321 child: OrderedLeaf,
322 }
323
324 impl Visitable for OrderedParent {
325 fn drive(&self, visitor: &mut dyn VisitorCore) {
326 perform_visit(visitor, &self.child, "child");
327 }
328
329 fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
330 perform_visit_mut(visitor, &mut self.child, "child");
331 }
332 }
333
334 impl NormalizeAuto for OrderedParent {
335 fn normalize_self(&mut self, _ctx: &mut dyn VisitorContext) {
336 self.events.borrow_mut().push("parent normalize auto");
337 }
338 }
339
340 impl NormalizeCustom for OrderedParent {
341 fn normalize_custom(&mut self, _ctx: &mut dyn VisitorContext) {
342 self.events.borrow_mut().push("parent normalize custom");
343 }
344 }
345
346 impl ValidateAuto for OrderedParent {
347 fn validate_self(&self, _ctx: &mut dyn VisitorContext) {
348 self.events.borrow_mut().push("parent validate auto");
349 }
350 }
351
352 impl ValidateCustom for OrderedParent {
353 fn validate_custom(&self, _ctx: &mut dyn VisitorContext) {
354 self.events.borrow_mut().push("parent validate custom");
355 }
356 }
357
358 fn ordered_parent() -> (OrderedParent, Rc<RefCell<Vec<&'static str>>>) {
359 let events = Rc::new(RefCell::new(Vec::new()));
360 (
361 OrderedParent {
362 events: Rc::clone(&events),
363 child: OrderedLeaf {
364 events: Rc::clone(&events),
365 },
366 },
367 events,
368 )
369 }
370
371 impl Visitable for HookProbe {}
372
373 impl NormalizeAuto for HookProbe {
374 fn normalize_self(&mut self, ctx: &mut dyn VisitorContext) {
375 self.auto_normalize += 1;
376 ctx.issue(AUTO_NORMALIZE_ISSUE);
377 }
378 }
379
380 impl NormalizeCustom for HookProbe {
381 fn normalize_custom(&mut self, ctx: &mut dyn VisitorContext) {
382 self.custom_normalize += 1;
383 ctx.issue(CUSTOM_NORMALIZE_ISSUE);
384 }
385 }
386
387 impl ValidateAuto for HookProbe {
388 fn validate_self(&self, ctx: &mut dyn VisitorContext) {
389 self.auto_validate.set(self.auto_validate.get() + 1);
390 ctx.issue(AUTO_VALIDATE_ISSUE);
391 }
392 }
393
394 impl ValidateCustom for HookProbe {
395 fn validate_custom(&self, ctx: &mut dyn VisitorContext) {
396 self.custom_validate.set(self.custom_validate.get() + 1);
397 ctx.issue(CUSTOM_VALIDATE_ISSUE);
398 }
399 }
400
401 fn assert_issues(error: &VisitorError, path: &str, expected: [&str; 2]) {
402 let issues = error
403 .issues()
404 .get(path)
405 .unwrap_or_else(|| panic!("expected visitor issues at {path}"));
406 let messages = issues.iter().map(Issue::message).collect::<Vec<_>>();
407 assert_eq!(messages, expected);
408 }
409
410 fn assert_callbacks(error: &VisitorError, path: &str, expected: [CallbackKind; 2]) {
411 let issues = error
412 .issues()
413 .get(path)
414 .unwrap_or_else(|| panic!("expected visitor issues at {path}"));
415 let callbacks = issues
416 .iter()
417 .map(|issue| {
418 issue
419 .callback()
420 .expect("top-level application traversal must type every callback")
421 })
422 .collect::<Vec<_>>();
423 assert_eq!(
424 callbacks
425 .iter()
426 .map(|callback| callback.kind())
427 .collect::<Vec<_>>(),
428 expected
429 );
430 assert!(
431 callbacks
432 .iter()
433 .all(|callback| callback.type_path() == std::any::type_name::<HookProbe>())
434 );
435 }
436
437 #[test]
438 fn option_vec_normalize_hooks_run_once_at_each_indexed_path() {
439 let mut value = Some(vec![HookProbe::default(), HookProbe::default()]);
440
441 let error = normalize(&mut value).expect_err("probe normalizers should report issues");
442 assert_eq!(error.operation(), ApplicationOperation::Normalize);
443
444 let Some(probes) = value.as_ref() else {
445 panic!("normalize should preserve the populated option");
446 };
447 for probe in probes {
448 assert_eq!(probe.auto_normalize, 1);
449 assert_eq!(probe.custom_normalize, 1);
450 }
451 assert!(error.issues().get("").is_none());
452 assert_issues(
453 &error,
454 "[0]",
455 [AUTO_NORMALIZE_ISSUE, CUSTOM_NORMALIZE_ISSUE],
456 );
457 assert_callbacks(
458 &error,
459 "[0]",
460 [CallbackKind::NormalizeAuto, CallbackKind::NormalizeCustom],
461 );
462 assert_issues(
463 &error,
464 "[1]",
465 [AUTO_NORMALIZE_ISSUE, CUSTOM_NORMALIZE_ISSUE],
466 );
467 }
468
469 #[test]
470 fn option_vec_validate_hooks_run_once_at_each_indexed_path() {
471 let value = Some(vec![HookProbe::default(), HookProbe::default()]);
472
473 let error = validate(&value).expect_err("probe validators should report issues");
474 assert_eq!(error.operation(), ApplicationOperation::Validate);
475
476 let Some(probes) = value.as_ref() else {
477 panic!("validate should preserve the populated option");
478 };
479 for probe in probes {
480 assert_eq!(probe.auto_validate.get(), 1);
481 assert_eq!(probe.custom_validate.get(), 1);
482 }
483 assert!(error.issues().get("").is_none());
484 assert_issues(&error, "[0]", [AUTO_VALIDATE_ISSUE, CUSTOM_VALIDATE_ISSUE]);
485 assert_callbacks(
486 &error,
487 "[0]",
488 [CallbackKind::ValidateAuto, CallbackKind::ValidateCustom],
489 );
490 assert_issues(&error, "[1]", [AUTO_VALIDATE_ISSUE, CUSTOM_VALIDATE_ISSUE]);
491 }
492
493 #[test]
494 fn box_transparency_keeps_one_forwarded_hook_call() {
495 let mut normalized = Box::new(HookProbe::default());
496 let normalize_error =
497 normalize(&mut normalized).expect_err("probe normalizers should report issues");
498 assert_eq!(normalized.auto_normalize, 1);
499 assert_eq!(normalized.custom_normalize, 1);
500 assert_callbacks(
501 &normalize_error,
502 "",
503 [CallbackKind::NormalizeAuto, CallbackKind::NormalizeCustom],
504 );
505
506 let validated = Box::new(HookProbe::default());
507 let validate_error =
508 validate(&validated).expect_err("probe validators should report issues");
509 assert_eq!(validated.auto_validate.get(), 1);
510 assert_eq!(validated.custom_validate.get(), 1);
511 assert_callbacks(
512 &validate_error,
513 "",
514 [CallbackKind::ValidateAuto, CallbackKind::ValidateCustom],
515 );
516 }
517
518 #[test]
519 fn normalize_and_validate_traversals_are_preorder_and_declaration_ordered() {
520 let (mut normalized, normalize_events) = ordered_parent();
521 normalize(&mut normalized).expect("ordered normalizers should succeed");
522 assert_eq!(
523 normalize_events.borrow().as_slice(),
524 [
525 "parent normalize auto",
526 "parent normalize custom",
527 "leaf normalize auto",
528 "leaf normalize custom",
529 ]
530 );
531
532 let (validated, validate_events) = ordered_parent();
533 validate(&validated).expect("ordered validators should succeed");
534 assert_eq!(
535 validate_events.borrow().as_slice(),
536 [
537 "parent validate auto",
538 "parent validate custom",
539 "leaf validate auto",
540 "leaf validate custom",
541 ]
542 );
543 }
544}