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::Principal,
113    crate::schema::Subaccount,
114    crate::schema::Timestamp,
115    crate::schema::U256,
116    crate::schema::Ulid,
117    crate::schema::Unit,
118);
119
120//
121// ============================================================================
122// Normalize
123// ============================================================================
124//
125
126/// Marker trait: a type supports normalization.
127pub trait Normalize: NormalizeAuto + NormalizeCustom {}
128
129impl<T> Normalize for T where T: NormalizeAuto + NormalizeCustom {}
130
131//
132// -------------------- NormalizeAuto --------------------
133//
134
135/// Schema-defined normalization for this node only.
136///
137/// Rules:
138/// - May mutate only `self`
139/// - Must NOT recurse
140/// - Must NOT fail-fast
141/// - Must report issues via `VisitorContext`
142pub 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
158//
159// -------------------- NormalizeCustom --------------------
160//
161
162/// User-defined normalization hooks.
163///
164/// Same rules as `NormalizeAuto`.
165pub 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
181//
182// ============================================================================
183// Validate
184// ============================================================================
185//
186
187/// Marker trait: a type supports validation.
188pub trait Validate: ValidateAuto + ValidateCustom {}
189
190impl<T> Validate for T where T: ValidateAuto + ValidateCustom {}
191
192//
193// -------------------- ValidateAuto --------------------
194//
195
196/// Schema-defined validation for this node only.
197///
198/// Rules:
199/// - Must NOT recurse
200/// - Must NOT aggregate
201/// - Must NOT return errors
202/// - Must report issues via `VisitorContext`
203pub 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
219//
220// -------------------- ValidateCustom --------------------
221//
222
223/// User-defined validation hooks.
224///
225/// Same rules as `ValidateAuto`.
226pub 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
242/// Transforms a value into a normalized version.
243pub 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
257/// Allows a node to validate values.
258pub 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}