1use crate::context::{join_path, PathPart};
4use crate::node::Kind;
5use crate::value::{value_kind, value_to_string, Map, Value};
6use std::fmt;
7
8pub const MARK_OBJECT_REQUIRED: i64 = 1010;
10pub const MARK_OBJECT_TYPE: i64 = 1020;
11pub const MARK_ARRAY_REQUIRED: i64 = 1030;
12pub const MARK_ARRAY_TYPE: i64 = 1040;
13pub const MARK_CHECK_TYPE: i64 = 1045;
14pub const MARK_SCALAR_TYPE: i64 = 1050;
15pub const MARK_SCALAR_REQUIRED: i64 = 1060;
16pub const MARK_NEVER: i64 = 1070;
17pub const MARK_REGEXP: i64 = 1045;
18pub const MARK_UNDEF_REQUIRED: i64 = 1080;
19pub const MARK_ARRAY_CLOSED: i64 = 1090;
20pub const MARK_OBJECT_CLOSED: i64 = 1100;
21pub const MARK_CUSTOM_CHECK_ERR: i64 = 2010;
22pub const MARK_CUSTOM_CHECK_TEXT: i64 = 4000;
23
24pub const WHY_TYPE: &str = "type";
26pub const WHY_REQUIRED: &str = "required";
27pub const WHY_CLOSED: &str = "closed";
28pub const WHY_CHECK: &str = "check";
29pub const WHY_NEVER: &str = "never";
30pub const WHY_REGEXP: &str = "regexp";
31
32#[derive(Clone, Debug, Default)]
34pub struct FieldError {
35 pub path: String,
37 pub path_arr: Vec<PathPart>,
39 pub key: String,
41 pub kind: Kind,
43 pub value: Value,
45 pub why: String,
46 pub mark: i64,
47 pub text: String,
49 pub args: Map,
51 pub check: String,
53 pub(crate) parent_arr: bool,
54 pub(crate) absent: bool,
55 pub(crate) regexp_src: String,
56 pub(crate) plural: bool,
57 pub(crate) terse: bool,
58}
59
60impl fmt::Display for FieldError {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 if !self.text.is_empty() {
63 return f.write_str(&self.text);
64 }
65 if self.path.is_empty() {
66 f.write_str(&self.why)
67 } else {
68 write!(f, "{}: {}", self.path, self.why)
69 }
70 }
71}
72
73#[derive(Clone, Debug, Default)]
75pub struct ValidationError {
76 pub issues: Vec<FieldError>,
77 pub(crate) terse: bool,
79 pub(crate) n: usize,
80}
81
82impl ValidationError {
83 pub(crate) fn add(&mut self, err: FieldError) {
84 if self.terse {
85 self.n += 1;
86 return;
87 }
88 self.issues.push(err);
89 }
90
91 pub fn has_any(&self) -> bool {
92 self.n > 0 || !self.issues.is_empty()
93 }
94
95 pub(crate) fn count(&self) -> usize {
97 self.n + self.issues.len()
98 }
99}
100
101impl fmt::Display for ValidationError {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 let parts: Vec<String> = self.issues.iter().map(|e| e.to_string()).collect();
104 f.write_str(&parts.join("\n"))
105 }
106}
107
108impl std::error::Error for ValidationError {}
109
110pub(crate) struct At<'a> {
112 pub path: &'a [PathPart],
113 pub key: &'a str,
114 pub kind: Kind,
115 pub value: &'a Value,
116 pub parent_arr: bool,
117 pub absent: bool,
118 pub check: &'a str,
119 pub regexp_src: Option<String>,
120 pub terse: bool,
121}
122
123pub(crate) fn make_err(at: &At<'_>, why: &str, mark: i64, text: &str) -> FieldError {
126 let why = if why.is_empty() { WHY_CHECK } else { why };
127 let mark = if mark == 0 {
128 MARK_CUSTOM_CHECK_TEXT
129 } else {
130 mark
131 };
132 if at.terse {
133 return FieldError {
134 key: at.key.to_string(),
135 kind: at.kind,
136 value: at.value.clone(),
137 why: why.to_string(),
138 mark,
139 check: at.check.to_string(),
140 terse: true,
141 ..Default::default()
142 };
143 }
144 let path = join_path(at.path);
145 let mut err = FieldError {
146 path,
147 path_arr: at.path.to_vec(),
148 key: at.key.to_string(),
149 kind: at.kind,
150 value: at.value.clone(),
151 why: why.to_string(),
152 mark,
153 check: at.check.to_string(),
154 parent_arr: at.parent_arr,
155 absent: at.absent,
156 regexp_src: at.regexp_src.clone().unwrap_or_default(),
157 ..Default::default()
158 };
159 err.text = if text.is_empty() {
160 default_err_text(&err)
161 } else {
162 expand_err_text(text, &err.path, at.value, at.absent)
163 };
164 err
165}
166
167pub(crate) fn expand_err_text(text: &str, path: &str, val: &Value, absent: bool) -> String {
170 let valstr = if absent {
171 "undefined".to_string()
172 } else {
173 value_to_string(val)
174 };
175 text.replace("$PATH", path).replace("$VALUE", &valstr)
176}
177
178pub(crate) fn default_err_text(e: &FieldError) -> String {
180 let mut valstr = value_to_string(&e.value);
181 let mut valkind = value_kind(&e.value);
182 if e.absent {
183 valstr = "undefined".to_string();
184 valkind = "value";
185 }
186 let propkind = if e.parent_arr || valstr.starts_with('[') {
188 "index"
189 } else {
190 "property"
191 };
192 let path_part = if e.path.is_empty() {
193 String::new()
194 } else {
195 format!("{} \"{}\" with ", propkind, e.path)
196 };
197 let head = format!(
198 "Validation failed for {}{} \"{}\"",
199 path_part, valkind, valstr
200 );
201 match e.why.as_str() {
202 WHY_TYPE => format!(
203 "{} because the {} is not of type {}.",
204 head, valkind, e.kind
205 ),
206 WHY_REQUIRED => {
207 if e.absent && !e.path.is_empty() {
209 let noun = if propkind == "index" {
210 "element"
211 } else {
212 "property"
213 };
214 return format!(
215 "Validation failed for {} \"{}\" because the {} is missing.",
216 propkind, e.path, noun
217 );
218 }
219 if matches!(e.value, Value::Str(ref s) if s.is_empty()) {
220 format!("{} because an empty string is not allowed.", head)
221 } else if e.value.is_null() || e.absent {
222 format!("{} because the value is required.", head)
223 } else {
224 format!("{} because the {} is required.", head, valkind)
225 }
226 }
227 WHY_CLOSED => {
228 let (noun, verb) = if e.plural {
229 ("properties", "are")
230 } else {
231 (propkind, "is")
232 };
233 format!(
234 "{} because the {} \"{}\" {} not allowed.",
235 head, noun, e.key, verb
236 )
237 }
238 WHY_NEVER => format!("{} because no value is allowed.", head),
239 WHY_REGEXP => format!(
240 "{} because the {} did not match {}.",
241 head, valkind, e.regexp_src
242 ),
243 _ => {
244 let name = if e.check.is_empty() {
245 e.why.as_str()
246 } else {
247 e.check.as_str()
248 };
249 format!("{} because check \"{}\" failed.", head, name)
250 }
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 fn at<'a>(path: &'a [PathPart], value: &'a Value, absent: bool) -> At<'a> {
259 At {
260 path,
261 key: "k",
262 kind: Kind::String,
263 value,
264 parent_arr: false,
265 absent,
266 check: "",
267 regexp_src: None,
268 terse: false,
269 }
270 }
271
272 #[test]
273 fn texts() {
274 let p = vec![PathPart::Key("a".into())];
275 let v = Value::from(1);
276 let e = make_err(&at(&p, &v, false), WHY_TYPE, MARK_SCALAR_TYPE, "");
277 assert_eq!(e.text, "Validation failed for property \"a\" with number \"1\" because the number is not of type string.");
278 assert_eq!(e.to_string(), e.text);
279 let e = make_err(&at(&p, &Value::Undefined, true), WHY_REQUIRED, 0, "");
280 assert_eq!(
281 e.text,
282 "Validation failed for property \"a\" because the property is missing."
283 );
284 assert_eq!(e.mark, MARK_CUSTOM_CHECK_TEXT);
285 let e = make_err(&at(&[], &Value::Undefined, true), WHY_REQUIRED, 1, "");
286 assert_eq!(
287 e.text,
288 "Validation failed for value \"undefined\" because the value is required."
289 );
290 let e = make_err(&at(&p, &Value::from(""), false), WHY_REQUIRED, 1, "");
291 assert_eq!(e.text, "Validation failed for property \"a\" with string \"\" because an empty string is not allowed.");
292 let e = make_err(&at(&p, &Value::Null, false), WHY_REQUIRED, 1, "");
293 assert_eq!(e.text, "Validation failed for property \"a\" with value \"null\" because the value is required.");
294 let e = make_err(&at(&p, &Value::from(2), false), WHY_REQUIRED, 1, "");
295 assert_eq!(e.text, "Validation failed for property \"a\" with number \"2\" because the number is required.");
296 let mut e = make_err(&at(&p, &v, false), WHY_CLOSED, 1, "");
297 e.plural = true;
298 e.key = "x, y".into();
299 assert_eq!(default_err_text(&e), "Validation failed for property \"a\" with number \"1\" because the properties \"x, y\" are not allowed.");
300 e.plural = false;
301 assert_eq!(default_err_text(&e), "Validation failed for property \"a\" with number \"1\" because the property \"x, y\" is not allowed.");
302 let e = make_err(&at(&p, &v, false), WHY_NEVER, 1, "");
303 assert_eq!(
304 e.text,
305 "Validation failed for property \"a\" with number \"1\" because no value is allowed."
306 );
307 let mut a = at(&p, &v, false);
308 a.regexp_src = Some("/x/".into());
309 let e = make_err(&a, WHY_REGEXP, 1, "");
310 assert_eq!(e.text, "Validation failed for property \"a\" with number \"1\" because the number did not match /x/.");
311 let mut a = at(&p, &v, false);
312 a.check = "Min";
313 let e = make_err(&a, "", 1, "");
314 assert_eq!(
315 e.text,
316 "Validation failed for property \"a\" with number \"1\" because check \"Min\" failed."
317 );
318 let e = make_err(&at(&p, &v, false), "why", 1, "");
319 assert_eq!(
320 e.text,
321 "Validation failed for property \"a\" with number \"1\" because check \"why\" failed."
322 );
323 let e = make_err(
324 &at(&p, &v, false),
325 "why",
326 1,
327 "Value \"$VALUE\" at \"$PATH\" is bad.",
328 );
329 assert_eq!(e.text, "Value \"1\" at \"a\" is bad.");
330 assert_eq!(
331 expand_err_text("$VALUE", "", &Value::Null, true),
332 "undefined"
333 );
334 let arr = Value::Arr(vec![Value::from(1)]);
335 let e = make_err(&at(&p, &arr, false), WHY_TYPE, 1, "");
336 assert_eq!(e.text, "Validation failed for index \"a\" with array \"[1]\" because the array is not of type string.");
337 let mut a = at(&p, &v, false);
338 a.parent_arr = true;
339 a.absent = true;
340 let e = make_err(&a, WHY_REQUIRED, 1, "");
341 assert_eq!(
342 e.text,
343 "Validation failed for index \"a\" because the element is missing."
344 );
345 let mut a = at(&p, &v, false);
346 a.terse = true;
347 let e = make_err(&a, WHY_TYPE, 1, "");
348 assert!(e.terse && e.text.is_empty() && e.path.is_empty());
349 let plain = FieldError {
350 path: "a".into(),
351 why: "w".into(),
352 ..Default::default()
353 };
354 assert_eq!(plain.to_string(), "a: w");
355 let plain = FieldError {
356 why: "w".into(),
357 ..Default::default()
358 };
359 assert_eq!(plain.to_string(), "w");
360 }
361
362 #[test]
363 fn collector() {
364 let mut v = ValidationError::default();
365 assert!(!v.has_any());
366 v.add(FieldError {
367 text: "a".into(),
368 ..Default::default()
369 });
370 v.add(FieldError {
371 text: "b".into(),
372 ..Default::default()
373 });
374 assert_eq!(v.to_string(), "a\nb");
375 let mut t = ValidationError {
376 terse: true,
377 ..Default::default()
378 };
379 t.add(FieldError::default());
380 t.add(FieldError::default());
381 assert!(t.has_any() && t.issues.is_empty() && t.n == 2);
382 let _: &dyn std::error::Error = &v;
383 }
384}