1#[macro_use]
8mod macros;
9mod traits;
10
11pub(crate) mod context;
12pub(crate) mod normalize;
13pub(crate) mod validate;
14
15use serde::Deserialize;
16use std::{collections::BTreeMap, fmt};
17use thiserror::Error as ThisError;
18
19pub use context::{Issue, PathSegment, ScopedContext, VisitorContext};
21pub use traits::{
22 Normalize, NormalizeAuto, NormalizeCustom, Normalizer, Validate, ValidateAuto, ValidateCustom,
23 Validator, Visitable,
24};
25
26#[derive(Debug, ThisError)]
32#[error("{issues}")]
33pub struct VisitorError {
34 issues: VisitorIssues,
35}
36
37impl VisitorError {
38 #[must_use]
39 pub const fn issues(&self) -> &VisitorIssues {
40 &self.issues
41 }
42}
43
44impl From<VisitorIssues> for VisitorError {
45 fn from(issues: VisitorIssues) -> Self {
46 Self { issues }
47 }
48}
49
50impl From<VisitorError> for VisitorIssues {
51 fn from(err: VisitorError) -> Self {
52 err.issues
53 }
54}
55
56#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
66pub struct VisitorIssues(BTreeMap<String, Vec<Issue>>);
67
68impl VisitorIssues {
69 #[must_use]
70 pub const fn new() -> Self {
71 Self(BTreeMap::new())
72 }
73
74 #[must_use]
75 pub fn is_empty(&self) -> bool {
76 self.0.is_empty()
77 }
78
79 #[must_use]
81 pub fn len(&self) -> usize {
82 self.0.len()
83 }
84
85 #[must_use]
86 pub fn get(&self, path: impl AsRef<str>) -> Option<&[Issue]> {
87 self.0.get(path.as_ref()).map(Vec::as_slice)
88 }
89
90 pub fn push(&mut self, path: String, issue: Issue) {
91 self.0.entry(path).or_default().push(issue);
92 }
93}
94
95impl From<BTreeMap<String, Vec<Issue>>> for VisitorIssues {
96 fn from(map: BTreeMap<String, Vec<Issue>>) -> Self {
97 Self(map)
98 }
99}
100
101impl fmt::Display for VisitorIssues {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 let mut wrote = false;
104
105 for (path, messages) in &self.0 {
106 for message in messages {
107 if wrote {
108 writeln!(f)?;
109 }
110
111 if path.is_empty() {
112 write!(f, "{message}")?;
113 } else {
114 write!(f, "{path}: {message}")?;
115 }
116
117 wrote = true;
118 }
119 }
120
121 if !wrote {
122 write!(f, "no visitor issues")?;
123 }
124
125 Ok(())
126 }
127}
128
129impl std::error::Error for VisitorIssues {}
130
131pub(crate) trait Visitor {
137 fn enter(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
138 fn exit(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
139}
140
141pub trait VisitorCore {
147 fn enter(&mut self, node: &dyn Visitable);
148 fn exit(&mut self, node: &dyn Visitable);
149
150 fn push(&mut self, _: PathSegment) {}
151 fn pop(&mut self) {}
152}
153
154pub struct VisitableFieldDescriptor<T> {
164 name: &'static str,
165 drive: fn(&T, &mut dyn VisitorCore),
166 drive_mut: fn(&mut T, &mut dyn VisitorMutCore),
167}
168
169impl<T> VisitableFieldDescriptor<T> {
170 #[must_use]
172 pub const fn new(
173 name: &'static str,
174 drive: fn(&T, &mut dyn VisitorCore),
175 drive_mut: fn(&mut T, &mut dyn VisitorMutCore),
176 ) -> Self {
177 Self {
178 name,
179 drive,
180 drive_mut,
181 }
182 }
183
184 #[must_use]
186 pub const fn name(&self) -> &'static str {
187 self.name
188 }
189}
190
191pub fn drive_visitable_fields<T>(
193 visitor: &mut dyn VisitorCore,
194 node: &T,
195 fields: &[VisitableFieldDescriptor<T>],
196) {
197 for field in fields {
198 (field.drive)(node, visitor);
199 }
200}
201
202pub fn drive_visitable_fields_mut<T>(
204 visitor: &mut dyn VisitorMutCore,
205 node: &mut T,
206 fields: &[VisitableFieldDescriptor<T>],
207) {
208 for field in fields {
209 (field.drive_mut)(node, visitor);
210 }
211}
212
213pub struct NormalizeFieldDescriptor<T> {
223 normalize: fn(&mut T, &mut dyn VisitorContext),
224}
225
226impl<T> NormalizeFieldDescriptor<T> {
227 #[must_use]
229 pub const fn new(normalize: fn(&mut T, &mut dyn VisitorContext)) -> Self {
230 Self { normalize }
231 }
232}
233
234pub fn drive_normalize_fields<T>(
236 node: &mut T,
237 ctx: &mut dyn VisitorContext,
238 fields: &[NormalizeFieldDescriptor<T>],
239) {
240 for field in fields {
241 (field.normalize)(node, ctx);
242 }
243}
244
245pub struct ValidateFieldDescriptor<T> {
255 validate: fn(&T, &mut dyn VisitorContext),
256}
257
258impl<T> ValidateFieldDescriptor<T> {
259 #[must_use]
261 pub const fn new(validate: fn(&T, &mut dyn VisitorContext)) -> Self {
262 Self { validate }
263 }
264}
265
266pub fn drive_validate_fields<T>(
268 node: &T,
269 ctx: &mut dyn VisitorContext,
270 fields: &[ValidateFieldDescriptor<T>],
271) {
272 for field in fields {
273 (field.validate)(node, ctx);
274 }
275}
276
277struct AdapterContext<'a> {
282 path: &'a [PathSegment],
283 issues: &'a mut VisitorIssues,
284}
285
286impl VisitorContext for AdapterContext<'_> {
287 fn add_issue(&mut self, issue: Issue) {
288 let key = render_path(self.path, None);
289 self.issues.push(key, issue);
290 }
291
292 fn add_issue_at(&mut self, seg: PathSegment, issue: Issue) {
293 let key = render_path(self.path, Some(seg));
294 self.issues.push(key, issue);
295 }
296}
297
298fn render_path(path: &[PathSegment], extra: Option<PathSegment>) -> String {
299 use std::fmt::Write;
300
301 let mut out = String::new();
302 let mut first = true;
303
304 let iter = path.iter().cloned().chain(extra);
305
306 for seg in iter {
307 match seg {
308 PathSegment::Field(s) => {
309 if !first {
310 out.push('.');
311 }
312 out.push_str(s);
313 first = false;
314 }
315 PathSegment::Index(i) => {
316 let _ = write!(out, "[{i}]");
317 first = false;
318 }
319 PathSegment::Empty => {}
320 }
321 }
322
323 out
324}
325
326pub(crate) struct VisitorAdapter<V> {
331 visitor: V,
332 path: Vec<PathSegment>,
333 issues: VisitorIssues,
334}
335
336impl<V> VisitorAdapter<V>
337where
338 V: Visitor,
339{
340 pub(crate) const fn new(visitor: V) -> Self {
341 Self {
342 visitor,
343 path: Vec::new(),
344 issues: VisitorIssues::new(),
345 }
346 }
347
348 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
349 if self.issues.is_empty() {
350 Ok(())
351 } else {
352 Err(self.issues)
353 }
354 }
355}
356
357impl<V> VisitorCore for VisitorAdapter<V>
358where
359 V: Visitor,
360{
361 fn push(&mut self, seg: PathSegment) {
362 if !matches!(seg, PathSegment::Empty) {
363 self.path.push(seg);
364 }
365 }
366
367 fn pop(&mut self) {
368 self.path.pop();
369 }
370
371 fn enter(&mut self, node: &dyn Visitable) {
372 let mut ctx = AdapterContext {
373 path: &self.path,
374 issues: &mut self.issues,
375 };
376 self.visitor.enter(node, &mut ctx);
377 }
378
379 fn exit(&mut self, node: &dyn Visitable) {
380 let mut ctx = AdapterContext {
381 path: &self.path,
382 issues: &mut self.issues,
383 };
384 self.visitor.exit(node, &mut ctx);
385 }
386}
387
388pub fn perform_visit<S: Into<PathSegment>>(
393 visitor: &mut dyn VisitorCore,
394 node: &dyn Visitable,
395 seg: S,
396) {
397 let seg = seg.into();
398 let should_push = !matches!(seg, PathSegment::Empty);
399
400 if should_push {
401 visitor.push(seg);
402 }
403
404 visitor.enter(node);
405 node.drive(visitor);
406 visitor.exit(node);
407
408 if should_push {
409 visitor.pop();
410 }
411}
412
413pub(crate) trait VisitorMut {
419 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
420 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
421}
422
423pub trait VisitorMutCore {
429 fn enter_mut(&mut self, node: &mut dyn Visitable);
430 fn exit_mut(&mut self, node: &mut dyn Visitable);
431
432 fn push(&mut self, _: PathSegment) {}
433 fn pop(&mut self) {}
434}
435
436pub(crate) struct VisitorMutAdapter<V> {
442 visitor: V,
443 path: Vec<PathSegment>,
444 issues: VisitorIssues,
445}
446
447impl<V> VisitorMutAdapter<V>
448where
449 V: VisitorMut,
450{
451 pub(crate) const fn new(visitor: V) -> Self {
452 Self {
453 visitor,
454 path: Vec::new(),
455 issues: VisitorIssues::new(),
456 }
457 }
458
459 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
460 if self.issues.is_empty() {
461 Ok(())
462 } else {
463 Err(self.issues)
464 }
465 }
466}
467
468impl<V> VisitorMutCore for VisitorMutAdapter<V>
469where
470 V: VisitorMut,
471{
472 fn push(&mut self, seg: PathSegment) {
473 if !matches!(seg, PathSegment::Empty) {
474 self.path.push(seg);
475 }
476 }
477
478 fn pop(&mut self) {
479 self.path.pop();
480 }
481
482 fn enter_mut(&mut self, node: &mut dyn Visitable) {
483 let mut ctx = AdapterContext {
484 path: &self.path,
485 issues: &mut self.issues,
486 };
487 self.visitor.enter_mut(node, &mut ctx);
488 }
489
490 fn exit_mut(&mut self, node: &mut dyn Visitable) {
491 let mut ctx = AdapterContext {
492 path: &self.path,
493 issues: &mut self.issues,
494 };
495 self.visitor.exit_mut(node, &mut ctx);
496 }
497}
498
499pub fn perform_visit_mut<S: Into<PathSegment>>(
511 visitor: &mut dyn VisitorMutCore,
512 node: &mut dyn Visitable,
513 seg: S,
514) {
515 let seg = seg.into();
516 let should_push = !matches!(seg, PathSegment::Empty);
517
518 if should_push {
519 visitor.push(seg);
520 }
521
522 visitor.enter_mut(node);
523 node.drive_mut(visitor);
524 visitor.exit_mut(node);
525
526 if should_push {
527 visitor.pop();
528 }
529}