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 trait VisitorMut {
283 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
284 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
285}
286
287pub trait VisitorMutCore {
292 fn enter_mut(&mut self, node: &mut dyn Visitable);
293 fn exit_mut(&mut self, node: &mut dyn Visitable);
294
295 fn push(&mut self, _: PathSegment) {}
296 fn pop(&mut self) {}
297}
298
299pub struct VisitorMutAdapter<V> {
304 visitor: V,
305 path: Vec<PathSegment>,
306 issues: VisitorIssues,
307}
308
309impl<V> VisitorMutAdapter<V>
310where
311 V: VisitorMut,
312{
313 pub const fn new(visitor: V) -> Self {
314 Self {
315 visitor,
316 path: Vec::new(),
317 issues: VisitorIssues::new(),
318 }
319 }
320
321 pub const fn issues(&self) -> &VisitorIssues {
322 &self.issues
323 }
324
325 pub fn result(self) -> Result<(), VisitorIssues> {
326 if self.issues.is_empty() {
327 Ok(())
328 } else {
329 Err(self.issues)
330 }
331 }
332}
333
334impl<V> VisitorMutCore for VisitorMutAdapter<V>
335where
336 V: VisitorMut,
337{
338 fn push(&mut self, seg: PathSegment) {
339 if !matches!(seg, PathSegment::Empty) {
340 self.path.push(seg);
341 }
342 }
343
344 fn pop(&mut self) {
345 self.path.pop();
346 }
347
348 fn enter_mut(&mut self, node: &mut dyn Visitable) {
349 let mut ctx = AdapterContext {
350 path: &self.path,
351 issues: &mut self.issues,
352 };
353 self.visitor.enter_mut(node, &mut ctx);
354 }
355
356 fn exit_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.exit_mut(node, &mut ctx);
362 }
363}
364
365pub fn perform_visit_mut<S: Into<PathSegment>>(
370 visitor: &mut dyn VisitorMutCore,
371 node: &mut dyn Visitable,
372 seg: S,
373) {
374 let seg = seg.into();
375 let should_push = !matches!(seg, PathSegment::Empty);
376
377 if should_push {
378 visitor.push(seg);
379 }
380
381 visitor.enter_mut(node);
382 node.drive_mut(visitor);
383 visitor.exit_mut(node);
384
385 if should_push {
386 visitor.pop();
387 }
388}