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