icydb_core/visitor/
mod.rs

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///
16/// VisitorIssues
17///
18
19#[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
67///
68/// Visitor
69/// (immutable)
70///
71
72pub 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
77// ============================================================================
78// VisitorCore (object-safe traversal)
79// ============================================================================
80
81pub 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
89// ============================================================================
90// Internal adapter context (fixes borrow checker)
91// ============================================================================
92
93struct 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
138// ============================================================================
139// VisitorAdapter (immutable)
140// ============================================================================
141
142pub 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
204// ============================================================================
205// Traversal (immutable)
206// ============================================================================
207
208pub 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
229// ============================================================================
230// VisitorMut (mutable)
231// ============================================================================
232
233pub 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
238// ============================================================================
239// VisitorMutCore
240// ============================================================================
241
242pub 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
250// ============================================================================
251// VisitorMutAdapter
252// ============================================================================
253
254pub 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
316// ============================================================================
317// Traversal (mutable)
318// ============================================================================
319
320pub 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}