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