icydb_core/visitor/
context.rs1use crate::sanitize::SanitizeWriteContext;
7use serde::Deserialize;
8use std::fmt;
9
10pub trait VisitorContext {
18 fn add_issue(&mut self, issue: Issue);
19 fn add_issue_at(&mut self, seg: PathSegment, issue: Issue);
20
21 fn sanitize_write_context(&self) -> Option<SanitizeWriteContext> {
22 None
23 }
24}
25
26impl dyn VisitorContext + '_ {
27 pub fn issue(&mut self, issue: impl Into<Issue>) {
28 self.add_issue(issue.into());
29 }
30
31 pub fn issue_at(&mut self, seg: PathSegment, issue: impl Into<Issue>) {
32 self.add_issue_at(seg, issue.into());
33 }
34}
35
36pub struct ScopedContext<'a> {
38 ctx: &'a mut dyn VisitorContext,
39 seg: PathSegment,
40}
41
42impl<'a> ScopedContext<'a> {
43 #[must_use]
44 pub fn new(ctx: &'a mut dyn VisitorContext, seg: PathSegment) -> Self {
45 Self { ctx, seg }
46 }
47}
48
49impl VisitorContext for ScopedContext<'_> {
50 fn add_issue(&mut self, issue: Issue) {
51 self.ctx.add_issue_at(self.seg.clone(), issue);
52 }
53
54 fn add_issue_at(&mut self, _seg: PathSegment, issue: Issue) {
55 self.ctx.add_issue_at(self.seg.clone(), issue);
56 }
57
58 fn sanitize_write_context(&self) -> Option<SanitizeWriteContext> {
59 self.ctx.sanitize_write_context()
60 }
61}
62
63#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
68pub struct Issue {
69 message: String,
70}
71
72impl Issue {
73 #[must_use]
74 pub fn new(message: impl Into<String>) -> Self {
75 Self {
76 message: message.into(),
77 }
78 }
79
80 #[must_use]
81 pub fn message(&self) -> &str {
82 &self.message
83 }
84
85 #[must_use]
86 pub fn into_message(self) -> String {
87 self.message
88 }
89}
90
91impl From<String> for Issue {
92 fn from(message: String) -> Self {
93 Self { message }
94 }
95}
96
97impl From<&str> for Issue {
98 fn from(message: &str) -> Self {
99 Self::new(message)
100 }
101}
102
103impl fmt::Display for Issue {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 f.write_str(&self.message)
106 }
107}
108
109#[derive(Clone, Debug)]
114pub enum PathSegment {
115 Empty,
116 Field(&'static str),
117 Index(usize),
118}
119
120impl From<&'static str> for PathSegment {
121 fn from(s: &'static str) -> Self {
122 Self::Field(s)
123 }
124}
125
126impl From<usize> for PathSegment {
127 fn from(i: usize) -> Self {
128 Self::Index(i)
129 }
130}
131
132impl From<Option<&'static str>> for PathSegment {
133 fn from(opt: Option<&'static str>) -> Self {
134 match opt {
135 Some(s) if !s.is_empty() => Self::Field(s),
136 _ => Self::Empty,
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::Issue;
144
145 #[test]
146 fn custom_issue_preserves_message() {
147 let issue = Issue::from("pet name is reserved");
148
149 assert_eq!(issue.message(), "pet name is reserved");
150 assert_eq!(issue.to_string(), "pet name is reserved");
151 }
152}