1pub mod sanitize;
2pub mod validate;
3
4pub use sanitize::*;
5pub use validate::*;
6
7use crate::traits::Visitable;
8use candid::CandidType;
9use derive_more::{Deref, DerefMut};
10use serde::{Deserialize, Serialize};
11use std::{collections::BTreeMap, fmt};
12
13#[derive(
18 Clone, Debug, Default, Deserialize, Deref, DerefMut, Serialize, CandidType, Eq, PartialEq,
19)]
20pub struct VisitorIssues(BTreeMap<String, Vec<String>>);
21
22impl VisitorIssues {
23 #[must_use]
24 pub const fn new() -> Self {
25 Self(BTreeMap::new())
26 }
27}
28
29impl fmt::Display for VisitorIssues {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 let mut wrote = false;
32
33 for (path, messages) in &self.0 {
34 for message in messages {
35 if wrote {
36 writeln!(f)?;
37 }
38
39 if path.is_empty() {
40 write!(f, "{message}")?;
41 } else {
42 write!(f, "{path}: {message}")?;
43 }
44
45 wrote = true;
46 }
47 }
48
49 if !wrote {
50 write!(f, "no visitor issues")?;
51 }
52
53 Ok(())
54 }
55}
56
57impl std::error::Error for VisitorIssues {}
58
59#[derive(Clone, Debug)]
64pub enum PathSegment {
65 Empty,
66 Field(&'static str),
67 Index(usize),
68}
69
70impl From<&'static str> for PathSegment {
71 fn from(s: &'static str) -> Self {
72 Self::Field(s)
73 }
74}
75
76impl From<usize> for PathSegment {
77 fn from(i: usize) -> Self {
78 Self::Index(i)
79 }
80}
81
82impl From<Option<&'static str>> for PathSegment {
83 fn from(opt: Option<&'static str>) -> Self {
84 match opt {
85 Some(s) if !s.is_empty() => Self::Field(s),
86 _ => Self::Empty,
87 }
88 }
89}
90
91pub trait VisitorContext {
98 fn add_issue(&mut self, message: String);
99 fn add_issue_at(&mut self, seg: PathSegment, message: String);
100}
101
102pub trait Visitor {
107 fn enter(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
108 fn exit(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
109}
110
111pub trait VisitorCore {
116 fn enter(&mut self, node: &dyn Visitable);
117 fn exit(&mut self, node: &dyn Visitable);
118
119 fn push(&mut self, _: PathSegment) {}
120 fn pop(&mut self) {}
121}
122
123struct AdapterContext<'a> {
128 path: &'a [PathSegment],
129 issues: &'a mut VisitorIssues,
130}
131
132impl VisitorContext for AdapterContext<'_> {
133 fn add_issue(&mut self, message: String) {
134 let key = render_path(self.path, None);
135 self.issues.entry(key).or_default().push(message);
136 }
137
138 fn add_issue_at(&mut self, seg: PathSegment, message: String) {
139 let key = render_path(self.path, Some(seg));
140 self.issues.entry(key).or_default().push(message);
141 }
142}
143
144fn render_path(path: &[PathSegment], extra: Option<PathSegment>) -> String {
145 use std::fmt::Write;
146
147 let mut out = String::new();
148 let mut first = true;
149
150 let iter = path.iter().cloned().chain(extra);
151
152 for seg in iter {
153 match seg {
154 PathSegment::Field(s) => {
155 if !first {
156 out.push('.');
157 }
158 out.push_str(s);
159 }
160 PathSegment::Index(i) => {
161 let _ = write!(out, "[{i}]");
162 }
163 PathSegment::Empty => {}
164 }
165 first = false;
166 }
167
168 out
169}
170
171pub struct VisitorAdapter<V> {
176 visitor: V,
177 path: Vec<PathSegment>,
178 issues: VisitorIssues,
179}
180
181impl<V> VisitorAdapter<V>
182where
183 V: Visitor,
184{
185 pub const fn new(visitor: V) -> Self {
186 Self {
187 visitor,
188 path: Vec::new(),
189 issues: VisitorIssues::new(),
190 }
191 }
192
193 pub const fn issues(&self) -> &VisitorIssues {
194 &self.issues
195 }
196
197 pub fn result(self) -> Result<(), VisitorIssues> {
198 if self.issues.is_empty() {
199 Ok(())
200 } else {
201 Err(self.issues)
202 }
203 }
204}
205
206impl<V> VisitorCore for VisitorAdapter<V>
207where
208 V: Visitor,
209{
210 fn push(&mut self, seg: PathSegment) {
211 if !matches!(seg, PathSegment::Empty) {
212 self.path.push(seg);
213 }
214 }
215
216 fn pop(&mut self) {
217 self.path.pop();
218 }
219
220 fn enter(&mut self, node: &dyn Visitable) {
221 let mut ctx = AdapterContext {
222 path: &self.path,
223 issues: &mut self.issues,
224 };
225 self.visitor.enter(node, &mut ctx);
226 }
227
228 fn exit(&mut self, node: &dyn Visitable) {
229 let mut ctx = AdapterContext {
230 path: &self.path,
231 issues: &mut self.issues,
232 };
233 self.visitor.exit(node, &mut ctx);
234 }
235}
236
237pub fn perform_visit<S: Into<PathSegment>>(
242 visitor: &mut dyn VisitorCore,
243 node: &dyn Visitable,
244 seg: S,
245) {
246 let seg = seg.into();
247 let should_push = !matches!(seg, PathSegment::Empty);
248
249 if should_push {
250 visitor.push(seg);
251 }
252
253 visitor.enter(node);
254 node.drive(visitor);
255 visitor.exit(node);
256
257 if should_push {
258 visitor.pop();
259 }
260}
261
262pub trait VisitorMut {
267 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
268 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
269}
270
271pub trait VisitorMutCore {
276 fn enter_mut(&mut self, node: &mut dyn Visitable);
277 fn exit_mut(&mut self, node: &mut dyn Visitable);
278
279 fn push(&mut self, _: PathSegment) {}
280 fn pop(&mut self) {}
281}
282
283pub struct VisitorMutAdapter<V> {
288 visitor: V,
289 path: Vec<PathSegment>,
290 issues: VisitorIssues,
291}
292
293impl<V> VisitorMutAdapter<V>
294where
295 V: VisitorMut,
296{
297 pub const fn new(visitor: V) -> Self {
298 Self {
299 visitor,
300 path: Vec::new(),
301 issues: VisitorIssues::new(),
302 }
303 }
304
305 pub const fn issues(&self) -> &VisitorIssues {
306 &self.issues
307 }
308
309 pub 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>>(
354 visitor: &mut dyn VisitorMutCore,
355 node: &mut dyn Visitable,
356 seg: S,
357) {
358 let seg = seg.into();
359 let should_push = !matches!(seg, PathSegment::Empty);
360
361 if should_push {
362 visitor.push(seg);
363 }
364
365 visitor.enter_mut(node);
366 node.drive_mut(visitor);
367 visitor.exit_mut(node);
368
369 if should_push {
370 visitor.pop();
371 }
372}