jsonschema_valid/
error.rs

1use std::error::Error as StdError;
2use std::fmt;
3use std::iter::{empty, once};
4
5use itertools::Itertools;
6use serde_json::Value;
7
8/// An error that can occur during validation.
9#[derive(Default, Debug, Clone)]
10pub struct ValidationError {
11    /// The error message.
12    pub msg: String,
13
14    /// The JSON instance fragment that had the issue.
15    pub instance: Option<serde_json::Value>,
16
17    /// The JSON schema fragment that had the issue.
18    pub schema: Option<serde_json::Value>,
19
20    /// The path to the JSON instance fragment within the entire instance document.
21    pub instance_path: Vec<String>,
22
23    /// The path to the JSON schema fragment within the entire schema.
24    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
76/// Stores information about a single validation error.
77impl ValidationError {
78    /// Create a new validation error with the given error message.
79    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    /// Update the instance and schema context for the error.
89    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    /// Update the instance context for the error.
96    pub fn instance_ctx(mut self, instance_context: String) -> Self {
97        self.instance_path.push(instance_context);
98        self
99    }
100
101    /// Update the schema context for the error.
102    pub fn schema_ctx(mut self, schema_context: String) -> Self {
103        self.schema_path.push(schema_context);
104        self
105    }
106}
107
108/// An `Iterator` over `ValidationError` objects. The main method by which
109/// validation errors are returned to the user.
110pub 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}