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
144struct AdapterContext<'a> {
149 path: &'a [PathSegment],
150 issues: &'a mut VisitorIssues,
151}
152
153impl VisitorContext for AdapterContext<'_> {
154 fn add_issue(&mut self, issue: Issue) {
155 let key = render_path(self.path, None);
156 self.issues
157 .entry(key)
158 .or_default()
159 .push(issue.into_message());
160 }
161
162 fn add_issue_at(&mut self, seg: PathSegment, issue: Issue) {
163 let key = render_path(self.path, Some(seg));
164 self.issues
165 .entry(key)
166 .or_default()
167 .push(issue.into_message());
168 }
169}
170
171fn render_path(path: &[PathSegment], extra: Option<PathSegment>) -> String {
172 use std::fmt::Write;
173
174 let mut out = String::new();
175 let mut first = true;
176
177 let iter = path.iter().cloned().chain(extra);
178
179 for seg in iter {
180 match seg {
181 PathSegment::Field(s) => {
182 if !first {
183 out.push('.');
184 }
185 out.push_str(s);
186 first = false;
187 }
188 PathSegment::Index(i) => {
189 let _ = write!(out, "[{i}]");
190 first = false;
191 }
192 PathSegment::Empty => {}
193 }
194 }
195
196 out
197}
198
199pub(crate) struct VisitorAdapter<V> {
204 visitor: V,
205 path: Vec<PathSegment>,
206 issues: VisitorIssues,
207}
208
209impl<V> VisitorAdapter<V>
210where
211 V: Visitor,
212{
213 pub(crate) const fn new(visitor: V) -> Self {
214 Self {
215 visitor,
216 path: Vec::new(),
217 issues: VisitorIssues::new(),
218 }
219 }
220
221 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
222 if self.issues.is_empty() {
223 Ok(())
224 } else {
225 Err(self.issues)
226 }
227 }
228}
229
230impl<V> VisitorCore for VisitorAdapter<V>
231where
232 V: Visitor,
233{
234 fn push(&mut self, seg: PathSegment) {
235 if !matches!(seg, PathSegment::Empty) {
236 self.path.push(seg);
237 }
238 }
239
240 fn pop(&mut self) {
241 self.path.pop();
242 }
243
244 fn enter(&mut self, node: &dyn Visitable) {
245 let mut ctx = AdapterContext {
246 path: &self.path,
247 issues: &mut self.issues,
248 };
249 self.visitor.enter(node, &mut ctx);
250 }
251
252 fn exit(&mut self, node: &dyn Visitable) {
253 let mut ctx = AdapterContext {
254 path: &self.path,
255 issues: &mut self.issues,
256 };
257 self.visitor.exit(node, &mut ctx);
258 }
259}
260
261pub fn perform_visit<S: Into<PathSegment>>(
266 visitor: &mut dyn VisitorCore,
267 node: &dyn Visitable,
268 seg: S,
269) {
270 let seg = seg.into();
271 let should_push = !matches!(seg, PathSegment::Empty);
272
273 if should_push {
274 visitor.push(seg);
275 }
276
277 visitor.enter(node);
278 node.drive(visitor);
279 visitor.exit(node);
280
281 if should_push {
282 visitor.pop();
283 }
284}
285
286pub(crate) trait VisitorMut {
292 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
293 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
294}
295
296pub trait VisitorMutCore {
302 fn enter_mut(&mut self, node: &mut dyn Visitable);
303 fn exit_mut(&mut self, node: &mut dyn Visitable);
304
305 fn push(&mut self, _: PathSegment) {}
306 fn pop(&mut self) {}
307}
308
309pub(crate) struct VisitorMutAdapter<V> {
315 visitor: V,
316 path: Vec<PathSegment>,
317 issues: VisitorIssues,
318}
319
320impl<V> VisitorMutAdapter<V>
321where
322 V: VisitorMut,
323{
324 pub(crate) const fn new(visitor: V) -> Self {
325 Self {
326 visitor,
327 path: Vec::new(),
328 issues: VisitorIssues::new(),
329 }
330 }
331
332 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
333 if self.issues.is_empty() {
334 Ok(())
335 } else {
336 Err(self.issues)
337 }
338 }
339}
340
341impl<V> VisitorMutCore for VisitorMutAdapter<V>
342where
343 V: VisitorMut,
344{
345 fn push(&mut self, seg: PathSegment) {
346 if !matches!(seg, PathSegment::Empty) {
347 self.path.push(seg);
348 }
349 }
350
351 fn pop(&mut self) {
352 self.path.pop();
353 }
354
355 fn enter_mut(&mut self, node: &mut dyn Visitable) {
356 let mut ctx = AdapterContext {
357 path: &self.path,
358 issues: &mut self.issues,
359 };
360 self.visitor.enter_mut(node, &mut ctx);
361 }
362
363 fn exit_mut(&mut self, node: &mut dyn Visitable) {
364 let mut ctx = AdapterContext {
365 path: &self.path,
366 issues: &mut self.issues,
367 };
368 self.visitor.exit_mut(node, &mut ctx);
369 }
370}
371
372pub fn perform_visit_mut<S: Into<PathSegment>>(
384 visitor: &mut dyn VisitorMutCore,
385 node: &mut dyn Visitable,
386 seg: S,
387) {
388 let seg = seg.into();
389 let should_push = !matches!(seg, PathSegment::Empty);
390
391 if should_push {
392 visitor.push(seg);
393 }
394
395 visitor.enter_mut(node);
396 node.drive_mut(visitor);
397 visitor.exit_mut(node);
398
399 if should_push {
400 visitor.pop();
401 }
402}