jsonschema_valid/
error.rs1use std::error::Error as StdError;
2use std::fmt;
3use std::iter::{empty, once};
4
5use itertools::Itertools;
6use serde_json::Value;
7
8#[derive(Default, Debug, Clone)]
10pub struct ValidationError {
11 pub msg: String,
13
14 pub instance: Option<serde_json::Value>,
16
17 pub schema: Option<serde_json::Value>,
19
20 pub instance_path: Vec<String>,
22
23 pub schema_path: Vec<String>,
25}
26
27impl StdError for ValidationError {}
28
29fn path_to_string(path: &[String]) -> String {
30 if path.is_empty() {
31 "/".to_string()
32 } else {
33 "/".to_owned() + &path.iter().rev().join("/")
34 }
35}
36
37impl fmt::Display for ValidationError {
38 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39 writeln!(f, "{}", textwrap::fill(&self.msg, 78))?;
40
41 if let Some(instance) = &self.instance {
42 writeln!(
43 f,
44 "At instance path {}:",
45 path_to_string(&self.instance_path)
46 )?;
47
48 let json_content =
49 serde_json::to_string_pretty(&instance).unwrap_or_else(|_| "".to_string());
50 writeln!(f, "{}", textwrap::indent(&json_content, " "))?;
51 }
52
53 if let Some(schema) = &self.schema {
54 writeln!(f, "At schema path {}:", path_to_string(&self.schema_path))?;
55
56 let json_content =
57 serde_json::to_string_pretty(&schema).unwrap_or_else(|_| "".to_string());
58 writeln!(f, "{}", textwrap::indent(&json_content, " "))?;
59
60 if let Some(description) = schema.get("description").and_then(|x| x.as_str()) {
61 writeln!(f, "Documentation for this node:")?;
62 writeln!(f, "{}", textwrap::indent(description, " "))?;
63 };
64 }
65
66 Ok(())
67 }
68}
69
70impl From<url::ParseError> for ValidationError {
71 fn from(err: url::ParseError) -> ValidationError {
72 ValidationError::new(&format!("Invalid URL: {:?}", err), None, None)
73 }
74}
75
76impl ValidationError {
78 pub fn new(msg: &str, instance: Option<&Value>, schema: Option<&Value>) -> ValidationError {
80 ValidationError {
81 msg: String::from(msg),
82 instance: instance.cloned(),
83 schema: schema.cloned(),
84 ..Default::default()
85 }
86 }
87
88 pub fn add_ctx(mut self, instance_context: String, schema_context: String) -> Self {
90 self.instance_path.push(instance_context);
91 self.schema_path.push(schema_context);
92 self
93 }
94
95 pub fn instance_ctx(mut self, instance_context: String) -> Self {
97 self.instance_path.push(instance_context);
98 self
99 }
100
101 pub fn schema_ctx(mut self, schema_context: String) -> Self {
103 self.schema_path.push(schema_context);
104 self
105 }
106}
107
108pub type ErrorIterator<'a> = Box<dyn Iterator<Item = ValidationError> + 'a>;
111
112pub fn make_error<'a, O: Into<String>>(
113 message: O,
114 instance: Option<&Value>,
115 schema: Option<&Value>,
116) -> ErrorIterator<'a> {
117 Box::new(once(ValidationError::new(
118 &message.into(),
119 instance,
120 schema,
121 )))
122}
123
124pub fn no_error<'a>() -> ErrorIterator<'a> {
125 Box::new(empty())
126}
127
128#[cfg(test)]
129mod tests {
130 use crate::{schemas, Config};
131 use serde_json::json;
132
133 #[test]
134 fn test_pretty_print_errors() {
135 let schema = json!(
136 { "properties": { "foo": { "type": "integer", "description": "HELLO" } } });
137 let instance = json!({"foo": "string"});
138 let cfg = Config::from_schema(&schema, Some(schemas::Draft::Draft6)).unwrap();
139 let validation = cfg.validate(&instance);
140
141 if let Err(errors) = validation {
142 for error in errors {
143 let formatted = format!("{}", error);
144 println!("{}", formatted);
145 assert!(error.instance_path == vec!("foo"));
146 assert!(error.schema_path == vec!("type", "foo", "properties"));
147
148 assert!(formatted.contains("At instance path /foo"));
149 assert!(formatted.contains("At schema path /properties/foo/type"));
150 assert!(formatted.contains("Invalid type"));
151 assert!(formatted.contains("HELLO"));
152 }
153 }
154 }
155}