1pub(crate) mod context;
8pub(crate) mod sanitize;
9pub(crate) mod validate;
10
11use crate::{
12 error::{ErrorClass, ErrorOrigin, InternalError},
13 traits::Visitable,
14};
15use candid::CandidType;
16use derive_more::{Deref, DerefMut};
17use serde::{Deserialize, Serialize};
18use std::{collections::BTreeMap, fmt};
19use thiserror::Error as ThisError;
20
21pub use context::{Issue, PathSegment, ScopedContext, VisitorContext};
23
24#[derive(Debug, ThisError)]
30#[error("{issues}")]
31pub struct VisitorError {
32 issues: VisitorIssues,
33}
34
35impl VisitorError {
36 #[must_use]
37 pub const fn issues(&self) -> &VisitorIssues {
38 &self.issues
39 }
40}
41
42impl From<VisitorIssues> for VisitorError {
43 fn from(issues: VisitorIssues) -> Self {
44 Self { issues }
45 }
46}
47
48impl From<VisitorError> for VisitorIssues {
49 fn from(err: VisitorError) -> Self {
50 err.issues
51 }
52}
53
54impl From<VisitorError> for InternalError {
55 fn from(err: VisitorError) -> Self {
56 Self::classified(
57 ErrorClass::Unsupported,
58 ErrorOrigin::Executor,
59 err.to_string(),
60 )
61 }
62}
63
64#[derive(
74 Clone, Debug, Default, Deserialize, Deref, DerefMut, Serialize, CandidType, Eq, PartialEq,
75)]
76pub struct VisitorIssues(BTreeMap<String, Vec<String>>);
77
78impl VisitorIssues {
79 #[must_use]
80 pub const fn new() -> Self {
81 Self(BTreeMap::new())
82 }
83}
84
85impl From<BTreeMap<String, Vec<String>>> for VisitorIssues {
86 fn from(map: BTreeMap<String, Vec<String>>) -> Self {
87 Self(map)
88 }
89}
90
91impl fmt::Display for VisitorIssues {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 let mut wrote = false;
94
95 for (path, messages) in &self.0 {
96 for message in messages {
97 if wrote {
98 writeln!(f)?;
99 }
100
101 if path.is_empty() {
102 write!(f, "{message}")?;
103 } else {
104 write!(f, "{path}: {message}")?;
105 }
106
107 wrote = true;
108 }
109 }
110
111 if !wrote {
112 write!(f, "no visitor issues")?;
113 }
114
115 Ok(())
116 }
117}
118
119impl std::error::Error for VisitorIssues {}
120
121pub(crate) trait Visitor {
127 fn enter(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
128 fn exit(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
129}
130
131pub trait VisitorCore {
137 fn enter(&mut self, node: &dyn Visitable);
138 fn exit(&mut self, node: &dyn Visitable);
139
140 fn push(&mut self, _: PathSegment) {}
141 fn pop(&mut self) {}
142}
143
144pub struct VisitableFieldDescriptor<T> {
154 name: &'static str,
155 drive: fn(&T, &mut dyn VisitorCore),
156 drive_mut: fn(&mut T, &mut dyn VisitorMutCore),
157}
158
159impl<T> VisitableFieldDescriptor<T> {
160 #[must_use]
162 pub const fn new(
163 name: &'static str,
164 drive: fn(&T, &mut dyn VisitorCore),
165 drive_mut: fn(&mut T, &mut dyn VisitorMutCore),
166 ) -> Self {
167 Self {
168 name,
169 drive,
170 drive_mut,
171 }
172 }
173
174 #[must_use]
176 pub const fn name(&self) -> &'static str {
177 self.name
178 }
179}
180
181pub fn drive_visitable_fields<T>(
183 visitor: &mut dyn VisitorCore,
184 node: &T,
185 fields: &[VisitableFieldDescriptor<T>],
186) {
187 for field in fields {
188 (field.drive)(node, visitor);
189 }
190}
191
192pub fn drive_visitable_fields_mut<T>(
194 visitor: &mut dyn VisitorMutCore,
195 node: &mut T,
196 fields: &[VisitableFieldDescriptor<T>],
197) {
198 for field in fields {
199 (field.drive_mut)(node, visitor);
200 }
201}
202
203pub struct SanitizeFieldDescriptor<T> {
213 sanitize: fn(&mut T, &mut dyn VisitorContext),
214}
215
216impl<T> SanitizeFieldDescriptor<T> {
217 #[must_use]
219 pub const fn new(sanitize: fn(&mut T, &mut dyn VisitorContext)) -> Self {
220 Self { sanitize }
221 }
222}
223
224pub fn drive_sanitize_fields<T>(
226 node: &mut T,
227 ctx: &mut dyn VisitorContext,
228 fields: &[SanitizeFieldDescriptor<T>],
229) {
230 for field in fields {
231 (field.sanitize)(node, ctx);
232 }
233}
234
235pub struct ValidateFieldDescriptor<T> {
245 validate: fn(&T, &mut dyn VisitorContext),
246}
247
248impl<T> ValidateFieldDescriptor<T> {
249 #[must_use]
251 pub const fn new(validate: fn(&T, &mut dyn VisitorContext)) -> Self {
252 Self { validate }
253 }
254}
255
256pub fn drive_validate_fields<T>(
258 node: &T,
259 ctx: &mut dyn VisitorContext,
260 fields: &[ValidateFieldDescriptor<T>],
261) {
262 for field in fields {
263 (field.validate)(node, ctx);
264 }
265}
266
267struct AdapterContext<'a> {
272 path: &'a [PathSegment],
273 issues: &'a mut VisitorIssues,
274}
275
276impl VisitorContext for AdapterContext<'_> {
277 fn add_issue(&mut self, issue: Issue) {
278 let key = render_path(self.path, None);
279 self.issues
280 .entry(key)
281 .or_default()
282 .push(issue.into_message());
283 }
284
285 fn add_issue_at(&mut self, seg: PathSegment, issue: Issue) {
286 let key = render_path(self.path, Some(seg));
287 self.issues
288 .entry(key)
289 .or_default()
290 .push(issue.into_message());
291 }
292}
293
294fn render_path(path: &[PathSegment], extra: Option<PathSegment>) -> String {
295 use std::fmt::Write;
296
297 let mut out = String::new();
298 let mut first = true;
299
300 let iter = path.iter().cloned().chain(extra);
301
302 for seg in iter {
303 match seg {
304 PathSegment::Field(s) => {
305 if !first {
306 out.push('.');
307 }
308 out.push_str(s);
309 first = false;
310 }
311 PathSegment::Index(i) => {
312 let _ = write!(out, "[{i}]");
313 first = false;
314 }
315 PathSegment::Empty => {}
316 }
317 }
318
319 out
320}
321
322pub(crate) struct VisitorAdapter<V> {
327 visitor: V,
328 path: Vec<PathSegment>,
329 issues: VisitorIssues,
330}
331
332impl<V> VisitorAdapter<V>
333where
334 V: Visitor,
335{
336 pub(crate) const fn new(visitor: V) -> Self {
337 Self {
338 visitor,
339 path: Vec::new(),
340 issues: VisitorIssues::new(),
341 }
342 }
343
344 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
345 if self.issues.is_empty() {
346 Ok(())
347 } else {
348 Err(self.issues)
349 }
350 }
351}
352
353impl<V> VisitorCore for VisitorAdapter<V>
354where
355 V: Visitor,
356{
357 fn push(&mut self, seg: PathSegment) {
358 if !matches!(seg, PathSegment::Empty) {
359 self.path.push(seg);
360 }
361 }
362
363 fn pop(&mut self) {
364 self.path.pop();
365 }
366
367 fn enter(&mut self, node: &dyn Visitable) {
368 let mut ctx = AdapterContext {
369 path: &self.path,
370 issues: &mut self.issues,
371 };
372 self.visitor.enter(node, &mut ctx);
373 }
374
375 fn exit(&mut self, node: &dyn Visitable) {
376 let mut ctx = AdapterContext {
377 path: &self.path,
378 issues: &mut self.issues,
379 };
380 self.visitor.exit(node, &mut ctx);
381 }
382}
383
384pub fn perform_visit<S: Into<PathSegment>>(
389 visitor: &mut dyn VisitorCore,
390 node: &dyn Visitable,
391 seg: S,
392) {
393 let seg = seg.into();
394 let should_push = !matches!(seg, PathSegment::Empty);
395
396 if should_push {
397 visitor.push(seg);
398 }
399
400 visitor.enter(node);
401 node.drive(visitor);
402 visitor.exit(node);
403
404 if should_push {
405 visitor.pop();
406 }
407}
408
409pub(crate) trait VisitorMut {
415 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
416 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
417}
418
419pub trait VisitorMutCore {
425 fn enter_mut(&mut self, node: &mut dyn Visitable);
426 fn exit_mut(&mut self, node: &mut dyn Visitable);
427
428 fn push(&mut self, _: PathSegment) {}
429 fn pop(&mut self) {}
430}
431
432pub(crate) struct VisitorMutAdapter<V> {
438 visitor: V,
439 path: Vec<PathSegment>,
440 issues: VisitorIssues,
441}
442
443impl<V> VisitorMutAdapter<V>
444where
445 V: VisitorMut,
446{
447 pub(crate) const fn new(visitor: V) -> Self {
448 Self {
449 visitor,
450 path: Vec::new(),
451 issues: VisitorIssues::new(),
452 }
453 }
454
455 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
456 if self.issues.is_empty() {
457 Ok(())
458 } else {
459 Err(self.issues)
460 }
461 }
462}
463
464impl<V> VisitorMutCore for VisitorMutAdapter<V>
465where
466 V: VisitorMut,
467{
468 fn push(&mut self, seg: PathSegment) {
469 if !matches!(seg, PathSegment::Empty) {
470 self.path.push(seg);
471 }
472 }
473
474 fn pop(&mut self) {
475 self.path.pop();
476 }
477
478 fn enter_mut(&mut self, node: &mut dyn Visitable) {
479 let mut ctx = AdapterContext {
480 path: &self.path,
481 issues: &mut self.issues,
482 };
483 self.visitor.enter_mut(node, &mut ctx);
484 }
485
486 fn exit_mut(&mut self, node: &mut dyn Visitable) {
487 let mut ctx = AdapterContext {
488 path: &self.path,
489 issues: &mut self.issues,
490 };
491 self.visitor.exit_mut(node, &mut ctx);
492 }
493}
494
495pub fn perform_visit_mut<S: Into<PathSegment>>(
507 visitor: &mut dyn VisitorMutCore,
508 node: &mut dyn Visitable,
509 seg: S,
510) {
511 let seg = seg.into();
512 let should_push = !matches!(seg, PathSegment::Empty);
513
514 if should_push {
515 visitor.push(seg);
516 }
517
518 visitor.enter_mut(node);
519 node.drive_mut(visitor);
520 visitor.exit_mut(node);
521
522 if should_push {
523 visitor.pop();
524 }
525}