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.entry(key).or_default().push(issue.message);
143 }
144
145 fn add_issue_at(&mut self, seg: PathSegment, issue: Issue) {
146 let key = render_path(self.path, Some(seg));
147 self.issues.entry(key).or_default().push(issue.message);
148 }
149}
150
151fn render_path(path: &[PathSegment], extra: Option<PathSegment>) -> String {
152 use std::fmt::Write;
153
154 let mut out = String::new();
155 let mut first = true;
156
157 let iter = path.iter().cloned().chain(extra);
158
159 for seg in iter {
160 match seg {
161 PathSegment::Field(s) => {
162 if !first {
163 out.push('.');
164 }
165 out.push_str(s);
166 first = false;
167 }
168 PathSegment::Index(i) => {
169 let _ = write!(out, "[{i}]");
170 first = false;
171 }
172 PathSegment::Empty => {}
173 }
174 }
175
176 out
177}
178
179pub(crate) struct VisitorAdapter<V> {
184 visitor: V,
185 path: Vec<PathSegment>,
186 issues: VisitorIssues,
187}
188
189impl<V> VisitorAdapter<V>
190where
191 V: Visitor,
192{
193 pub(crate) const fn new(visitor: V) -> Self {
194 Self {
195 visitor,
196 path: Vec::new(),
197 issues: VisitorIssues::new(),
198 }
199 }
200
201 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
202 if self.issues.is_empty() {
203 Ok(())
204 } else {
205 Err(self.issues)
206 }
207 }
208}
209
210impl<V> VisitorCore for VisitorAdapter<V>
211where
212 V: Visitor,
213{
214 fn push(&mut self, seg: PathSegment) {
215 if !matches!(seg, PathSegment::Empty) {
216 self.path.push(seg);
217 }
218 }
219
220 fn pop(&mut self) {
221 self.path.pop();
222 }
223
224 fn enter(&mut self, node: &dyn Visitable) {
225 let mut ctx = AdapterContext {
226 path: &self.path,
227 issues: &mut self.issues,
228 };
229 self.visitor.enter(node, &mut ctx);
230 }
231
232 fn exit(&mut self, node: &dyn Visitable) {
233 let mut ctx = AdapterContext {
234 path: &self.path,
235 issues: &mut self.issues,
236 };
237 self.visitor.exit(node, &mut ctx);
238 }
239}
240
241pub fn perform_visit<S: Into<PathSegment>>(
246 visitor: &mut dyn VisitorCore,
247 node: &dyn Visitable,
248 seg: S,
249) {
250 let seg = seg.into();
251 let should_push = !matches!(seg, PathSegment::Empty);
252
253 if should_push {
254 visitor.push(seg);
255 }
256
257 visitor.enter(node);
258 node.drive(visitor);
259 visitor.exit(node);
260
261 if should_push {
262 visitor.pop();
263 }
264}
265
266pub(crate) trait VisitorMut {
271 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
272 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
273}
274
275pub trait VisitorMutCore {
280 fn enter_mut(&mut self, node: &mut dyn Visitable);
281 fn exit_mut(&mut self, node: &mut dyn Visitable);
282
283 fn push(&mut self, _: PathSegment) {}
284 fn pop(&mut self) {}
285}
286
287pub(crate) struct VisitorMutAdapter<V> {
292 visitor: V,
293 path: Vec<PathSegment>,
294 issues: VisitorIssues,
295}
296
297impl<V> VisitorMutAdapter<V>
298where
299 V: VisitorMut,
300{
301 pub(crate) const fn new(visitor: V) -> Self {
302 Self {
303 visitor,
304 path: Vec::new(),
305 issues: VisitorIssues::new(),
306 }
307 }
308
309 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
310 if self.issues.is_empty() {
311 Ok(())
312 } else {
313 Err(self.issues)
314 }
315 }
316}
317
318impl<V> VisitorMutCore for VisitorMutAdapter<V>
319where
320 V: VisitorMut,
321{
322 fn push(&mut self, seg: PathSegment) {
323 if !matches!(seg, PathSegment::Empty) {
324 self.path.push(seg);
325 }
326 }
327
328 fn pop(&mut self) {
329 self.path.pop();
330 }
331
332 fn enter_mut(&mut self, node: &mut dyn Visitable) {
333 let mut ctx = AdapterContext {
334 path: &self.path,
335 issues: &mut self.issues,
336 };
337 self.visitor.enter_mut(node, &mut ctx);
338 }
339
340 fn exit_mut(&mut self, node: &mut dyn Visitable) {
341 let mut ctx = AdapterContext {
342 path: &self.path,
343 issues: &mut self.issues,
344 };
345 self.visitor.exit_mut(node, &mut ctx);
346 }
347}
348
349pub fn perform_visit_mut<S: Into<PathSegment>>(
361 visitor: &mut dyn VisitorMutCore,
362 node: &mut dyn Visitable,
363 seg: S,
364) {
365 let seg = seg.into();
366 let should_push = !matches!(seg, PathSegment::Empty);
367
368 if should_push {
369 visitor.push(seg);
370 }
371
372 visitor.enter_mut(node);
373 node.drive_mut(visitor);
374 visitor.exit_mut(node);
375
376 if should_push {
377 visitor.pop();
378 }
379}