1use core::fmt;
27
28#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct Violation {
31 pub pointer: String,
35 pub code: ViolationCode,
37 pub message: String,
39}
40
41impl fmt::Display for Violation {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 let at = if self.pointer.is_empty() { "/" } else { &self.pointer };
44 write!(f, "{at}: {}", self.message)
45 }
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
50#[non_exhaustive]
51pub enum ViolationCode {
52 TooLong,
54 IllegalCharacter,
57 EmptyRequiredList,
59 OutOfRange,
61 Inconsistent,
63 MissingConditional,
65 Imprecise,
67}
68
69impl ViolationCode {
70 #[must_use]
72 pub const fn as_str(self) -> &'static str {
73 match self {
74 Self::TooLong => "too_long",
75 Self::IllegalCharacter => "illegal_character",
76 Self::EmptyRequiredList => "empty_required_list",
77 Self::OutOfRange => "out_of_range",
78 Self::Inconsistent => "inconsistent",
79 Self::MissingConditional => "missing_conditional",
80 Self::Imprecise => "imprecise",
81 }
82 }
83}
84
85#[derive(Clone, Debug, PartialEq, Eq, Default)]
87pub struct Violations(Vec<Violation>);
88
89impl Violations {
90 #[must_use]
92 pub fn as_slice(&self) -> &[Violation] {
93 &self.0
94 }
95
96 #[must_use]
98 pub fn is_empty(&self) -> bool {
99 self.0.is_empty()
100 }
101
102 #[must_use]
104 pub fn len(&self) -> usize {
105 self.0.len()
106 }
107
108 #[must_use]
110 pub fn into_vec(self) -> Vec<Violation> {
111 self.0
112 }
113
114 pub fn iter(&self) -> core::slice::Iter<'_, Violation> {
116 self.0.iter()
117 }
118}
119
120impl IntoIterator for Violations {
121 type Item = Violation;
122 type IntoIter = std::vec::IntoIter<Violation>;
123 fn into_iter(self) -> Self::IntoIter {
124 self.0.into_iter()
125 }
126}
127
128impl<'a> IntoIterator for &'a Violations {
129 type Item = &'a Violation;
130 type IntoIter = core::slice::Iter<'a, Violation>;
131 fn into_iter(self) -> Self::IntoIter {
132 self.iter()
133 }
134}
135
136impl fmt::Display for Violations {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 for (i, v) in self.0.iter().enumerate() {
139 if i > 0 {
140 f.write_str("; ")?;
141 }
142 write!(f, "{v}")?;
143 }
144 Ok(())
145 }
146}
147
148impl std::error::Error for Violations {}
149
150#[derive(Debug, Default)]
152pub struct Validator {
153 path: String,
154 found: Vec<Violation>,
155}
156
157impl Validator {
158 #[must_use]
160 pub fn new() -> Self {
161 Self::default()
162 }
163
164 pub fn report(&mut self, code: ViolationCode, message: impl Into<String>) {
166 self.found.push(Violation { pointer: self.path.clone(), code, message: message.into() });
167 }
168
169 pub fn report_at(&mut self, field: &str, code: ViolationCode, message: impl Into<String>) {
171 self.enter(field);
172 self.report(code, message);
173 self.leave();
174 }
175
176 pub fn enter(&mut self, segment: &str) {
178 self.path.push('/');
179 for ch in segment.chars() {
180 match ch {
181 '~' => self.path.push_str("~0"),
182 '/' => self.path.push_str("~1"),
183 c => self.path.push(c),
184 }
185 }
186 }
187
188 pub fn leave(&mut self) {
194 let cut = self.path.rfind('/').expect("leave() without a matching enter()");
195 self.path.truncate(cut);
196 }
197
198 pub fn field(&mut self, segment: &str, value: &impl Validate) {
200 self.enter(segment);
201 value.validate_in(self);
202 self.leave();
203 }
204
205 #[must_use]
207 pub fn pointer(&self) -> &str {
208 &self.path
209 }
210
211 #[must_use]
213 pub fn finish(self) -> Violations {
214 Violations(self.found)
215 }
216}
217
218pub trait Validate {
222 fn validate_in(&self, v: &mut Validator);
224
225 fn validate(&self) -> Result<(), Violations> {
231 let mut v = Validator::new();
232 self.validate_in(&mut v);
233 let found = v.finish();
234 if found.is_empty() { Ok(()) } else { Err(found) }
235 }
236}
237
238impl<T: Validate> Validate for Option<T> {
239 fn validate_in(&self, v: &mut Validator) {
240 if let Some(inner) = self {
241 inner.validate_in(v);
242 }
243 }
244}
245
246impl<T: Validate> Validate for Vec<T> {
247 fn validate_in(&self, v: &mut Validator) {
248 for (i, item) in self.iter().enumerate() {
249 v.enter(&i.to_string());
250 item.validate_in(v);
251 v.leave();
252 }
253 }
254}
255
256impl<T: Validate> Validate for Box<T> {
257 fn validate_in(&self, v: &mut Validator) {
258 T::validate_in(self, v);
259 }
260}
261
262macro_rules! impl_validate_noop {
264 ($($t:ty),* $(,)?) => {
265 $(impl Validate for $t {
266 fn validate_in(&self, _v: &mut Validator) {}
267 })*
268 };
269}
270
271impl_validate_noop!(bool, i8, i16, i32, i64, u8, u16, u32, u64, usize, String, serde_json::Value);
272
273macro_rules! validate_fields {
280 ($self:ident, $v:ident, $($field:ident $(as $wire:literal)?),* $(,)?) => {
281 $( $v.field(validate_fields!(@wire $field $(, $wire)?), &$self.$field); )*
282 };
283 (@wire $field:ident) => { stringify!($field) };
284 (@wire $field:ident, $wire:literal) => { $wire };
285}
286
287pub(crate) use validate_fields;
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 struct Leaf(bool);
294 impl Validate for Leaf {
295 fn validate_in(&self, v: &mut Validator) {
296 if !self.0 {
297 v.report(ViolationCode::OutOfRange, "leaf is false");
298 }
299 }
300 }
301
302 #[test]
303 fn pointer_tracks_nesting_and_escapes_rfc6901() {
304 let mut v = Validator::new();
305 v.enter("a/b");
306 v.enter("c~d");
307 v.report(ViolationCode::TooLong, "boom");
308 v.leave();
309 v.leave();
310 let found = v.finish();
311 assert_eq!(found.as_slice()[0].pointer, "/a~1b/c~0d");
312 }
313
314 #[test]
315 fn vec_and_option_are_walked_with_indices() {
316 let value = vec![Leaf(true), Leaf(false), Leaf(false)];
317 let err = value.validate().unwrap_err();
318 assert_eq!(err.len(), 2);
319 assert_eq!(err.as_slice()[0].pointer, "/1");
320 assert_eq!(err.as_slice()[1].pointer, "/2");
321 assert!(Some(Leaf(true)).validate().is_ok());
322 assert!(Option::<Leaf>::None.validate().is_ok());
323 }
324}