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