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