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