1#[macro_use]
8mod macros;
9mod traits;
10
11pub(crate) mod context;
12pub(crate) mod sanitize;
13pub(crate) mod validate;
14
15use crate::{
16 error::{ErrorClass, ErrorOrigin, InternalError},
17 sanitize::SanitizeWriteContext,
18};
19use serde::Deserialize;
20use std::{collections::BTreeMap, fmt};
21use thiserror::Error as ThisError;
22
23pub use context::{Issue, PathSegment, ScopedContext, VisitorContext};
25pub use traits::{
26 Sanitize, SanitizeAuto, SanitizeCustom, Sanitizer, Validate, ValidateAuto, ValidateCustom,
27 Validator, Visitable,
28};
29
30#[derive(Debug, ThisError)]
36#[error("{issues}")]
37pub struct VisitorError {
38 issues: VisitorIssues,
39}
40
41impl VisitorError {
42 #[must_use]
43 pub const fn issues(&self) -> &VisitorIssues {
44 &self.issues
45 }
46}
47
48impl From<VisitorIssues> for VisitorError {
49 fn from(issues: VisitorIssues) -> Self {
50 Self { issues }
51 }
52}
53
54impl From<VisitorError> for VisitorIssues {
55 fn from(err: VisitorError) -> Self {
56 err.issues
57 }
58}
59
60impl From<VisitorError> for InternalError {
61 fn from(_err: VisitorError) -> Self {
62 Self::classified(ErrorClass::Unsupported, ErrorOrigin::Executor)
63 }
64}
65
66#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
76pub struct VisitorIssues(BTreeMap<String, Vec<Issue>>);
77
78impl VisitorIssues {
79 #[must_use]
80 pub const fn new() -> Self {
81 Self(BTreeMap::new())
82 }
83
84 #[must_use]
85 pub fn is_empty(&self) -> bool {
86 self.0.is_empty()
87 }
88
89 #[must_use]
91 pub fn len(&self) -> usize {
92 self.0.len()
93 }
94
95 #[must_use]
96 pub fn get(&self, path: impl AsRef<str>) -> Option<&[Issue]> {
97 self.0.get(path.as_ref()).map(Vec::as_slice)
98 }
99
100 pub fn push(&mut self, path: String, issue: Issue) {
101 self.0.entry(path).or_default().push(issue);
102 }
103}
104
105impl From<BTreeMap<String, Vec<Issue>>> for VisitorIssues {
106 fn from(map: BTreeMap<String, Vec<Issue>>) -> Self {
107 Self(map)
108 }
109}
110
111impl fmt::Display for VisitorIssues {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 let mut wrote = false;
114
115 for (path, messages) in &self.0 {
116 for message in messages {
117 if wrote {
118 writeln!(f)?;
119 }
120
121 if path.is_empty() {
122 write!(f, "{message}")?;
123 } else {
124 write!(f, "{path}: {message}")?;
125 }
126
127 wrote = true;
128 }
129 }
130
131 if !wrote {
132 write!(f, "no visitor issues")?;
133 }
134
135 Ok(())
136 }
137}
138
139impl std::error::Error for VisitorIssues {}
140
141pub(crate) trait Visitor {
147 fn enter(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
148 fn exit(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
149}
150
151pub trait VisitorCore {
157 fn enter(&mut self, node: &dyn Visitable);
158 fn exit(&mut self, node: &dyn Visitable);
159
160 fn push(&mut self, _: PathSegment) {}
161 fn pop(&mut self) {}
162}
163
164pub struct VisitableFieldDescriptor<T> {
174 name: &'static str,
175 drive: fn(&T, &mut dyn VisitorCore),
176 drive_mut: fn(&mut T, &mut dyn VisitorMutCore),
177}
178
179impl<T> VisitableFieldDescriptor<T> {
180 #[must_use]
182 pub const fn new(
183 name: &'static str,
184 drive: fn(&T, &mut dyn VisitorCore),
185 drive_mut: fn(&mut T, &mut dyn VisitorMutCore),
186 ) -> Self {
187 Self {
188 name,
189 drive,
190 drive_mut,
191 }
192 }
193
194 #[must_use]
196 pub const fn name(&self) -> &'static str {
197 self.name
198 }
199}
200
201pub fn drive_visitable_fields<T>(
203 visitor: &mut dyn VisitorCore,
204 node: &T,
205 fields: &[VisitableFieldDescriptor<T>],
206) {
207 for field in fields {
208 (field.drive)(node, visitor);
209 }
210}
211
212pub fn drive_visitable_fields_mut<T>(
214 visitor: &mut dyn VisitorMutCore,
215 node: &mut T,
216 fields: &[VisitableFieldDescriptor<T>],
217) {
218 for field in fields {
219 (field.drive_mut)(node, visitor);
220 }
221}
222
223pub struct SanitizeFieldDescriptor<T> {
233 sanitize: fn(&mut T, &mut dyn VisitorContext),
234}
235
236impl<T> SanitizeFieldDescriptor<T> {
237 #[must_use]
239 pub const fn new(sanitize: fn(&mut T, &mut dyn VisitorContext)) -> Self {
240 Self { sanitize }
241 }
242}
243
244pub fn drive_sanitize_fields<T>(
246 node: &mut T,
247 ctx: &mut dyn VisitorContext,
248 fields: &[SanitizeFieldDescriptor<T>],
249) {
250 for field in fields {
251 (field.sanitize)(node, ctx);
252 }
253}
254
255pub struct ValidateFieldDescriptor<T> {
265 validate: fn(&T, &mut dyn VisitorContext),
266}
267
268impl<T> ValidateFieldDescriptor<T> {
269 #[must_use]
271 pub const fn new(validate: fn(&T, &mut dyn VisitorContext)) -> Self {
272 Self { validate }
273 }
274}
275
276pub fn drive_validate_fields<T>(
278 node: &T,
279 ctx: &mut dyn VisitorContext,
280 fields: &[ValidateFieldDescriptor<T>],
281) {
282 for field in fields {
283 (field.validate)(node, ctx);
284 }
285}
286
287struct AdapterContext<'a> {
292 path: &'a [PathSegment],
293 issues: &'a mut VisitorIssues,
294 sanitize_write_context: Option<SanitizeWriteContext>,
295}
296
297impl VisitorContext for AdapterContext<'_> {
298 fn add_issue(&mut self, issue: Issue) {
299 let key = render_path(self.path, None);
300 self.issues.push(key, issue);
301 }
302
303 fn add_issue_at(&mut self, seg: PathSegment, issue: Issue) {
304 let key = render_path(self.path, Some(seg));
305 self.issues.push(key, issue);
306 }
307
308 fn sanitize_write_context(&self) -> Option<SanitizeWriteContext> {
309 self.sanitize_write_context
310 }
311}
312
313fn render_path(path: &[PathSegment], extra: Option<PathSegment>) -> String {
314 use std::fmt::Write;
315
316 let mut out = String::new();
317 let mut first = true;
318
319 let iter = path.iter().cloned().chain(extra);
320
321 for seg in iter {
322 match seg {
323 PathSegment::Field(s) => {
324 if !first {
325 out.push('.');
326 }
327 out.push_str(s);
328 first = false;
329 }
330 PathSegment::Index(i) => {
331 let _ = write!(out, "[{i}]");
332 first = false;
333 }
334 PathSegment::Empty => {}
335 }
336 }
337
338 out
339}
340
341pub(crate) struct VisitorAdapter<V> {
346 visitor: V,
347 path: Vec<PathSegment>,
348 issues: VisitorIssues,
349}
350
351impl<V> VisitorAdapter<V>
352where
353 V: Visitor,
354{
355 pub(crate) const fn new(visitor: V) -> Self {
356 Self {
357 visitor,
358 path: Vec::new(),
359 issues: VisitorIssues::new(),
360 }
361 }
362
363 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
364 if self.issues.is_empty() {
365 Ok(())
366 } else {
367 Err(self.issues)
368 }
369 }
370}
371
372impl<V> VisitorCore for VisitorAdapter<V>
373where
374 V: Visitor,
375{
376 fn push(&mut self, seg: PathSegment) {
377 if !matches!(seg, PathSegment::Empty) {
378 self.path.push(seg);
379 }
380 }
381
382 fn pop(&mut self) {
383 self.path.pop();
384 }
385
386 fn enter(&mut self, node: &dyn Visitable) {
387 let mut ctx = AdapterContext {
388 path: &self.path,
389 issues: &mut self.issues,
390 sanitize_write_context: None,
391 };
392 self.visitor.enter(node, &mut ctx);
393 }
394
395 fn exit(&mut self, node: &dyn Visitable) {
396 let mut ctx = AdapterContext {
397 path: &self.path,
398 issues: &mut self.issues,
399 sanitize_write_context: None,
400 };
401 self.visitor.exit(node, &mut ctx);
402 }
403}
404
405pub fn perform_visit<S: Into<PathSegment>>(
410 visitor: &mut dyn VisitorCore,
411 node: &dyn Visitable,
412 seg: S,
413) {
414 let seg = seg.into();
415 let should_push = !matches!(seg, PathSegment::Empty);
416
417 if should_push {
418 visitor.push(seg);
419 }
420
421 visitor.enter(node);
422 node.drive(visitor);
423 visitor.exit(node);
424
425 if should_push {
426 visitor.pop();
427 }
428}
429
430pub(crate) trait VisitorMut {
436 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
437 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
438}
439
440pub trait VisitorMutCore {
446 fn enter_mut(&mut self, node: &mut dyn Visitable);
447 fn exit_mut(&mut self, node: &mut dyn Visitable);
448
449 fn push(&mut self, _: PathSegment) {}
450 fn pop(&mut self) {}
451}
452
453pub(crate) struct VisitorMutAdapter<V> {
459 visitor: V,
460 path: Vec<PathSegment>,
461 issues: VisitorIssues,
462 sanitize_write_context: Option<SanitizeWriteContext>,
463}
464
465impl<V> VisitorMutAdapter<V>
466where
467 V: VisitorMut,
468{
469 pub(crate) const fn with_sanitize_write_context(
470 visitor: V,
471 sanitize_write_context: Option<SanitizeWriteContext>,
472 ) -> Self {
473 Self {
474 visitor,
475 path: Vec::new(),
476 issues: VisitorIssues::new(),
477 sanitize_write_context,
478 }
479 }
480
481 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
482 if self.issues.is_empty() {
483 Ok(())
484 } else {
485 Err(self.issues)
486 }
487 }
488}
489
490impl<V> VisitorMutCore for VisitorMutAdapter<V>
491where
492 V: VisitorMut,
493{
494 fn push(&mut self, seg: PathSegment) {
495 if !matches!(seg, PathSegment::Empty) {
496 self.path.push(seg);
497 }
498 }
499
500 fn pop(&mut self) {
501 self.path.pop();
502 }
503
504 fn enter_mut(&mut self, node: &mut dyn Visitable) {
505 let mut ctx = AdapterContext {
506 path: &self.path,
507 issues: &mut self.issues,
508 sanitize_write_context: self.sanitize_write_context,
509 };
510 self.visitor.enter_mut(node, &mut ctx);
511 }
512
513 fn exit_mut(&mut self, node: &mut dyn Visitable) {
514 let mut ctx = AdapterContext {
515 path: &self.path,
516 issues: &mut self.issues,
517 sanitize_write_context: self.sanitize_write_context,
518 };
519 self.visitor.exit_mut(node, &mut ctx);
520 }
521}
522
523pub fn perform_visit_mut<S: Into<PathSegment>>(
535 visitor: &mut dyn VisitorMutCore,
536 node: &mut dyn Visitable,
537 seg: S,
538) {
539 let seg = seg.into();
540 let should_push = !matches!(seg, PathSegment::Empty);
541
542 if should_push {
543 visitor.push(seg);
544 }
545
546 visitor.enter_mut(node);
547 node.drive_mut(visitor);
548 visitor.exit_mut(node);
549
550 if should_push {
551 visitor.pop();
552 }
553}