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 {
124 fn enter(&mut self, node: &dyn Visitable);
125 fn exit(&mut self, node: &dyn Visitable);
126
127 fn push(&mut self, _: PathSegment) {}
128 fn pop(&mut self) {}
129}
130
131struct AdapterContext<'a> {
136 path: &'a [PathSegment],
137 issues: &'a mut VisitorIssues,
138}
139
140impl VisitorContext for AdapterContext<'_> {
141 fn add_issue(&mut self, issue: Issue) {
142 let key = render_path(self.path, None);
143 self.issues
144 .entry(key)
145 .or_default()
146 .push(issue.into_message());
147 }
148
149 fn add_issue_at(&mut self, seg: PathSegment, issue: Issue) {
150 let key = render_path(self.path, Some(seg));
151 self.issues
152 .entry(key)
153 .or_default()
154 .push(issue.into_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 {
279 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
280 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
281}
282
283pub trait VisitorMutCore {
289 fn enter_mut(&mut self, node: &mut dyn Visitable);
290 fn exit_mut(&mut self, node: &mut dyn Visitable);
291
292 fn push(&mut self, _: PathSegment) {}
293 fn pop(&mut self) {}
294}
295
296pub(crate) struct VisitorMutAdapter<V> {
302 visitor: V,
303 path: Vec<PathSegment>,
304 issues: VisitorIssues,
305}
306
307impl<V> VisitorMutAdapter<V>
308where
309 V: VisitorMut,
310{
311 pub(crate) const fn new(visitor: V) -> Self {
312 Self {
313 visitor,
314 path: Vec::new(),
315 issues: VisitorIssues::new(),
316 }
317 }
318
319 pub(crate) fn result(self) -> Result<(), VisitorIssues> {
320 if self.issues.is_empty() {
321 Ok(())
322 } else {
323 Err(self.issues)
324 }
325 }
326}
327
328impl<V> VisitorMutCore for VisitorMutAdapter<V>
329where
330 V: VisitorMut,
331{
332 fn push(&mut self, seg: PathSegment) {
333 if !matches!(seg, PathSegment::Empty) {
334 self.path.push(seg);
335 }
336 }
337
338 fn pop(&mut self) {
339 self.path.pop();
340 }
341
342 fn enter_mut(&mut self, node: &mut dyn Visitable) {
343 let mut ctx = AdapterContext {
344 path: &self.path,
345 issues: &mut self.issues,
346 };
347 self.visitor.enter_mut(node, &mut ctx);
348 }
349
350 fn exit_mut(&mut self, node: &mut dyn Visitable) {
351 let mut ctx = AdapterContext {
352 path: &self.path,
353 issues: &mut self.issues,
354 };
355 self.visitor.exit_mut(node, &mut ctx);
356 }
357}
358
359pub fn perform_visit_mut<S: Into<PathSegment>>(
371 visitor: &mut dyn VisitorMutCore,
372 node: &mut dyn Visitable,
373 seg: S,
374) {
375 let seg = seg.into();
376 let should_push = !matches!(seg, PathSegment::Empty);
377
378 if should_push {
379 visitor.push(seg);
380 }
381
382 visitor.enter_mut(node);
383 node.drive_mut(visitor);
384 visitor.exit_mut(node);
385
386 if should_push {
387 visitor.pop();
388 }
389}