Skip to main content

icydb_core/visitor/
context.rs

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