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(
56 ErrorClass::Unsupported,
57 ErrorOrigin::Executor,
58 err.to_string(),
59 )
60 }
61}
62
63#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
73pub struct VisitorIssues(BTreeMap<String, Vec<String>>);
74
75impl VisitorIssues {
76 #[must_use]
77 pub const fn new() -> Self {
78 Self(BTreeMap::new())
79 }
80
81 #[must_use]
82 pub fn is_empty(&self) -> bool {
83 self.0.is_empty()
84 }
85
86 #[must_use]
88 pub fn len(&self) -> usize {
89 self.0.len()
90 }
91
92 #[must_use]
93 pub fn get(&self, path: impl AsRef<str>) -> Option<&[String]> {
94 self.0.get(path.as_ref()).map(Vec::as_slice)
95 }
96
97 pub fn push(&mut self, path: String, issue: Issue) {
98 self.0.entry(path).or_default().push(issue.into_message());
99 }
100}
101
102impl From<BTreeMap<String, Vec<String>>> for VisitorIssues {
103 fn from(map: BTreeMap<String, Vec<String>>) -> Self {
104 Self(map)
105 }
106}
107
108impl fmt::Display for VisitorIssues {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 let mut wrote = false;
111
112 for (path, messages) in &self.0 {
113 for message in messages {
114 if wrote {
115 writeln!(f)?;
116 }
117
118 if path.is_empty() {
119 write!(f, "{message}")?;
120 } else {
121 write!(f, "{path}: {message}")?;
122 }
123
124 wrote = true;
125 }
126 }
127
128 if !wrote {
129 write!(f, "no visitor issues")?;
130 }
131
132 Ok(())
133 }
134}
135
136impl std::error::Error for VisitorIssues {}
137
138pub(crate) trait Visitor {
144 fn enter(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
145 fn exit(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
146}
147
148pub trait VisitorCore {
154 fn enter(&mut self, node: &dyn Visitable);
155 fn exit(&mut self, node: &dyn Visitable);
156
157 fn push(&mut self, _: PathSegment) {}
158 fn pop(&mut self) {}
159}
160
161pub struct VisitableFieldDescriptor<T> {
171 name: &'static str,
172 drive: fn(&T, &mut dyn VisitorCore),
173 drive_mut: fn(&mut T, &mut dyn VisitorMutCore),
174}
175
176impl<T> VisitableFieldDescriptor<T> {
177 #[must_use]
179 pub const fn new(
180 name: &'static str,
181 drive: fn(&T, &mut dyn VisitorCore),
182 drive_mut: fn(&mut T, &mut dyn VisitorMutCore),
183 ) -> Self {
184 Self {
185 name,
186 drive,
187 drive_mut,
188 }
189 }
190
191 #[must_use]
193 pub const fn name(&self) -> &'static str {
194 self.name
195 }
196}
197
198pub fn drive_visitable_fields<T>(
200 visitor: &mut dyn VisitorCore,
201 node: &T,
202 fields: &[VisitableFieldDescriptor<T>],
203) {
204 for field in fields {
205 (field.drive)(node, visitor);
206 }
207}
208
209pub fn drive_visitable_fields_mut<T>(
211 visitor: &mut dyn VisitorMutCore,
212 node: &mut T,
213 fields: &[VisitableFieldDescriptor<T>],
214) {
215 for field in fields {
216 (field.drive_mut)(node, visitor);
217 }
218}
219
220pub struct SanitizeFieldDescriptor<T> {
230 sanitize: fn(&mut T, &mut dyn VisitorContext),
231}
232
233impl<T> SanitizeFieldDescriptor<T> {
234 #[must_use]
236 pub const fn new(sanitize: fn(&mut T, &mut dyn VisitorContext)) -> Self {
237 Self { sanitize }
238 }
239}
240
241pub fn drive_sanitize_fields<T>(
243 node: &mut T,
244 ctx: &mut dyn VisitorContext,
245 fields: &[SanitizeFieldDescriptor<T>],
246) {
247 for field in fields {
248 (field.sanitize)(node, ctx);
249 }
250}
251
252pub struct ValidateFieldDescriptor<T> {
262 validate: fn(&T, &mut dyn VisitorContext),
263}
264
265impl<T> ValidateFieldDescriptor<T> {
266 #[must_use]
268 pub const fn new(validate: fn(&T, &mut dyn VisitorContext)) -> Self {
269 Self { validate }
270 }
271}
272
273pub fn drive_validate_fields<T>(
275 node: &T,
276 ctx: &mut dyn VisitorContext,
277 fields: &[ValidateFieldDescriptor<T>],
278) {
279 for field in fields {
280 (field.validate)(node, ctx);
281 }
282}
283
284struct AdapterContext<'a> {
289 path: &'a [PathSegment],
290 issues: &'a mut VisitorIssues,
291 sanitize_write_context: Option<SanitizeWriteContext>,
292}
293
294impl VisitorContext for AdapterContext<'_> {
295 fn add_issue(&mut self, issue: Issue) {
296 let key = render_path(self.path, None);
297 self.issues.push(key, issue);
298 }
299
300 fn add_issue_at(&mut self, seg: PathSegment, issue: Issue) {
301 let key = render_path(self.path, Some(seg));
302 self.issues.push(key, issue);
303 }
304
305 fn sanitize_write_context(&self) -> Option<SanitizeWriteContext> {
306 self.sanitize_write_context
307 }
308}
309
310fn render_path(path: &[PathSegment], extra: Option<PathSegment>) -> String {
311 use std::fmt::Write;
312
313 let mut out = String::new();
314 let mut first = true;
315
316 let iter = path.iter().cloned().chain(extra);
317
318 for seg in iter {
319 match seg {
320 PathSegment::Field(s) => {
321 if !first {
322 out.push('.');
323 }
324 out.push_str(s);
325 first = false;
326 }
327 PathSegment::Index(i) => {
328 let _ = write!(out, "[{i}]");
329 first = false;
330 }
331 PathSegment::Empty => {}
332 }
333 }
334
335 out
336}
337
338pub(crate) struct VisitorAdapter<V> {
343 visitor: V,
344 path: Vec<PathSegment>,
345 issues: VisitorIssues,
346}
347
348impl<V> VisitorAdapter<V>
349where
350 V: Visitor,
351{
352 pub(crate) const fn new(visitor: V) -> Self {
353 Self {
354 visitor,
355 path: Vec::new(),
356 issues: VisitorIssues::new(),
357 }
358 }
359
360 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
361 if self.issues.is_empty() {
362 Ok(())
363 } else {
364 Err(self.issues)
365 }
366 }
367}
368
369impl<V> VisitorCore for VisitorAdapter<V>
370where
371 V: Visitor,
372{
373 fn push(&mut self, seg: PathSegment) {
374 if !matches!(seg, PathSegment::Empty) {
375 self.path.push(seg);
376 }
377 }
378
379 fn pop(&mut self) {
380 self.path.pop();
381 }
382
383 fn enter(&mut self, node: &dyn Visitable) {
384 let mut ctx = AdapterContext {
385 path: &self.path,
386 issues: &mut self.issues,
387 sanitize_write_context: None,
388 };
389 self.visitor.enter(node, &mut ctx);
390 }
391
392 fn exit(&mut self, node: &dyn Visitable) {
393 let mut ctx = AdapterContext {
394 path: &self.path,
395 issues: &mut self.issues,
396 sanitize_write_context: None,
397 };
398 self.visitor.exit(node, &mut ctx);
399 }
400}
401
402pub fn perform_visit<S: Into<PathSegment>>(
407 visitor: &mut dyn VisitorCore,
408 node: &dyn Visitable,
409 seg: S,
410) {
411 let seg = seg.into();
412 let should_push = !matches!(seg, PathSegment::Empty);
413
414 if should_push {
415 visitor.push(seg);
416 }
417
418 visitor.enter(node);
419 node.drive(visitor);
420 visitor.exit(node);
421
422 if should_push {
423 visitor.pop();
424 }
425}
426
427pub(crate) trait VisitorMut {
433 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
434 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
435}
436
437pub trait VisitorMutCore {
443 fn enter_mut(&mut self, node: &mut dyn Visitable);
444 fn exit_mut(&mut self, node: &mut dyn Visitable);
445
446 fn push(&mut self, _: PathSegment) {}
447 fn pop(&mut self) {}
448}
449
450pub(crate) struct VisitorMutAdapter<V> {
456 visitor: V,
457 path: Vec<PathSegment>,
458 issues: VisitorIssues,
459 sanitize_write_context: Option<SanitizeWriteContext>,
460}
461
462impl<V> VisitorMutAdapter<V>
463where
464 V: VisitorMut,
465{
466 pub(crate) const fn with_sanitize_write_context(
467 visitor: V,
468 sanitize_write_context: Option<SanitizeWriteContext>,
469 ) -> Self {
470 Self {
471 visitor,
472 path: Vec::new(),
473 issues: VisitorIssues::new(),
474 sanitize_write_context,
475 }
476 }
477
478 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
479 if self.issues.is_empty() {
480 Ok(())
481 } else {
482 Err(self.issues)
483 }
484 }
485}
486
487impl<V> VisitorMutCore for VisitorMutAdapter<V>
488where
489 V: VisitorMut,
490{
491 fn push(&mut self, seg: PathSegment) {
492 if !matches!(seg, PathSegment::Empty) {
493 self.path.push(seg);
494 }
495 }
496
497 fn pop(&mut self) {
498 self.path.pop();
499 }
500
501 fn enter_mut(&mut self, node: &mut dyn Visitable) {
502 let mut ctx = AdapterContext {
503 path: &self.path,
504 issues: &mut self.issues,
505 sanitize_write_context: self.sanitize_write_context,
506 };
507 self.visitor.enter_mut(node, &mut ctx);
508 }
509
510 fn exit_mut(&mut self, node: &mut dyn Visitable) {
511 let mut ctx = AdapterContext {
512 path: &self.path,
513 issues: &mut self.issues,
514 sanitize_write_context: self.sanitize_write_context,
515 };
516 self.visitor.exit_mut(node, &mut ctx);
517 }
518}
519
520pub fn perform_visit_mut<S: Into<PathSegment>>(
532 visitor: &mut dyn VisitorMutCore,
533 node: &mut dyn Visitable,
534 seg: S,
535) {
536 let seg = seg.into();
537 let should_push = !matches!(seg, PathSegment::Empty);
538
539 if should_push {
540 visitor.push(seg);
541 }
542
543 visitor.enter_mut(node);
544 node.drive_mut(visitor);
545 visitor.exit_mut(node);
546
547 if should_push {
548 visitor.pop();
549 }
550}