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