icydb_core/visitor/
validate.rs1use crate::{
2 Error, ThisError,
3 traits::Visitable,
4 visitor::{Event, PathSegment, Visitor, VisitorError, perform_visit},
5};
6use icydb_error::ErrorTree;
7use std::fmt::Write;
8
9#[derive(Debug, ThisError)]
14pub enum ValidateError {
15 #[error("validation failed: {0}")]
16 ValidationFailed(ErrorTree),
17}
18
19impl From<ValidateError> for Error {
20 fn from(err: ValidateError) -> Self {
21 VisitorError::from(err).into()
22 }
23}
24
25pub fn validate(node: &dyn Visitable) -> Result<(), ValidateError> {
27 let mut visitor = ValidateVisitor::new();
28 perform_visit(&mut visitor, node, PathSegment::Empty);
29
30 visitor
31 .errors
32 .result()
33 .map_err(ValidateError::ValidationFailed)?;
34
35 Ok(())
36}
37
38#[derive(Debug, Default)]
43pub struct ValidateVisitor {
44 pub errors: ErrorTree,
45 pub path: Vec<PathSegment>,
46}
47
48impl ValidateVisitor {
49 #[must_use]
50 pub fn new() -> Self {
51 Self::default()
52 }
53
54 #[inline]
55 fn current_route(&self) -> String {
56 let mut out = String::new();
57 let mut first = true;
58
59 for seg in &self.path {
60 match seg {
61 PathSegment::Field(s) if !s.is_empty() => {
62 if !first {
63 out.push('.');
64 }
65 out.push_str(s);
66 first = false;
67 }
68 PathSegment::Index(i) => {
69 let _ = write!(out, "[{i}]");
71 first = false;
72 }
73 _ => {}
74 }
75 }
76
77 out
78 }
79}
80
81impl Visitor for ValidateVisitor {
82 #[inline]
83 fn visit(&mut self, node: &dyn Visitable, event: Event) {
84 match event {
85 Event::Enter => {
86 let mut errs = ErrorTree::new();
87
88 if let Err(e) = node.validate_self() {
91 errs.merge(e);
92 }
93 if let Err(e) = node.validate_children() {
94 errs.merge(e);
95 }
96 if let Err(e) = node.validate_custom() {
97 errs.merge(e);
98 }
99
100 if !errs.is_empty() {
102 if self.path.is_empty() {
103 self.errors.merge(errs);
105 } else {
106 let route = self.current_route();
108 self.errors.children.entry(route).or_default().merge(errs);
109 }
110 }
111 }
112 Event::Exit => {}
113 }
114 }
115
116 #[inline]
117 fn push(&mut self, seg: PathSegment) {
118 if !matches!(seg, PathSegment::Empty) {
119 self.path.push(seg);
120 }
121 }
122
123 #[inline]
124 fn pop(&mut self) {
125 self.path.pop();
126 }
127}
128
129#[cfg(test)]
134mod tests {
135 use super::*;
136 use crate::{
137 traits::{SanitizeAuto, SanitizeCustom, ValidateAuto, ValidateCustom, Visitable},
138 visitor::{perform_visit, validate},
139 };
140
141 const ERR_MSG: &str = "leaf error";
142
143 #[derive(Clone, Debug, Default)]
145 struct Leaf(bool);
146
147 impl SanitizeAuto for Leaf {}
148 impl SanitizeCustom for Leaf {}
149 impl Visitable for Leaf {}
150 impl ValidateAuto for Leaf {}
151 impl ValidateCustom for Leaf {
152 fn validate_custom(&self) -> Result<(), ErrorTree> {
153 if self.0 {
154 Err(ErrorTree::from(ERR_MSG))
155 } else {
156 Ok(())
157 }
158 }
159 }
160
161 fn flatten_errs(node: &dyn Visitable) -> Vec<(String, String)> {
163 match validate(node) {
164 Ok(()) => Vec::new(),
165 Err(crate::visitor::ValidateError::ValidationFailed(tree)) => tree.flatten_ref(),
166 }
167 }
168
169 #[test]
171 fn root_level_error_is_at_root() {
172 let leaf = Leaf(true);
173 let flat = flatten_errs(&leaf);
174
175 assert!(flat.iter().any(|(k, v)| k.is_empty() && v == ERR_MSG));
176 }
177
178 #[test]
180 fn record_field_vec_item_path_is_indexed() {
181 #[derive(Debug, Default)]
182 struct Container {
183 nums: Vec<Leaf>,
184 }
185
186 impl SanitizeAuto for Container {}
187 impl SanitizeCustom for Container {}
188 impl ValidateAuto for Container {}
189 impl ValidateCustom for Container {}
190 impl Visitable for Container {
191 fn drive(&self, visitor: &mut dyn Visitor) {
192 perform_visit(visitor, &self.nums, "nums");
194 }
195 }
196
197 let node = Container {
198 nums: vec![Leaf(false), Leaf(true), Leaf(false)],
199 };
200
201 let flat = flatten_errs(&node);
202 assert!(flat.iter().any(|(k, v)| k == "nums[1]" && v == ERR_MSG));
203 }
204
205 #[test]
207 fn nested_record_tuple_map_paths_are_dotted() {
208 #[derive(Debug, Default)]
210 struct Inner {
211 leaf: Leaf,
212 }
213
214 impl SanitizeAuto for Inner {}
215 impl SanitizeCustom for Inner {}
216 impl ValidateAuto for Inner {}
217 impl ValidateCustom for Inner {}
218 impl Visitable for Inner {
219 fn drive(&self, visitor: &mut dyn Visitor) {
220 perform_visit(visitor, &self.leaf, "leaf");
221 }
222 }
223
224 #[derive(Debug, Default)]
226 struct Tup2(Leaf, Leaf);
227
228 impl SanitizeAuto for Tup2 {}
229 impl SanitizeCustom for Tup2 {}
230 impl ValidateAuto for Tup2 {}
231 impl ValidateCustom for Tup2 {}
232 impl Visitable for Tup2 {
233 fn drive(&self, visitor: &mut dyn Visitor) {
234 perform_visit(visitor, &self.0, 0);
235 perform_visit(visitor, &self.1, 1);
236 }
237 }
238
239 #[derive(Debug, Default)]
241 struct MyMap(Vec<(String, Leaf)>);
242
243 impl SanitizeAuto for MyMap {}
244 impl SanitizeCustom for MyMap {}
245 impl ValidateAuto for MyMap {}
246 impl ValidateCustom for MyMap {}
247 impl Visitable for MyMap {
248 fn drive(&self, visitor: &mut dyn Visitor) {
249 for (_k, v) in &self.0 {
250 perform_visit(visitor, v, "value");
252 }
253 }
254 }
255
256 #[derive(Debug, Default)]
257 struct Outer {
258 rec: Inner,
259 tup: Tup2,
260 map: MyMap,
261 }
262
263 impl SanitizeAuto for Outer {}
264 impl SanitizeCustom for Outer {}
265 impl ValidateAuto for Outer {}
266 impl ValidateCustom for Outer {}
267 impl Visitable for Outer {
268 fn drive(&self, visitor: &mut dyn Visitor) {
269 perform_visit(visitor, &self.rec, "rec");
270 perform_visit(visitor, &self.tup, "tup");
271 perform_visit(visitor, &self.map, "map");
272 }
273 }
274
275 let node = Outer {
276 rec: Inner { leaf: Leaf(true) },
277 tup: Tup2(Leaf(false), Leaf(true)),
278 map: MyMap(vec![("k".to_string(), Leaf(true))]),
279 };
280
281 let flat = flatten_errs(&node);
282
283 assert!(flat.iter().any(|(k, v)| k == "rec.leaf" && v == ERR_MSG));
285 assert!(flat.iter().any(|(k, v)| k == "tup[1]" && v == ERR_MSG));
286 assert!(flat.iter().any(|(k, v)| k == "map.value" && v == ERR_MSG));
287 }
288}