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