1use std::fmt;
12
13use crate::ErrorCode;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ViolationKind {
17 Format,
19 Occurrence,
21 Property,
23 Type,
25}
26
27impl ViolationKind {
28 pub fn error_code(self) -> ErrorCode {
29 match self {
30 ViolationKind::Format => ErrorCode::FormatViolation,
31 ViolationKind::Occurrence => ErrorCode::OccurrenceConstraintViolation,
32 ViolationKind::Property => ErrorCode::PropertyConstraintViolation,
33 ViolationKind::Type => ErrorCode::TypeConstraintViolation,
34 }
35 }
36}
37
38#[derive(Debug, Clone, PartialEq)]
39pub struct Violation {
40 pub kind: ViolationKind,
41 pub message: String,
42}
43
44impl Violation {
45 pub fn new(kind: ViolationKind, message: impl Into<String>) -> Self {
46 Violation {
47 kind,
48 message: message.into(),
49 }
50 }
51
52 pub fn from_serde(e: serde_json::Error) -> Self {
55 use serde_json::error::Category;
56 let msg = e.to_string();
57 let kind = match e.classify() {
58 Category::Syntax | Category::Eof | Category::Io => ViolationKind::Format,
59 Category::Data => {
60 if msg.starts_with("missing field") {
61 ViolationKind::Occurrence
62 } else if msg.starts_with("invalid type") {
63 ViolationKind::Type
64 } else if msg.starts_with("unknown variant")
65 || msg.starts_with("invalid value")
66 || msg.starts_with("invalid length")
67 {
68 ViolationKind::Property
69 } else {
70 ViolationKind::Format
72 }
73 }
74 };
75 Violation { kind, message: msg }
76 }
77}
78
79impl fmt::Display for Violation {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 f.write_str(&self.message)
82 }
83}
84
85impl std::error::Error for Violation {}
86
87#[derive(Clone, Copy)]
89pub struct Path<'a> {
90 parent: Option<&'a Path<'a>>,
91 seg: Seg<'a>,
92}
93
94#[derive(Clone, Copy)]
95enum Seg<'a> {
96 Root,
97 Field(&'a str),
98 Index(usize),
99}
100
101impl<'a> Path<'a> {
102 pub const fn root() -> Path<'static> {
103 Path {
104 parent: None,
105 seg: Seg::Root,
106 }
107 }
108 pub fn field(&'a self, name: &'a str) -> Path<'a> {
109 Path {
110 parent: Some(self),
111 seg: Seg::Field(name),
112 }
113 }
114 pub fn index(&'a self, i: usize) -> Path<'a> {
115 Path {
116 parent: Some(self),
117 seg: Seg::Index(i),
118 }
119 }
120}
121
122impl fmt::Display for Path<'_> {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 if let Some(p) = self.parent {
125 fmt::Display::fmt(p, f)?;
126 }
127 match self.seg {
128 Seg::Root => f.write_str("$"),
129 Seg::Field(n) => write!(f, ".{n}"),
130 Seg::Index(i) => write!(f, "[{i}]"),
131 }
132 }
133}
134
135pub trait Validate {
136 fn validate(&self, path: &Path<'_>) -> Result<(), Violation>;
137}
138
139impl<T: Validate> Validate for Option<T> {
140 fn validate(&self, path: &Path<'_>) -> Result<(), Violation> {
141 match self {
142 Some(v) => v.validate(path),
143 None => Ok(()),
144 }
145 }
146}
147
148impl<T: Validate> Validate for Vec<T> {
149 fn validate(&self, path: &Path<'_>) -> Result<(), Violation> {
150 for (i, v) in self.iter().enumerate() {
151 v.validate(&path.index(i))?;
152 }
153 Ok(())
154 }
155}
156
157impl<T: Validate> Validate for Box<T> {
158 fn validate(&self, path: &Path<'_>) -> Result<(), Violation> {
159 (**self).validate(path)
160 }
161}
162
163macro_rules! no_constraints {
164 ($($t:ty),*) => { $(impl Validate for $t { #[inline] fn validate(&self, _: &Path<'_>) -> Result<(), Violation> { Ok(()) } })* };
165}
166no_constraints!(String, i64, f64, bool, crate::DateTime, serde_json::Value);
167
168#[inline]
171pub fn max_length(v: &str, max: usize, path: &Path<'_>) -> Result<(), Violation> {
172 if v.len() > max && v.chars().count() > max {
174 return Err(Violation::new(
175 ViolationKind::Property,
176 format!("{path}: string longer than {max} characters"),
177 ));
178 }
179 Ok(())
180}
181
182#[inline]
183pub fn minimum<T: PartialOrd + fmt::Display>(
184 v: T,
185 min: T,
186 path: &Path<'_>,
187) -> Result<(), Violation> {
188 if v < min {
189 return Err(Violation::new(
190 ViolationKind::Property,
191 format!("{path}: value {v} is below minimum {min}"),
192 ));
193 }
194 Ok(())
195}
196
197#[inline]
198pub fn maximum<T: PartialOrd + fmt::Display>(
199 v: T,
200 max: T,
201 path: &Path<'_>,
202) -> Result<(), Violation> {
203 if v > max {
204 return Err(Violation::new(
205 ViolationKind::Property,
206 format!("{path}: value {v} is above maximum {max}"),
207 ));
208 }
209 Ok(())
210}
211
212#[inline]
213pub fn items<T>(
214 v: &[T],
215 min: Option<usize>,
216 max: Option<usize>,
217 path: &Path<'_>,
218) -> Result<(), Violation> {
219 if let Some(min) = min
220 && v.len() < min
221 {
222 return Err(Violation::new(
223 ViolationKind::Occurrence,
224 format!("{path}: expected at least {min} items, got {}", v.len()),
225 ));
226 }
227 if let Some(max) = max
228 && v.len() > max
229 {
230 return Err(Violation::new(
231 ViolationKind::Occurrence,
232 format!("{path}: expected at most {max} items, got {}", v.len()),
233 ));
234 }
235 Ok(())
236}
237
238#[inline]
239pub fn multiple_of(v: f64, step: f64, path: &Path<'_>) -> Result<(), Violation> {
240 let q = v / step;
241 if (q - q.round()).abs() > 1e-6 {
242 return Err(Violation::new(
243 ViolationKind::Property,
244 format!("{path}: value {v} is not a multiple of {step}"),
245 ));
246 }
247 Ok(())
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn path_rendering() {
256 let r = Path::root();
257 let a = r.field("a");
258 let b = a.index(3);
259 let c = b.field("c");
260 assert_eq!(c.to_string(), "$.a[3].c");
261 }
262
263 #[test]
264 fn serde_classification() {
265 #[derive(serde::Deserialize, Debug)]
266 #[serde(deny_unknown_fields)]
267 #[allow(dead_code)]
268 struct T {
269 a: i64,
270 e: Option<E>,
271 }
272 #[derive(serde::Deserialize, Debug)]
273 enum E {
274 X,
275 }
276 let k = |s: &str| Violation::from_serde(serde_json::from_str::<T>(s).unwrap_err()).kind;
277 assert_eq!(k("{}"), ViolationKind::Occurrence);
278 assert_eq!(k(r#"{"a":"x"}"#), ViolationKind::Type);
279 assert_eq!(k(r#"{"a":1,"e":"Y"}"#), ViolationKind::Property);
280 assert_eq!(k(r#"{"a":1,"z":1}"#), ViolationKind::Format);
281 assert_eq!(k(r#"{"a":1"#), ViolationKind::Format);
282 }
283
284 #[test]
285 fn helpers() {
286 let p = Path::root();
287 assert!(max_length("ééé", 3, &p).is_ok());
288 assert!(max_length("éééé", 3, &p).is_err());
289 assert!(multiple_of(8.1, 0.1, &p).is_ok());
290 assert!(multiple_of(8.15, 0.1, &p).is_err());
291 assert!(items(&[1, 2], Some(1), Some(2), &p).is_ok());
292 assert!(items::<i32>(&[], Some(1), None, &p).is_err());
293 }
294}