Skip to main content

icydb_model/visitor/
traits.rs

1//! Module: visitor::traits
2//! Responsibility: visitable-node traits and default container traversal wiring.
3//! Does not own: concrete normalize/validate visitor implementations.
4//! Boundary: structural traversal contract implemented by domain types.
5
6use crate::visitor::{
7    PathSegment, VisitorContext, VisitorCore, VisitorMutCore, perform_visit, perform_visit_mut,
8};
9
10//
11// ============================================================================
12// Visitable
13// ============================================================================
14//
15
16/// A node that participates in visitor-based traversal.
17///
18/// Invariants:
19/// - Traversal is owned by the visitor, not by normalize/validate hooks.
20/// - `drive` / `drive_mut` describe *structure only*.
21/// - No validation or normalization logic lives here.
22pub trait Visitable: Normalize + Validate {
23    /// Return the concrete Rust application-value type used for callback
24    /// diagnostics.
25    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
33//
34// -------------------- Container forwarding --------------------
35//
36
37// `Option` and `Vec` describe child structure here; their normalize and
38// validate hooks remain node-local no-ops. `Box` is transparent instead, so
39// its hook forwarding supplies the boxed node's one logical hook call.
40
41impl<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
83// Primitive leaf nodes: no structure
84macro_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::U256,
118    crate::schema::Ulid,
119    crate::schema::Unit,
120);
121
122//
123// ============================================================================
124// Normalize
125// ============================================================================
126//
127
128/// Marker trait: a type supports normalization.
129pub trait Normalize: NormalizeAuto + NormalizeCustom {}
130
131impl<T> Normalize for T where T: NormalizeAuto + NormalizeCustom {}
132
133//
134// -------------------- NormalizeAuto --------------------
135//
136
137/// Schema-defined normalization for this node only.
138///
139/// Rules:
140/// - May mutate only `self`
141/// - Must NOT recurse
142/// - Must NOT fail-fast
143/// - Must report issues via `VisitorContext`
144pub trait NormalizeAuto {
145    fn normalize_self(&mut self, _ctx: &mut dyn VisitorContext) {}
146}
147
148impl<T: NormalizeAuto> NormalizeAuto for Option<T> {}
149
150impl<T: NormalizeAuto> NormalizeAuto for Vec<T> {}
151
152impl<T: NormalizeAuto + ?Sized> NormalizeAuto for Box<T> {
153    fn normalize_self(&mut self, ctx: &mut dyn VisitorContext) {
154        (**self).normalize_self(ctx);
155    }
156}
157
158impl_primitive!(NormalizeAuto);
159
160//
161// -------------------- NormalizeCustom --------------------
162//
163
164/// User-defined normalization hooks.
165///
166/// Same rules as `NormalizeAuto`.
167pub trait NormalizeCustom {
168    fn normalize_custom(&mut self, _ctx: &mut dyn VisitorContext) {}
169}
170
171impl<T: NormalizeCustom> NormalizeCustom for Option<T> {}
172
173impl<T: NormalizeCustom> NormalizeCustom for Vec<T> {}
174
175impl<T: NormalizeCustom + ?Sized> NormalizeCustom for Box<T> {
176    fn normalize_custom(&mut self, ctx: &mut dyn VisitorContext) {
177        (**self).normalize_custom(ctx);
178    }
179}
180
181impl_primitive!(NormalizeCustom);
182
183//
184// ============================================================================
185// Validate
186// ============================================================================
187//
188
189/// Marker trait: a type supports validation.
190pub trait Validate: ValidateAuto + ValidateCustom {}
191
192impl<T> Validate for T where T: ValidateAuto + ValidateCustom {}
193
194//
195// -------------------- ValidateAuto --------------------
196//
197
198/// Schema-defined validation for this node only.
199///
200/// Rules:
201/// - Must NOT recurse
202/// - Must NOT aggregate
203/// - Must NOT return errors
204/// - Must report issues via `VisitorContext`
205pub trait ValidateAuto {
206    fn validate_self(&self, _ctx: &mut dyn VisitorContext) {}
207}
208
209impl<T: ValidateAuto> ValidateAuto for Option<T> {}
210
211impl<T: ValidateAuto> ValidateAuto for Vec<T> {}
212
213impl<T: ValidateAuto + ?Sized> ValidateAuto for Box<T> {
214    fn validate_self(&self, ctx: &mut dyn VisitorContext) {
215        (**self).validate_self(ctx);
216    }
217}
218
219impl_primitive!(ValidateAuto);
220
221//
222// -------------------- ValidateCustom --------------------
223//
224
225/// User-defined validation hooks.
226///
227/// Same rules as `ValidateAuto`.
228pub trait ValidateCustom {
229    fn validate_custom(&self, _ctx: &mut dyn VisitorContext) {}
230}
231
232impl<T: ValidateCustom> ValidateCustom for Option<T> {}
233
234impl<T: ValidateCustom> ValidateCustom for Vec<T> {}
235
236impl<T: ValidateCustom + ?Sized> ValidateCustom for Box<T> {
237    fn validate_custom(&self, ctx: &mut dyn VisitorContext) {
238        (**self).validate_custom(ctx);
239    }
240}
241
242impl_primitive!(ValidateCustom);
243
244/// Transforms a value into a normalized version.
245pub trait Normalizer<T> {
246    fn normalize(&self, value: &mut T) -> Result<(), String>;
247
248    fn normalize_with_context(
249        &self,
250        value: &mut T,
251        ctx: &mut dyn VisitorContext,
252    ) -> Result<(), String> {
253        let _ = ctx;
254
255        self.normalize(value)
256    }
257}
258
259/// Allows a node to validate values.
260pub trait Validator<T: ?Sized> {
261    fn validate(&self, value: &T, ctx: &mut dyn VisitorContext);
262}
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267    use crate::{
268        normalize::normalize,
269        validate::validate,
270        visitor::{ApplicationOperation, CallbackKind, Issue, VisitorError},
271    };
272    use std::{
273        cell::{Cell, RefCell},
274        rc::Rc,
275    };
276
277    const AUTO_NORMALIZE_ISSUE: &str = "automatic normalize";
278    const CUSTOM_NORMALIZE_ISSUE: &str = "custom normalize";
279    const AUTO_VALIDATE_ISSUE: &str = "automatic validate";
280    const CUSTOM_VALIDATE_ISSUE: &str = "custom validate";
281
282    #[derive(Default)]
283    struct HookProbe {
284        auto_normalize: u32,
285        custom_normalize: u32,
286        auto_validate: Cell<u32>,
287        custom_validate: Cell<u32>,
288    }
289
290    struct OrderedLeaf {
291        events: Rc<RefCell<Vec<&'static str>>>,
292    }
293
294    impl Visitable for OrderedLeaf {}
295
296    impl NormalizeAuto for OrderedLeaf {
297        fn normalize_self(&mut self, _ctx: &mut dyn VisitorContext) {
298            self.events.borrow_mut().push("leaf normalize auto");
299        }
300    }
301
302    impl NormalizeCustom for OrderedLeaf {
303        fn normalize_custom(&mut self, _ctx: &mut dyn VisitorContext) {
304            self.events.borrow_mut().push("leaf normalize custom");
305        }
306    }
307
308    impl ValidateAuto for OrderedLeaf {
309        fn validate_self(&self, _ctx: &mut dyn VisitorContext) {
310            self.events.borrow_mut().push("leaf validate auto");
311        }
312    }
313
314    impl ValidateCustom for OrderedLeaf {
315        fn validate_custom(&self, _ctx: &mut dyn VisitorContext) {
316            self.events.borrow_mut().push("leaf validate custom");
317        }
318    }
319
320    struct OrderedParent {
321        events: Rc<RefCell<Vec<&'static str>>>,
322        child: OrderedLeaf,
323    }
324
325    impl Visitable for OrderedParent {
326        fn drive(&self, visitor: &mut dyn VisitorCore) {
327            perform_visit(visitor, &self.child, "child");
328        }
329
330        fn drive_mut(&mut self, visitor: &mut dyn VisitorMutCore) {
331            perform_visit_mut(visitor, &mut self.child, "child");
332        }
333    }
334
335    impl NormalizeAuto for OrderedParent {
336        fn normalize_self(&mut self, _ctx: &mut dyn VisitorContext) {
337            self.events.borrow_mut().push("parent normalize auto");
338        }
339    }
340
341    impl NormalizeCustom for OrderedParent {
342        fn normalize_custom(&mut self, _ctx: &mut dyn VisitorContext) {
343            self.events.borrow_mut().push("parent normalize custom");
344        }
345    }
346
347    impl ValidateAuto for OrderedParent {
348        fn validate_self(&self, _ctx: &mut dyn VisitorContext) {
349            self.events.borrow_mut().push("parent validate auto");
350        }
351    }
352
353    impl ValidateCustom for OrderedParent {
354        fn validate_custom(&self, _ctx: &mut dyn VisitorContext) {
355            self.events.borrow_mut().push("parent validate custom");
356        }
357    }
358
359    fn ordered_parent() -> (OrderedParent, Rc<RefCell<Vec<&'static str>>>) {
360        let events = Rc::new(RefCell::new(Vec::new()));
361        (
362            OrderedParent {
363                events: Rc::clone(&events),
364                child: OrderedLeaf {
365                    events: Rc::clone(&events),
366                },
367            },
368            events,
369        )
370    }
371
372    impl Visitable for HookProbe {}
373
374    impl NormalizeAuto for HookProbe {
375        fn normalize_self(&mut self, ctx: &mut dyn VisitorContext) {
376            self.auto_normalize += 1;
377            ctx.issue(AUTO_NORMALIZE_ISSUE);
378        }
379    }
380
381    impl NormalizeCustom for HookProbe {
382        fn normalize_custom(&mut self, ctx: &mut dyn VisitorContext) {
383            self.custom_normalize += 1;
384            ctx.issue(CUSTOM_NORMALIZE_ISSUE);
385        }
386    }
387
388    impl ValidateAuto for HookProbe {
389        fn validate_self(&self, ctx: &mut dyn VisitorContext) {
390            self.auto_validate.set(self.auto_validate.get() + 1);
391            ctx.issue(AUTO_VALIDATE_ISSUE);
392        }
393    }
394
395    impl ValidateCustom for HookProbe {
396        fn validate_custom(&self, ctx: &mut dyn VisitorContext) {
397            self.custom_validate.set(self.custom_validate.get() + 1);
398            ctx.issue(CUSTOM_VALIDATE_ISSUE);
399        }
400    }
401
402    fn assert_issues(error: &VisitorError, path: &str, expected: [&str; 2]) {
403        let issues = error
404            .issues()
405            .get(path)
406            .unwrap_or_else(|| panic!("expected visitor issues at {path}"));
407        let messages = issues.iter().map(Issue::message).collect::<Vec<_>>();
408        assert_eq!(messages, expected);
409    }
410
411    fn assert_callbacks(error: &VisitorError, path: &str, expected: [CallbackKind; 2]) {
412        let issues = error
413            .issues()
414            .get(path)
415            .unwrap_or_else(|| panic!("expected visitor issues at {path}"));
416        let callbacks = issues
417            .iter()
418            .map(|issue| {
419                issue
420                    .callback()
421                    .expect("top-level application traversal must type every callback")
422            })
423            .collect::<Vec<_>>();
424        assert_eq!(
425            callbacks
426                .iter()
427                .map(|callback| callback.kind())
428                .collect::<Vec<_>>(),
429            expected
430        );
431        assert!(
432            callbacks
433                .iter()
434                .all(|callback| callback.type_path() == std::any::type_name::<HookProbe>())
435        );
436    }
437
438    #[test]
439    fn option_vec_normalize_hooks_run_once_at_each_indexed_path() {
440        let mut value = Some(vec![HookProbe::default(), HookProbe::default()]);
441
442        let error = normalize(&mut value).expect_err("probe normalizers should report issues");
443        assert_eq!(error.operation(), ApplicationOperation::Normalize);
444
445        let Some(probes) = value.as_ref() else {
446            panic!("normalize should preserve the populated option");
447        };
448        for probe in probes {
449            assert_eq!(probe.auto_normalize, 1);
450            assert_eq!(probe.custom_normalize, 1);
451        }
452        assert!(error.issues().get("").is_none());
453        assert_issues(
454            &error,
455            "[0]",
456            [AUTO_NORMALIZE_ISSUE, CUSTOM_NORMALIZE_ISSUE],
457        );
458        assert_callbacks(
459            &error,
460            "[0]",
461            [CallbackKind::NormalizeAuto, CallbackKind::NormalizeCustom],
462        );
463        assert_issues(
464            &error,
465            "[1]",
466            [AUTO_NORMALIZE_ISSUE, CUSTOM_NORMALIZE_ISSUE],
467        );
468    }
469
470    #[test]
471    fn option_vec_validate_hooks_run_once_at_each_indexed_path() {
472        let value = Some(vec![HookProbe::default(), HookProbe::default()]);
473
474        let error = validate(&value).expect_err("probe validators should report issues");
475        assert_eq!(error.operation(), ApplicationOperation::Validate);
476
477        let Some(probes) = value.as_ref() else {
478            panic!("validate should preserve the populated option");
479        };
480        for probe in probes {
481            assert_eq!(probe.auto_validate.get(), 1);
482            assert_eq!(probe.custom_validate.get(), 1);
483        }
484        assert!(error.issues().get("").is_none());
485        assert_issues(&error, "[0]", [AUTO_VALIDATE_ISSUE, CUSTOM_VALIDATE_ISSUE]);
486        assert_callbacks(
487            &error,
488            "[0]",
489            [CallbackKind::ValidateAuto, CallbackKind::ValidateCustom],
490        );
491        assert_issues(&error, "[1]", [AUTO_VALIDATE_ISSUE, CUSTOM_VALIDATE_ISSUE]);
492    }
493
494    #[test]
495    fn box_transparency_keeps_one_forwarded_hook_call() {
496        let mut normalized = Box::new(HookProbe::default());
497        let normalize_error =
498            normalize(&mut normalized).expect_err("probe normalizers should report issues");
499        assert_eq!(normalized.auto_normalize, 1);
500        assert_eq!(normalized.custom_normalize, 1);
501        assert_callbacks(
502            &normalize_error,
503            "",
504            [CallbackKind::NormalizeAuto, CallbackKind::NormalizeCustom],
505        );
506
507        let validated = Box::new(HookProbe::default());
508        let validate_error =
509            validate(&validated).expect_err("probe validators should report issues");
510        assert_eq!(validated.auto_validate.get(), 1);
511        assert_eq!(validated.custom_validate.get(), 1);
512        assert_callbacks(
513            &validate_error,
514            "",
515            [CallbackKind::ValidateAuto, CallbackKind::ValidateCustom],
516        );
517    }
518
519    #[test]
520    fn normalize_and_validate_traversals_are_preorder_and_declaration_ordered() {
521        let (mut normalized, normalize_events) = ordered_parent();
522        normalize(&mut normalized).expect("ordered normalizers should succeed");
523        assert_eq!(
524            normalize_events.borrow().as_slice(),
525            [
526                "parent normalize auto",
527                "parent normalize custom",
528                "leaf normalize auto",
529                "leaf normalize custom",
530            ]
531        );
532
533        let (validated, validate_events) = ordered_parent();
534        validate(&validated).expect("ordered validators should succeed");
535        assert_eq!(
536            validate_events.borrow().as_slice(),
537            [
538                "parent validate auto",
539                "parent validate custom",
540                "leaf validate auto",
541                "leaf validate custom",
542            ]
543        );
544    }
545}