1pub mod context;
2pub mod sanitize;
3pub mod validate;
4
5pub use context::*;
6pub use sanitize::SanitizeVisitor;
7pub use validate::ValidateVisitor;
8
9use crate::error::{ErrorClass, ErrorOrigin, InternalError};
10use crate::traits::Visitable;
11use candid::CandidType;
12use derive_more::{Deref, DerefMut};
13use serde::{Deserialize, Serialize};
14use std::{collections::BTreeMap, fmt};
15use thiserror::Error as ThisError;
16
17#[derive(Debug, ThisError)]
23#[error("{issues}")]
24pub struct VisitorError {
25 issues: VisitorIssues,
26}
27
28impl VisitorError {
29 #[must_use]
30 pub const fn issues(&self) -> &VisitorIssues {
31 &self.issues
32 }
33}
34
35impl From<VisitorIssues> for VisitorError {
36 fn from(issues: VisitorIssues) -> Self {
37 Self { issues }
38 }
39}
40
41impl From<VisitorError> for VisitorIssues {
42 fn from(err: VisitorError) -> Self {
43 err.issues
44 }
45}
46
47impl From<VisitorError> for InternalError {
48 fn from(err: VisitorError) -> Self {
49 Self::new(
50 ErrorClass::Unsupported,
51 ErrorOrigin::Executor,
52 err.to_string(),
53 )
54 }
55}
56
57#[derive(
67 Clone, Debug, Default, Deserialize, Deref, DerefMut, Serialize, CandidType, Eq, PartialEq,
68)]
69pub struct VisitorIssues(BTreeMap<String, Vec<String>>);
70
71impl VisitorIssues {
72 #[must_use]
73 pub const fn new() -> Self {
74 Self(BTreeMap::new())
75 }
76}
77
78impl From<BTreeMap<String, Vec<String>>> for VisitorIssues {
79 fn from(map: BTreeMap<String, Vec<String>>) -> Self {
80 Self(map)
81 }
82}
83
84impl fmt::Display for VisitorIssues {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 let mut wrote = false;
87
88 for (path, messages) in &self.0 {
89 for message in messages {
90 if wrote {
91 writeln!(f)?;
92 }
93
94 if path.is_empty() {
95 write!(f, "{message}")?;
96 } else {
97 write!(f, "{path}: {message}")?;
98 }
99
100 wrote = true;
101 }
102 }
103
104 if !wrote {
105 write!(f, "no visitor issues")?;
106 }
107
108 Ok(())
109 }
110}
111
112impl std::error::Error for VisitorIssues {}
113
114pub trait Visitor {
120 fn enter(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
121 fn exit(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
122}
123
124pub trait VisitorCore {
129 fn enter(&mut self, node: &dyn Visitable);
130 fn exit(&mut self, node: &dyn Visitable);
131
132 fn push(&mut self, _: PathSegment) {}
133 fn pop(&mut self) {}
134}
135
136struct AdapterContext<'a> {
141 path: &'a [PathSegment],
142 issues: &'a mut VisitorIssues,
143}
144
145impl VisitorContext for AdapterContext<'_> {
146 fn add_issue(&mut self, issue: Issue) {
147 let key = render_path(self.path, None);
148 self.issues.entry(key).or_default().push(issue.message);
149 }
150
151 fn add_issue_at(&mut self, seg: PathSegment, issue: Issue) {
152 let key = render_path(self.path, Some(seg));
153 self.issues.entry(key).or_default().push(issue.message);
154 }
155}
156
157fn render_path(path: &[PathSegment], extra: Option<PathSegment>) -> String {
158 use std::fmt::Write;
159
160 let mut out = String::new();
161 let mut first = true;
162
163 let iter = path.iter().cloned().chain(extra);
164
165 for seg in iter {
166 match seg {
167 PathSegment::Field(s) => {
168 if !first {
169 out.push('.');
170 }
171 out.push_str(s);
172 first = false;
173 }
174 PathSegment::Index(i) => {
175 let _ = write!(out, "[{i}]");
176 first = false;
177 }
178 PathSegment::Empty => {}
179 }
180 }
181
182 out
183}
184
185pub struct VisitorAdapter<V> {
190 visitor: V,
191 path: Vec<PathSegment>,
192 issues: VisitorIssues,
193}
194
195impl<V> VisitorAdapter<V>
196where
197 V: Visitor,
198{
199 pub const fn new(visitor: V) -> Self {
200 Self {
201 visitor,
202 path: Vec::new(),
203 issues: VisitorIssues::new(),
204 }
205 }
206
207 pub const fn issues(&self) -> &VisitorIssues {
208 &self.issues
209 }
210
211 pub fn result(self) -> Result<(), VisitorIssues> {
212 if self.issues.is_empty() {
213 Ok(())
214 } else {
215 Err(self.issues)
216 }
217 }
218}
219
220impl<V> VisitorCore for VisitorAdapter<V>
221where
222 V: Visitor,
223{
224 fn push(&mut self, seg: PathSegment) {
225 if !matches!(seg, PathSegment::Empty) {
226 self.path.push(seg);
227 }
228 }
229
230 fn pop(&mut self) {
231 self.path.pop();
232 }
233
234 fn enter(&mut self, node: &dyn Visitable) {
235 let mut ctx = AdapterContext {
236 path: &self.path,
237 issues: &mut self.issues,
238 };
239 self.visitor.enter(node, &mut ctx);
240 }
241
242 fn exit(&mut self, node: &dyn Visitable) {
243 let mut ctx = AdapterContext {
244 path: &self.path,
245 issues: &mut self.issues,
246 };
247 self.visitor.exit(node, &mut ctx);
248 }
249}
250
251pub fn perform_visit<S: Into<PathSegment>>(
256 visitor: &mut dyn VisitorCore,
257 node: &dyn Visitable,
258 seg: S,
259) {
260 let seg = seg.into();
261 let should_push = !matches!(seg, PathSegment::Empty);
262
263 if should_push {
264 visitor.push(seg);
265 }
266
267 visitor.enter(node);
268 node.drive(visitor);
269 visitor.exit(node);
270
271 if should_push {
272 visitor.pop();
273 }
274}
275
276pub trait VisitorMut {
281 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
282 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
283}
284
285pub trait VisitorMutCore {
290 fn enter_mut(&mut self, node: &mut dyn Visitable);
291 fn exit_mut(&mut self, node: &mut dyn Visitable);
292
293 fn push(&mut self, _: PathSegment) {}
294 fn pop(&mut self) {}
295}
296
297pub struct VisitorMutAdapter<V> {
302 visitor: V,
303 path: Vec<PathSegment>,
304 issues: VisitorIssues,
305}
306
307impl<V> VisitorMutAdapter<V>
308where
309 V: VisitorMut,
310{
311 pub const fn new(visitor: V) -> Self {
312 Self {
313 visitor,
314 path: Vec::new(),
315 issues: VisitorIssues::new(),
316 }
317 }
318
319 pub const fn issues(&self) -> &VisitorIssues {
320 &self.issues
321 }
322
323 pub fn result(self) -> Result<(), VisitorIssues> {
324 if self.issues.is_empty() {
325 Ok(())
326 } else {
327 Err(self.issues)
328 }
329 }
330}
331
332impl<V> VisitorMutCore for VisitorMutAdapter<V>
333where
334 V: VisitorMut,
335{
336 fn push(&mut self, seg: PathSegment) {
337 if !matches!(seg, PathSegment::Empty) {
338 self.path.push(seg);
339 }
340 }
341
342 fn pop(&mut self) {
343 self.path.pop();
344 }
345
346 fn enter_mut(&mut self, node: &mut dyn Visitable) {
347 let mut ctx = AdapterContext {
348 path: &self.path,
349 issues: &mut self.issues,
350 };
351 self.visitor.enter_mut(node, &mut ctx);
352 }
353
354 fn exit_mut(&mut self, node: &mut dyn Visitable) {
355 let mut ctx = AdapterContext {
356 path: &self.path,
357 issues: &mut self.issues,
358 };
359 self.visitor.exit_mut(node, &mut ctx);
360 }
361}
362
363pub fn perform_visit_mut<S: Into<PathSegment>>(
368 visitor: &mut dyn VisitorMutCore,
369 node: &mut dyn Visitable,
370 seg: S,
371) {
372 let seg = seg.into();
373 let should_push = !matches!(seg, PathSegment::Empty);
374
375 if should_push {
376 visitor.push(seg);
377 }
378
379 visitor.enter_mut(node);
380 node.drive_mut(visitor);
381 visitor.exit_mut(node);
382
383 if should_push {
384 visitor.pop();
385 }
386}