jsonschema_valid_compat/
error.rs

1use 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/// An error that can occur during validation.
12#[derive(Default, Debug, Clone)]
13pub struct ValidationError {
14    /// The error message.
15    pub msg: String,
16
17    /// The JSON instance fragment that had the issue.
18    pub instance: Option<serde_json::Value>,
19
20    /// The JSON schema fragment that had the issue.
21    pub schema: Option<serde_json::Value>,
22
23    /// The path to the JSON instance fragment within the entire instance document.
24    pub instance_path: Vec<String>,
25
26    /// The path to the JSON schema fragment within the entire schema.
27    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
79/// Stores information about a single validation error.
80impl ValidationError {
81    /// Create a new validation error with the given error message.
82    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    /// Update the instance and schema context for the error.
92    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    /// Update the instance context for the error.
99    pub fn instance_ctx(mut self, instance_context: String) -> Self {
100        self.instance_path.push(instance_context);
101        self
102    }
103
104    /// Update the schema context for the error.
105    pub fn schema_ctx(mut self, schema_context: String) -> Self {
106        self.schema_path.push(schema_context);
107        self
108    }
109}
110
111/// An `Iterator` over `ValidationError` objects. The main method by which
112/// validation errors are returned to the user.
113pub 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}