1pub(crate) mod context;
8pub(crate) mod sanitize;
9pub(crate) mod validate;
10
11use crate::{
12 error::{ErrorClass, ErrorOrigin, InternalError},
13 sanitize::SanitizeWriteContext,
14 traits::Visitable,
15};
16use serde::Deserialize;
17use std::{collections::BTreeMap, fmt};
18use thiserror::Error as ThisError;
19
20pub use context::{Issue, PathSegment, ScopedContext, VisitorContext};
22
23#[derive(Debug, ThisError)]
29#[error("{issues}")]
30pub struct VisitorError {
31 issues: VisitorIssues,
32}
33
34impl VisitorError {
35 #[must_use]
36 pub const fn issues(&self) -> &VisitorIssues {
37 &self.issues
38 }
39}
40
41impl From<VisitorIssues> for VisitorError {
42 fn from(issues: VisitorIssues) -> Self {
43 Self { issues }
44 }
45}
46
47impl From<VisitorError> for VisitorIssues {
48 fn from(err: VisitorError) -> Self {
49 err.issues
50 }
51}
52
53impl From<VisitorError> for InternalError {
54 fn from(_err: VisitorError) -> Self {
55 Self::classified(ErrorClass::Unsupported, ErrorOrigin::Executor)
56 }
57}
58
59#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
69pub struct VisitorIssues(BTreeMap<String, Vec<Issue>>);
70
71impl VisitorIssues {
72 #[must_use]
73 pub const fn new() -> Self {
74 Self(BTreeMap::new())
75 }
76
77 #[must_use]
78 pub fn is_empty(&self) -> bool {
79 self.0.is_empty()
80 }
81
82 #[must_use]
84 pub fn len(&self) -> usize {
85 self.0.len()
86 }
87
88 #[must_use]
89 pub fn get(&self, path: impl AsRef<str>) -> Option<&[Issue]> {
90 self.0.get(path.as_ref()).map(Vec::as_slice)
91 }
92
93 pub fn push(&mut self, path: String, issue: Issue) {
94 self.0.entry(path).or_default().push(issue);
95 }
96}
97
98impl From<BTreeMap<String, Vec<Issue>>> for VisitorIssues {
99 fn from(map: BTreeMap<String, Vec<Issue>>) -> Self {
100 Self(map)
101 }
102}
103
104impl fmt::Display for VisitorIssues {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 let mut wrote = false;
107
108 for (path, messages) in &self.0 {
109 for message in messages {
110 if wrote {
111 writeln!(f)?;
112 }
113
114 if path.is_empty() {
115 write!(f, "{message}")?;
116 } else {
117 write!(f, "{path}: {message}")?;
118 }
119
120 wrote = true;
121 }
122 }
123
124 if !wrote {
125 write!(f, "no visitor issues")?;
126 }
127
128 Ok(())
129 }
130}
131
132impl std::error::Error for VisitorIssues {}
133
134pub(crate) trait Visitor {
140 fn enter(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
141 fn exit(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
142}
143
144pub trait VisitorCore {
150 fn enter(&mut self, node: &dyn Visitable);
151 fn exit(&mut self, node: &dyn Visitable);
152
153 fn push(&mut self, _: PathSegment) {}
154 fn pop(&mut self) {}
155}
156
157pub struct VisitableFieldDescriptor<T> {
167 name: &'static str,
168 drive: fn(&T, &mut dyn VisitorCore),
169 drive_mut: fn(&mut T, &mut dyn VisitorMutCore),
170}
171
172impl<T> VisitableFieldDescriptor<T> {
173 #[must_use]
175 pub const fn new(
176 name: &'static str,
177 drive: fn(&T, &mut dyn VisitorCore),
178 drive_mut: fn(&mut T, &mut dyn VisitorMutCore),
179 ) -> Self {
180 Self {
181 name,
182 drive,
183 drive_mut,
184 }
185 }
186
187 #[must_use]
189 pub const fn name(&self) -> &'static str {
190 self.name
191 }
192}
193
194pub fn drive_visitable_fields<T>(
196 visitor: &mut dyn VisitorCore,
197 node: &T,
198 fields: &[VisitableFieldDescriptor<T>],
199) {
200 for field in fields {
201 (field.drive)(node, visitor);
202 }
203}
204
205pub fn drive_visitable_fields_mut<T>(
207 visitor: &mut dyn VisitorMutCore,
208 node: &mut T,
209 fields: &[VisitableFieldDescriptor<T>],
210) {
211 for field in fields {
212 (field.drive_mut)(node, visitor);
213 }
214}
215
216pub struct SanitizeFieldDescriptor<T> {
226 sanitize: fn(&mut T, &mut dyn VisitorContext),
227}
228
229impl<T> SanitizeFieldDescriptor<T> {
230 #[must_use]
232 pub const fn new(sanitize: fn(&mut T, &mut dyn VisitorContext)) -> Self {
233 Self { sanitize }
234 }
235}
236
237pub fn drive_sanitize_fields<T>(
239 node: &mut T,
240 ctx: &mut dyn VisitorContext,
241 fields: &[SanitizeFieldDescriptor<T>],
242) {
243 for field in fields {
244 (field.sanitize)(node, ctx);
245 }
246}
247
248pub struct ValidateFieldDescriptor<T> {
258 validate: fn(&T, &mut dyn VisitorContext),
259}
260
261impl<T> ValidateFieldDescriptor<T> {
262 #[must_use]
264 pub const fn new(validate: fn(&T, &mut dyn VisitorContext)) -> Self {
265 Self { validate }
266 }
267}
268
269pub fn drive_validate_fields<T>(
271 node: &T,
272 ctx: &mut dyn VisitorContext,
273 fields: &[ValidateFieldDescriptor<T>],
274) {
275 for field in fields {
276 (field.validate)(node, ctx);
277 }
278}
279
280struct AdapterContext<'a> {
285 path: &'a [PathSegment],
286 issues: &'a mut VisitorIssues,
287 sanitize_write_context: Option<SanitizeWriteContext>,
288}
289
290impl VisitorContext for AdapterContext<'_> {
291 fn add_issue(&mut self, issue: Issue) {
292 let key = render_path(self.path, None);
293 self.issues.push(key, issue);
294 }
295
296 fn add_issue_at(&mut self, seg: PathSegment, issue: Issue) {
297 let key = render_path(self.path, Some(seg));
298 self.issues.push(key, issue);
299 }
300
301 fn sanitize_write_context(&self) -> Option<SanitizeWriteContext> {
302 self.sanitize_write_context
303 }
304}
305
306fn render_path(path: &[PathSegment], extra: Option<PathSegment>) -> String {
307 use std::fmt::Write;
308
309 let mut out = String::new();
310 let mut first = true;
311
312 let iter = path.iter().cloned().chain(extra);
313
314 for seg in iter {
315 match seg {
316 PathSegment::Field(s) => {
317 if !first {
318 out.push('.');
319 }
320 out.push_str(s);
321 first = false;
322 }
323 PathSegment::Index(i) => {
324 let _ = write!(out, "[{i}]");
325 first = false;
326 }
327 PathSegment::Empty => {}
328 }
329 }
330
331 out
332}
333
334pub(crate) struct VisitorAdapter<V> {
339 visitor: V,
340 path: Vec<PathSegment>,
341 issues: VisitorIssues,
342}
343
344impl<V> VisitorAdapter<V>
345where
346 V: Visitor,
347{
348 pub(crate) const fn new(visitor: V) -> Self {
349 Self {
350 visitor,
351 path: Vec::new(),
352 issues: VisitorIssues::new(),
353 }
354 }
355
356 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
357 if self.issues.is_empty() {
358 Ok(())
359 } else {
360 Err(self.issues)
361 }
362 }
363}
364
365impl<V> VisitorCore for VisitorAdapter<V>
366where
367 V: Visitor,
368{
369 fn push(&mut self, seg: PathSegment) {
370 if !matches!(seg, PathSegment::Empty) {
371 self.path.push(seg);
372 }
373 }
374
375 fn pop(&mut self) {
376 self.path.pop();
377 }
378
379 fn enter(&mut self, node: &dyn Visitable) {
380 let mut ctx = AdapterContext {
381 path: &self.path,
382 issues: &mut self.issues,
383 sanitize_write_context: None,
384 };
385 self.visitor.enter(node, &mut ctx);
386 }
387
388 fn exit(&mut self, node: &dyn Visitable) {
389 let mut ctx = AdapterContext {
390 path: &self.path,
391 issues: &mut self.issues,
392 sanitize_write_context: None,
393 };
394 self.visitor.exit(node, &mut ctx);
395 }
396}
397
398pub fn perform_visit<S: Into<PathSegment>>(
403 visitor: &mut dyn VisitorCore,
404 node: &dyn Visitable,
405 seg: S,
406) {
407 let seg = seg.into();
408 let should_push = !matches!(seg, PathSegment::Empty);
409
410 if should_push {
411 visitor.push(seg);
412 }
413
414 visitor.enter(node);
415 node.drive(visitor);
416 visitor.exit(node);
417
418 if should_push {
419 visitor.pop();
420 }
421}
422
423pub(crate) trait VisitorMut {
429 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
430 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
431}
432
433pub trait VisitorMutCore {
439 fn enter_mut(&mut self, node: &mut dyn Visitable);
440 fn exit_mut(&mut self, node: &mut dyn Visitable);
441
442 fn push(&mut self, _: PathSegment) {}
443 fn pop(&mut self) {}
444}
445
446pub(crate) struct VisitorMutAdapter<V> {
452 visitor: V,
453 path: Vec<PathSegment>,
454 issues: VisitorIssues,
455 sanitize_write_context: Option<SanitizeWriteContext>,
456}
457
458impl<V> VisitorMutAdapter<V>
459where
460 V: VisitorMut,
461{
462 pub(crate) const fn with_sanitize_write_context(
463 visitor: V,
464 sanitize_write_context: Option<SanitizeWriteContext>,
465 ) -> Self {
466 Self {
467 visitor,
468 path: Vec::new(),
469 issues: VisitorIssues::new(),
470 sanitize_write_context,
471 }
472 }
473
474 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
475 if self.issues.is_empty() {
476 Ok(())
477 } else {
478 Err(self.issues)
479 }
480 }
481}
482
483impl<V> VisitorMutCore for VisitorMutAdapter<V>
484where
485 V: VisitorMut,
486{
487 fn push(&mut self, seg: PathSegment) {
488 if !matches!(seg, PathSegment::Empty) {
489 self.path.push(seg);
490 }
491 }
492
493 fn pop(&mut self) {
494 self.path.pop();
495 }
496
497 fn enter_mut(&mut self, node: &mut dyn Visitable) {
498 let mut ctx = AdapterContext {
499 path: &self.path,
500 issues: &mut self.issues,
501 sanitize_write_context: self.sanitize_write_context,
502 };
503 self.visitor.enter_mut(node, &mut ctx);
504 }
505
506 fn exit_mut(&mut self, node: &mut dyn Visitable) {
507 let mut ctx = AdapterContext {
508 path: &self.path,
509 issues: &mut self.issues,
510 sanitize_write_context: self.sanitize_write_context,
511 };
512 self.visitor.exit_mut(node, &mut ctx);
513 }
514}
515
516pub fn perform_visit_mut<S: Into<PathSegment>>(
528 visitor: &mut dyn VisitorMutCore,
529 node: &mut dyn Visitable,
530 seg: S,
531) {
532 let seg = seg.into();
533 let should_push = !matches!(seg, PathSegment::Empty);
534
535 if should_push {
536 visitor.push(seg);
537 }
538
539 visitor.enter_mut(node);
540 node.drive_mut(visitor);
541 visitor.exit_mut(node);
542
543 if should_push {
544 visitor.pop();
545 }
546}