1pub mod context;
2pub mod sanitize;
3pub mod validate;
4
5pub use context::*;
6pub use sanitize::*;
7pub use validate::*;
8
9use crate::traits::Visitable;
10use candid::CandidType;
11use derive_more::{Deref, DerefMut};
12use serde::{Deserialize, Serialize};
13use std::{collections::BTreeMap, fmt};
14
15#[derive(
20 Clone, Debug, Default, Deserialize, Deref, DerefMut, Serialize, CandidType, Eq, PartialEq,
21)]
22pub struct VisitorIssues(BTreeMap<String, Vec<String>>);
23
24impl VisitorIssues {
25 #[must_use]
26 pub const fn new() -> Self {
27 Self(BTreeMap::new())
28 }
29}
30
31impl From<BTreeMap<String, Vec<String>>> for VisitorIssues {
32 fn from(map: BTreeMap<String, Vec<String>>) -> Self {
33 Self(map)
34 }
35}
36
37impl fmt::Display for VisitorIssues {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 let mut wrote = false;
40
41 for (path, messages) in &self.0 {
42 for message in messages {
43 if wrote {
44 writeln!(f)?;
45 }
46
47 if path.is_empty() {
48 write!(f, "{message}")?;
49 } else {
50 write!(f, "{path}: {message}")?;
51 }
52
53 wrote = true;
54 }
55 }
56
57 if !wrote {
58 write!(f, "no visitor issues")?;
59 }
60
61 Ok(())
62 }
63}
64
65impl std::error::Error for VisitorIssues {}
66
67pub trait Visitor {
73 fn enter(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
74 fn exit(&mut self, node: &dyn Visitable, ctx: &mut dyn VisitorContext);
75}
76
77pub trait VisitorCore {
82 fn enter(&mut self, node: &dyn Visitable);
83 fn exit(&mut self, node: &dyn Visitable);
84
85 fn push(&mut self, _: PathSegment) {}
86 fn pop(&mut self) {}
87}
88
89struct AdapterContext<'a> {
94 path: &'a [PathSegment],
95 issues: &'a mut VisitorIssues,
96}
97
98impl VisitorContext for AdapterContext<'_> {
99 fn add_issue(&mut self, issue: Issue) {
100 let key = render_path(self.path, None);
101 self.issues.entry(key).or_default().push(issue.message);
102 }
103
104 fn add_issue_at(&mut self, seg: PathSegment, issue: Issue) {
105 let key = render_path(self.path, Some(seg));
106 self.issues.entry(key).or_default().push(issue.message);
107 }
108}
109
110fn render_path(path: &[PathSegment], extra: Option<PathSegment>) -> String {
111 use std::fmt::Write;
112
113 let mut out = String::new();
114 let mut first = true;
115
116 let iter = path.iter().cloned().chain(extra);
117
118 for seg in iter {
119 match seg {
120 PathSegment::Field(s) => {
121 if !first {
122 out.push('.');
123 }
124 out.push_str(s);
125 first = false;
126 }
127 PathSegment::Index(i) => {
128 let _ = write!(out, "[{i}]");
129 first = false;
130 }
131 PathSegment::Empty => {}
132 }
133 }
134
135 out
136}
137
138pub struct VisitorAdapter<V> {
143 visitor: V,
144 path: Vec<PathSegment>,
145 issues: VisitorIssues,
146}
147
148impl<V> VisitorAdapter<V>
149where
150 V: Visitor,
151{
152 pub const fn new(visitor: V) -> Self {
153 Self {
154 visitor,
155 path: Vec::new(),
156 issues: VisitorIssues::new(),
157 }
158 }
159
160 pub const fn issues(&self) -> &VisitorIssues {
161 &self.issues
162 }
163
164 pub fn result(self) -> Result<(), VisitorIssues> {
165 if self.issues.is_empty() {
166 Ok(())
167 } else {
168 Err(self.issues)
169 }
170 }
171}
172
173impl<V> VisitorCore for VisitorAdapter<V>
174where
175 V: Visitor,
176{
177 fn push(&mut self, seg: PathSegment) {
178 if !matches!(seg, PathSegment::Empty) {
179 self.path.push(seg);
180 }
181 }
182
183 fn pop(&mut self) {
184 self.path.pop();
185 }
186
187 fn enter(&mut self, node: &dyn Visitable) {
188 let mut ctx = AdapterContext {
189 path: &self.path,
190 issues: &mut self.issues,
191 };
192 self.visitor.enter(node, &mut ctx);
193 }
194
195 fn exit(&mut self, node: &dyn Visitable) {
196 let mut ctx = AdapterContext {
197 path: &self.path,
198 issues: &mut self.issues,
199 };
200 self.visitor.exit(node, &mut ctx);
201 }
202}
203
204pub fn perform_visit<S: Into<PathSegment>>(
209 visitor: &mut dyn VisitorCore,
210 node: &dyn Visitable,
211 seg: S,
212) {
213 let seg = seg.into();
214 let should_push = !matches!(seg, PathSegment::Empty);
215
216 if should_push {
217 visitor.push(seg);
218 }
219
220 visitor.enter(node);
221 node.drive(visitor);
222 visitor.exit(node);
223
224 if should_push {
225 visitor.pop();
226 }
227}
228
229pub trait VisitorMut {
234 fn enter_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
235 fn exit_mut(&mut self, node: &mut dyn Visitable, ctx: &mut dyn VisitorContext);
236}
237
238pub trait VisitorMutCore {
243 fn enter_mut(&mut self, node: &mut dyn Visitable);
244 fn exit_mut(&mut self, node: &mut dyn Visitable);
245
246 fn push(&mut self, _: PathSegment) {}
247 fn pop(&mut self) {}
248}
249
250pub struct VisitorMutAdapter<V> {
255 visitor: V,
256 path: Vec<PathSegment>,
257 issues: VisitorIssues,
258}
259
260impl<V> VisitorMutAdapter<V>
261where
262 V: VisitorMut,
263{
264 pub const fn new(visitor: V) -> Self {
265 Self {
266 visitor,
267 path: Vec::new(),
268 issues: VisitorIssues::new(),
269 }
270 }
271
272 pub const fn issues(&self) -> &VisitorIssues {
273 &self.issues
274 }
275
276 pub fn result(self) -> Result<(), VisitorIssues> {
277 if self.issues.is_empty() {
278 Ok(())
279 } else {
280 Err(self.issues)
281 }
282 }
283}
284
285impl<V> VisitorMutCore for VisitorMutAdapter<V>
286where
287 V: VisitorMut,
288{
289 fn push(&mut self, seg: PathSegment) {
290 if !matches!(seg, PathSegment::Empty) {
291 self.path.push(seg);
292 }
293 }
294
295 fn pop(&mut self) {
296 self.path.pop();
297 }
298
299 fn enter_mut(&mut self, node: &mut dyn Visitable) {
300 let mut ctx = AdapterContext {
301 path: &self.path,
302 issues: &mut self.issues,
303 };
304 self.visitor.enter_mut(node, &mut ctx);
305 }
306
307 fn exit_mut(&mut self, node: &mut dyn Visitable) {
308 let mut ctx = AdapterContext {
309 path: &self.path,
310 issues: &mut self.issues,
311 };
312 self.visitor.exit_mut(node, &mut ctx);
313 }
314}
315
316pub fn perform_visit_mut<S: Into<PathSegment>>(
321 visitor: &mut dyn VisitorMutCore,
322 node: &mut dyn Visitable,
323 seg: S,
324) {
325 let seg = seg.into();
326 let should_push = !matches!(seg, PathSegment::Empty);
327
328 if should_push {
329 visitor.push(seg);
330 }
331
332 visitor.enter_mut(node);
333 node.drive_mut(visitor);
334 visitor.exit_mut(node);
335
336 if should_push {
337 visitor.pop();
338 }
339}