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