1pub(crate) mod context;
8pub(crate) mod sanitize;
9pub(crate) mod validate;
10
11use crate::{error::InternalError, traits::Visitable};
12use candid::CandidType;
13use derive_more::{Deref, DerefMut};
14use serde::{Deserialize, Serialize};
15use std::{collections::BTreeMap, fmt};
16use thiserror::Error as ThisError;
17
18pub use context::{Issue, PathSegment, ScopedContext, VisitorContext};
20
21#[derive(Debug, ThisError)]
27#[error("{issues}")]
28pub struct VisitorError {
29 issues: VisitorIssues,
30}
31
32impl VisitorError {
33 #[must_use]
34 pub const fn issues(&self) -> &VisitorIssues {
35 &self.issues
36 }
37}
38
39impl From<VisitorIssues> for VisitorError {
40 fn from(issues: VisitorIssues) -> Self {
41 Self { issues }
42 }
43}
44
45impl From<VisitorError> for VisitorIssues {
46 fn from(err: VisitorError) -> Self {
47 err.issues
48 }
49}
50
51impl From<VisitorError> for InternalError {
52 fn from(err: VisitorError) -> Self {
53 Self::executor_unsupported(err.to_string())
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(crate) 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 {
130 fn enter(&mut self, node: &dyn Visitable);
131 fn exit(&mut self, node: &dyn Visitable);
132
133 fn push(&mut self, _: PathSegment) {}
134 fn pop(&mut self) {}
135}
136
137struct AdapterContext<'a> {
142 path: &'a [PathSegment],
143 issues: &'a mut VisitorIssues,
144}
145
146impl VisitorContext for AdapterContext<'_> {
147 fn add_issue(&mut self, issue: Issue) {
148 let key = render_path(self.path, None);
149 self.issues
150 .entry(key)
151 .or_default()
152 .push(issue.into_message());
153 }
154
155 fn add_issue_at(&mut self, seg: PathSegment, issue: Issue) {
156 let key = render_path(self.path, Some(seg));
157 self.issues
158 .entry(key)
159 .or_default()
160 .push(issue.into_message());
161 }
162}
163
164fn render_path(path: &[PathSegment], extra: Option<PathSegment>) -> String {
165 use std::fmt::Write;
166
167 let mut out = String::new();
168 let mut first = true;
169
170 let iter = path.iter().cloned().chain(extra);
171
172 for seg in iter {
173 match seg {
174 PathSegment::Field(s) => {
175 if !first {
176 out.push('.');
177 }
178 out.push_str(s);
179 first = false;
180 }
181 PathSegment::Index(i) => {
182 let _ = write!(out, "[{i}]");
183 first = false;
184 }
185 PathSegment::Empty => {}
186 }
187 }
188
189 out
190}
191
192pub(crate) struct VisitorAdapter<V> {
197 visitor: V,
198 path: Vec<PathSegment>,
199 issues: VisitorIssues,
200}
201
202impl<V> VisitorAdapter<V>
203where
204 V: Visitor,
205{
206 pub(crate) const fn new(visitor: V) -> Self {
207 Self {
208 visitor,
209 path: Vec::new(),
210 issues: VisitorIssues::new(),
211 }
212 }
213
214 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
215 if self.issues.is_empty() {
216 Ok(())
217 } else {
218 Err(self.issues)
219 }
220 }
221}
222
223impl<V> VisitorCore for VisitorAdapter<V>
224where
225 V: Visitor,
226{
227 fn push(&mut self, seg: PathSegment) {
228 if !matches!(seg, PathSegment::Empty) {
229 self.path.push(seg);
230 }
231 }
232
233 fn pop(&mut self) {
234 self.path.pop();
235 }
236
237 fn enter(&mut self, node: &dyn Visitable) {
238 let mut ctx = AdapterContext {
239 path: &self.path,
240 issues: &mut self.issues,
241 };
242 self.visitor.enter(node, &mut ctx);
243 }
244
245 fn exit(&mut self, node: &dyn Visitable) {
246 let mut ctx = AdapterContext {
247 path: &self.path,
248 issues: &mut self.issues,
249 };
250 self.visitor.exit(node, &mut ctx);
251 }
252}
253
254pub fn perform_visit<S: Into<PathSegment>>(
259 visitor: &mut dyn VisitorCore,
260 node: &dyn Visitable,
261 seg: S,
262) {
263 let seg = seg.into();
264 let should_push = !matches!(seg, PathSegment::Empty);
265
266 if should_push {
267 visitor.push(seg);
268 }
269
270 visitor.enter(node);
271 node.drive(visitor);
272 visitor.exit(node);
273
274 if should_push {
275 visitor.pop();
276 }
277}
278
279pub(crate) trait VisitorMut {
285 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
286 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
287}
288
289pub trait VisitorMutCore {
295 fn enter_mut(&mut self, node: &mut dyn Visitable);
296 fn exit_mut(&mut self, node: &mut dyn Visitable);
297
298 fn push(&mut self, _: PathSegment) {}
299 fn pop(&mut self) {}
300}
301
302pub(crate) struct VisitorMutAdapter<V> {
308 visitor: V,
309 path: Vec<PathSegment>,
310 issues: VisitorIssues,
311}
312
313impl<V> VisitorMutAdapter<V>
314where
315 V: VisitorMut,
316{
317 pub(crate) const fn new(visitor: V) -> Self {
318 Self {
319 visitor,
320 path: Vec::new(),
321 issues: VisitorIssues::new(),
322 }
323 }
324
325 pub(crate) 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>>(
377 visitor: &mut dyn VisitorMutCore,
378 node: &mut dyn Visitable,
379 seg: S,
380) {
381 let seg = seg.into();
382 let should_push = !matches!(seg, PathSegment::Empty);
383
384 if should_push {
385 visitor.push(seg);
386 }
387
388 visitor.enter_mut(node);
389 node.drive_mut(visitor);
390 visitor.exit_mut(node);
391
392 if should_push {
393 visitor.pop();
394 }
395}