Skip to main content

icydb_schema/
visit.rs

1use crate::{node::VisitableNode, prelude::*};
2
3///
4/// Event
5///
6/// Enter/exit marker passed to schema visitors during recursive traversal.
7///
8
9#[derive(Debug)]
10pub enum Event {
11    Enter,
12    Exit,
13}
14
15///
16/// Visitor
17///
18/// Minimal visitor trait over schema nodes.
19/// Implementors may track path state, collect errors, or gather derived
20/// metadata while nodes drive traversal.
21///
22
23pub trait Visitor {
24    // Observe one node at one traversal phase.
25    fn visit<V: VisitableNode + ?Sized>(&mut self, _: &V, _: Event) {}
26
27    // Maintain the current visitor path as traversal enters and exits nodes.
28    fn push(&mut self, _: &str) {}
29    fn pop(&mut self) {}
30}
31
32///
33/// ValidateVisitor
34///
35/// Visitor that runs `ValidateNode::validate()` across the schema tree and
36/// merges errors under their computed traversal route.
37///
38
39#[derive(Debug, Default)]
40pub struct ValidateVisitor {
41    errors: ErrorTree,
42    path: Vec<String>,
43    node_count: usize,
44}
45
46impl ValidateVisitor {
47    /// Build one empty validating visitor.
48    #[must_use]
49    pub fn new() -> Self {
50        Self {
51            errors: ErrorTree::new(),
52            ..Default::default()
53        }
54    }
55
56    #[must_use]
57    pub const fn node_count(&self) -> usize {
58        self.node_count
59    }
60
61    #[must_use]
62    pub const fn errors(&self) -> &ErrorTree {
63        &self.errors
64    }
65
66    #[must_use]
67    pub fn into_errors(self) -> ErrorTree {
68        self.errors
69    }
70}
71
72impl ValidateVisitor {
73    // Build one dotted route string from the active visitor path stack.
74    fn current_route(&self) -> String {
75        let mut route = String::new();
76
77        for segment in self.path.iter().filter(|segment| !segment.is_empty()) {
78            if !route.is_empty() {
79                route.push('.');
80            }
81            route.push_str(segment);
82        }
83
84        route
85    }
86}
87
88impl Visitor for ValidateVisitor {
89    fn visit<T: VisitableNode + ?Sized>(&mut self, node: &T, event: Event) {
90        match event {
91            Event::Enter => {
92                self.node_count += 1;
93
94                match node.validate() {
95                    Ok(()) => {}
96                    Err(errs) => {
97                        if !errs.is_empty() {
98                            let route = self.current_route();
99
100                            if route.is_empty() {
101                                // At the current level, merge directly.
102                                self.errors.merge(errs);
103                            } else {
104                                // Add to a child entry under the computed route.
105                                self.errors.merge_for(route, errs);
106                            }
107                        }
108                    }
109                }
110            }
111            Event::Exit => {}
112        }
113    }
114
115    fn push(&mut self, s: &str) {
116        self.path.push(s.to_string());
117    }
118
119    fn pop(&mut self) {
120        self.path.pop();
121    }
122}