Skip to main content

icydb_core/visitor/
context.rs

1//! Module: visitor::context
2//! Responsibility: visitor issue-reporting context and path scoping helpers.
3//! Does not own: concrete sanitize/validate traversal behavior.
4//! Boundary: shared diagnostics context passed through visitor entrypoints.
5
6use crate::sanitize::SanitizeWriteContext;
7
8///
9/// VisitorContext
10///
11/// Narrow interface exposed to visitors for reporting non-fatal issues.
12/// Implemented by adapters via a short-lived context object.
13///
14
15pub trait VisitorContext {
16    fn add_issue(&mut self, issue: Issue);
17    fn add_issue_at(&mut self, seg: PathSegment, issue: Issue);
18
19    fn sanitize_write_context(&self) -> Option<SanitizeWriteContext> {
20        None
21    }
22}
23
24impl dyn VisitorContext + '_ {
25    pub fn issue(&mut self, msg: impl Into<String>) {
26        self.add_issue(Issue::new(msg));
27    }
28
29    pub fn issue_at(&mut self, seg: PathSegment, msg: impl Into<String>) {
30        self.add_issue_at(seg, Issue::new(msg));
31    }
32}
33
34/// VisitorContext that pins all issues to a single path segment.
35pub struct ScopedContext<'a> {
36    ctx: &'a mut dyn VisitorContext,
37    seg: PathSegment,
38}
39
40impl<'a> ScopedContext<'a> {
41    #[must_use]
42    pub fn new(ctx: &'a mut dyn VisitorContext, seg: PathSegment) -> Self {
43        Self { ctx, seg }
44    }
45}
46
47impl VisitorContext for ScopedContext<'_> {
48    fn add_issue(&mut self, issue: Issue) {
49        self.ctx.add_issue_at(self.seg.clone(), issue);
50    }
51
52    fn add_issue_at(&mut self, _seg: PathSegment, issue: Issue) {
53        self.ctx.add_issue_at(self.seg.clone(), issue);
54    }
55
56    fn sanitize_write_context(&self) -> Option<SanitizeWriteContext> {
57        self.ctx.sanitize_write_context()
58    }
59}
60
61///
62/// Issue
63///
64
65#[derive(Clone, Debug, Default)]
66pub struct Issue {
67    message: String,
68}
69
70impl Issue {
71    #[must_use]
72    pub fn new(message: impl Into<String>) -> Self {
73        Self {
74            message: message.into(),
75        }
76    }
77
78    #[must_use]
79    pub fn message(&self) -> &str {
80        &self.message
81    }
82
83    #[must_use]
84    pub fn into_message(self) -> String {
85        self.message
86    }
87}
88
89///
90/// PathSegment
91///
92
93#[derive(Clone, Debug)]
94pub enum PathSegment {
95    Empty,
96    Field(&'static str),
97    Index(usize),
98}
99
100impl From<&'static str> for PathSegment {
101    fn from(s: &'static str) -> Self {
102        Self::Field(s)
103    }
104}
105
106impl From<usize> for PathSegment {
107    fn from(i: usize) -> Self {
108        Self::Index(i)
109    }
110}
111
112impl From<Option<&'static str>> for PathSegment {
113    fn from(opt: Option<&'static str>) -> Self {
114        match opt {
115            Some(s) if !s.is_empty() => Self::Field(s),
116            _ => Self::Empty,
117        }
118    }
119}