Skip to main content

icydb_schema/
visit.rs

1use crate::{node::VisitableNode, prelude::*};
2
3///
4/// Event
5/// fine to redeclare this every time we use the visitor pattern
6/// makes no sense to rely on such a simple dependency
7///
8
9#[derive(Debug)]
10pub enum Event {
11    Enter,
12    Exit,
13}
14
15///
16/// Visitor
17///
18
19pub trait Visitor {
20    // visit
21    fn visit<V: VisitableNode + ?Sized>(&mut self, _: &V, _: Event) {}
22
23    // key
24    fn push(&mut self, _: &str) {}
25    fn pop(&mut self) {}
26}
27
28///
29/// ValidateVisitor
30///
31
32#[derive(Debug, Default)]
33pub struct ValidateVisitor {
34    errors: ErrorTree,
35    path: Vec<String>,
36    node_count: usize,
37}
38
39impl ValidateVisitor {
40    #[must_use]
41    pub fn new() -> Self {
42        Self {
43            errors: ErrorTree::new(),
44            ..Default::default()
45        }
46    }
47
48    #[must_use]
49    pub const fn node_count(&self) -> usize {
50        self.node_count
51    }
52
53    #[must_use]
54    pub const fn errors(&self) -> &ErrorTree {
55        &self.errors
56    }
57
58    #[must_use]
59    pub fn into_errors(self) -> ErrorTree {
60        self.errors
61    }
62}
63
64impl ValidateVisitor {
65    fn current_route(&self) -> String {
66        let mut route = String::new();
67
68        for segment in self.path.iter().filter(|segment| !segment.is_empty()) {
69            if !route.is_empty() {
70                route.push('.');
71            }
72            route.push_str(segment);
73        }
74
75        route
76    }
77}
78
79impl Visitor for ValidateVisitor {
80    fn visit<T: VisitableNode + ?Sized>(&mut self, node: &T, event: Event) {
81        match event {
82            Event::Enter => {
83                self.node_count += 1;
84
85                match node.validate() {
86                    Ok(()) => {}
87                    Err(errs) => {
88                        if !errs.is_empty() {
89                            let route = self.current_route();
90
91                            if route.is_empty() {
92                                // At the current level, merge directly.
93                                self.errors.merge(errs);
94                            } else {
95                                // Add to a child entry under the computed route.
96                                self.errors.merge_for(route, errs);
97                            }
98                        }
99                    }
100                }
101            }
102            Event::Exit => {}
103        }
104    }
105
106    fn push(&mut self, s: &str) {
107        self.path.push(s.to_string());
108    }
109
110    fn pop(&mut self) {
111        self.path.pop();
112    }
113}