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 {
136 kind: at.kind,
137 mark,
138 terse: true,
139 ..Default::default()
140 };
141 }
142 let path = join_path(at.path);
143 let mut err = FieldError {
144 path,
145 path_arr: at.path.to_vec(),
146 key: at.key.to_string(),
147 kind: at.kind,
148 value: at.value.clone(),
149 why: why.to_string(),
150 mark,
151 check: at.check.to_string(),
152 parent_arr: at.parent_arr,
153 absent: at.absent,
154 regexp_src: at.regexp_src.clone().unwrap_or_default(),
155 ..Default::default()
156 };
157 err.text = if text.is_empty() {
158 default_err_text(&err)
159 } else {
160 expand_err_text(text, &err.path, at.value, at.absent)
161 };
162 err
163}
164
165pub(crate) fn expand_err_text(text: &str, path: &str, val: &Value, absent: bool) -> String {
168 let valstr = if absent {
169 "undefined".to_string()
170 } else {
171 value_to_string(val)
172 };
173 text.replace("$PATH", path).replace("$VALUE", &valstr)
174}
175
176pub(crate) fn default_err_text(e: &FieldError) -> String {
178 let (valstr, valkind) = if e.absent {
179 ("undefined".to_string(), "value")
180 } else {
181 (value_to_string(&e.value), value_kind(&e.value))
182 };
183 let propkind = if e.parent_arr || valstr.starts_with('[') {
185 "index"
186 } else {
187 "property"
188 };
189 if e.why == WHY_REQUIRED && e.absent && !e.path.is_empty() {
191 let noun = if propkind == "index" {
192 "element"
193 } else {
194 "property"
195 };
196 return format!(
197 "Validation failed for {} \"{}\" because the {} is missing.",
198 propkind, e.path, noun
199 );
200 }
201 let mut out = String::with_capacity(96 + e.path.len() + valstr.len() + e.key.len());
203 out.push_str("Validation failed for ");
204 if !e.path.is_empty() {
205 out.push_str(propkind);
206 out.push_str(" \"");
207 out.push_str(&e.path);
208 out.push_str("\" with ");
209 }
210 out.push_str(valkind);
211 out.push_str(" \"");
212 out.push_str(&valstr);
213 out.push_str("\" because ");
214 match e.why.as_str() {
215 WHY_TYPE => {
216 out.push_str("the ");
217 out.push_str(valkind);
218 out.push_str(" is not of type ");
219 out.push_str(e.kind.as_str());
220 out.push('.');
221 }
222 WHY_REQUIRED => {
223 if matches!(e.value, Value::Str(ref s) if s.is_empty()) {
224 out.push_str("an empty string is not allowed.");
225 } else if e.value.is_null() || e.absent {
226 out.push_str("the value is required.");
227 } else {
228 out.push_str("the ");
229 out.push_str(valkind);
230 out.push_str(" is required.");
231 }
232 }
233 WHY_CLOSED => {
234 let (noun, verb) = if e.plural {
235 ("properties", "are")
236 } else {
237 (propkind, "is")
238 };
239 out.push_str("the ");
240 out.push_str(noun);
241 out.push_str(" \"");
242 out.push_str(&e.key);
243 out.push_str("\" ");
244 out.push_str(verb);
245 out.push_str(" not allowed.");
246 }
247 WHY_NEVER => out.push_str("no value is allowed."),
248 WHY_REGEXP => {
249 out.push_str("the ");
250 out.push_str(valkind);
251 out.push_str(" did not match ");
252 out.push_str(&e.regexp_src);
253 out.push('.');
254 }
255 _ => {
256 let name = if e.check.is_empty() {
257 e.why.as_str()
258 } else {
259 e.check.as_str()
260 };
261 out.push_str("check \"");
262 out.push_str(name);
263 out.push_str("\" failed.");
264 }
265 }
266 out
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 fn at<'a>(path: &'a [PathPart], value: &'a Value, absent: bool) -> At<'a> {
274 At {
275 path,
276 key: "k",
277 kind: Kind::String,
278 value,
279 parent_arr: false,
280 absent,
281 check: "",
282 regexp_src: None,
283 terse: false,
284 }
285 }
286
287 #[test]
288 fn texts() {
289 let p = vec![PathPart::Key("a".into())];
290 let v = Value::from(1);
291 let e = make_err(&at(&p, &v, false), WHY_TYPE, MARK_SCALAR_TYPE, "");
292 assert_eq!(e.text, "Validation failed for property \"a\" with number \"1\" because the number is not of type string.");
293 assert_eq!(e.to_string(), e.text);
294 let e = make_err(&at(&p, &Value::Undefined, true), WHY_REQUIRED, 0, "");
295 assert_eq!(
296 e.text,
297 "Validation failed for property \"a\" because the property is missing."
298 );
299 assert_eq!(e.mark, MARK_CUSTOM_CHECK_TEXT);
300 let e = make_err(&at(&[], &Value::Undefined, true), WHY_REQUIRED, 1, "");
301 assert_eq!(
302 e.text,
303 "Validation failed for value \"undefined\" because the value is required."
304 );
305 let e = make_err(&at(&p, &Value::from(""), false), WHY_REQUIRED, 1, "");
306 assert_eq!(e.text, "Validation failed for property \"a\" with string \"\" because an empty string is not allowed.");
307 let e = make_err(&at(&p, &Value::Null, false), WHY_REQUIRED, 1, "");
308 assert_eq!(e.text, "Validation failed for property \"a\" with value \"null\" because the value is required.");
309 let e = make_err(&at(&p, &Value::from(2), false), WHY_REQUIRED, 1, "");
310 assert_eq!(e.text, "Validation failed for property \"a\" with number \"2\" because the number is required.");
311 let mut e = make_err(&at(&p, &v, false), WHY_CLOSED, 1, "");
312 e.plural = true;
313 e.key = "x, y".into();
314 assert_eq!(default_err_text(&e), "Validation failed for property \"a\" with number \"1\" because the properties \"x, y\" are not allowed.");
315 e.plural = false;
316 assert_eq!(default_err_text(&e), "Validation failed for property \"a\" with number \"1\" because the property \"x, y\" is not allowed.");
317 let e = make_err(&at(&p, &v, false), WHY_NEVER, 1, "");
318 assert_eq!(
319 e.text,
320 "Validation failed for property \"a\" with number \"1\" because no value is allowed."
321 );
322 let mut a = at(&p, &v, false);
323 a.regexp_src = Some("/x/".into());
324 let e = make_err(&a, WHY_REGEXP, 1, "");
325 assert_eq!(e.text, "Validation failed for property \"a\" with number \"1\" because the number did not match /x/.");
326 let mut a = at(&p, &v, false);
327 a.check = "Min";
328 let e = make_err(&a, "", 1, "");
329 assert_eq!(
330 e.text,
331 "Validation failed for property \"a\" with number \"1\" because check \"Min\" failed."
332 );
333 let e = make_err(&at(&p, &v, false), "why", 1, "");
334 assert_eq!(
335 e.text,
336 "Validation failed for property \"a\" with number \"1\" because check \"why\" failed."
337 );
338 let e = make_err(
339 &at(&p, &v, false),
340 "why",
341 1,
342 "Value \"$VALUE\" at \"$PATH\" is bad.",
343 );
344 assert_eq!(e.text, "Value \"1\" at \"a\" is bad.");
345 assert_eq!(
346 expand_err_text("$VALUE", "", &Value::Null, true),
347 "undefined"
348 );
349 let arr = Value::Arr(vec![Value::from(1)]);
350 let e = make_err(&at(&p, &arr, false), WHY_TYPE, 1, "");
351 assert_eq!(e.text, "Validation failed for index \"a\" with array \"[1]\" because the array is not of type string.");
352 let mut a = at(&p, &v, false);
353 a.parent_arr = true;
354 a.absent = true;
355 let e = make_err(&a, WHY_REQUIRED, 1, "");
356 assert_eq!(
357 e.text,
358 "Validation failed for index \"a\" because the element is missing."
359 );
360 let mut a = at(&p, &v, false);
361 a.terse = true;
362 let e = make_err(&a, WHY_TYPE, 1, "");
363 assert!(e.terse && e.text.is_empty() && e.path.is_empty());
364 let plain = FieldError {
365 path: "a".into(),
366 why: "w".into(),
367 ..Default::default()
368 };
369 assert_eq!(plain.to_string(), "a: w");
370 let plain = FieldError {
371 why: "w".into(),
372 ..Default::default()
373 };
374 assert_eq!(plain.to_string(), "w");
375 }
376
377 #[test]
378 fn collector() {
379 let mut v = ValidationError::default();
380 assert!(!v.has_any());
381 v.add(FieldError {
382 text: "a".into(),
383 ..Default::default()
384 });
385 v.add(FieldError {
386 text: "b".into(),
387 ..Default::default()
388 });
389 assert_eq!(v.to_string(), "a\nb");
390 let mut t = ValidationError {
391 terse: true,
392 ..Default::default()
393 };
394 t.add(FieldError::default());
395 t.add(FieldError::default());
396 assert!(t.has_any() && t.issues.is_empty() && t.n == 2);
397 let _: &dyn std::error::Error = &v;
398 }
399}