yaml_validator/errors/
schema.rs

1#![macro_use]
2
3use thiserror::Error;
4
5use super::GenericError;
6use crate::breadcrumb::{Breadcrumb, BreadcrumbSegment, BreadcrumbSegmentVec};
7
8#[derive(Error, Debug, PartialEq, Eq)]
9pub enum SchemaErrorKind<'a> {
10    #[error("wrong type, expected {expected} got {actual}")]
11    WrongType {
12        expected: &'static str,
13        actual: &'a str,
14    },
15    #[error("malformed field: {error}")]
16    MalformedField { error: String },
17    #[error("field '{field}' missing")]
18    FieldMissing { field: &'a str },
19    #[error("field '{field}' is not specified in the schema")]
20    ExtraField { field: &'a str },
21    #[error("unknown type specified: {unknown_type}")]
22    UnknownType { unknown_type: &'a str },
23    #[error("multiple errors were encountered: {errors:?}")]
24    Multiple { errors: Vec<SchemaError<'a>> },
25}
26
27/// A wrapper type around SchemaErrorKind containing path information about where the error occurred.
28#[derive(Debug, PartialEq, Eq)]
29pub struct SchemaError<'schema> {
30    pub kind: SchemaErrorKind<'schema>,
31    pub state: Breadcrumb<'schema>,
32}
33
34impl<'a> SchemaError<'a> {
35    fn flatten(&self, fmt: &mut std::fmt::Formatter<'_>, root: String) -> std::fmt::Result {
36        match &self.kind {
37            SchemaErrorKind::Multiple { errors } => {
38                for err in errors {
39                    err.flatten(fmt, format!("{}{}", root, self.state))?;
40                }
41            }
42            err => writeln!(fmt, "{}{}: {}", root, self.state, err)?,
43        }
44
45        Ok(())
46    }
47
48    pub fn add_path_name(path: &'a str) -> impl Fn(SchemaError<'a>) -> SchemaError<'a> {
49        move |mut err: SchemaError<'a>| -> SchemaError<'a> {
50            err.state.push(BreadcrumbSegment::Name(path));
51            err
52        }
53    }
54
55    pub fn add_path_index(index: usize) -> impl Fn(SchemaError<'a>) -> SchemaError<'a> {
56        move |mut err: SchemaError<'a>| -> SchemaError<'a> {
57            err.state.push(BreadcrumbSegment::Index(index));
58            err
59        }
60    }
61}
62
63impl<'a> std::fmt::Display for SchemaError<'a> {
64    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        self.flatten(fmt, "#".to_string())
66    }
67}
68
69impl<'a> SchemaErrorKind<'a> {
70    pub fn with_path(self, path: BreadcrumbSegmentVec<'a>) -> SchemaError<'a> {
71        SchemaError {
72            kind: self,
73            state: Breadcrumb::new(path),
74        }
75    }
76
77    pub fn with_path_name(self, path: &'a str) -> SchemaError<'a> {
78        let mut err: SchemaError = self.into();
79        err.state.push(BreadcrumbSegment::Name(path));
80        err
81    }
82
83    pub fn with_path_index(self, index: usize) -> SchemaError<'a> {
84        let mut err: SchemaError = self.into();
85        err.state.push(BreadcrumbSegment::Index(index));
86        err
87    }
88}
89
90impl<'a> From<SchemaErrorKind<'a>> for SchemaError<'a> {
91    fn from(kind: SchemaErrorKind<'a>) -> SchemaError<'a> {
92        SchemaError {
93            kind,
94            state: Breadcrumb::default(),
95        }
96    }
97}
98
99impl<'a> From<Vec<SchemaError<'a>>> for SchemaError<'a> {
100    fn from(errors: Vec<SchemaError<'a>>) -> Self {
101        SchemaErrorKind::Multiple { errors }.into()
102    }
103}
104
105impl<'a> From<GenericError<'a>> for SchemaErrorKind<'a> {
106    fn from(e: GenericError<'a>) -> Self {
107        match e {
108            GenericError::WrongType { expected, actual } => {
109                SchemaErrorKind::WrongType { expected, actual }
110            }
111            GenericError::FieldMissing { field } => SchemaErrorKind::FieldMissing { field },
112            GenericError::ExtraField { field } => SchemaErrorKind::ExtraField { field },
113            GenericError::Multiple { errors } => SchemaErrorKind::Multiple {
114                errors: errors
115                    .into_iter()
116                    .map(SchemaErrorKind::from)
117                    .map(SchemaError::from)
118                    .collect(),
119            },
120        }
121    }
122}
123
124impl<'a> From<GenericError<'a>> for SchemaError<'a> {
125    fn from(e: GenericError<'a>) -> Self {
126        SchemaErrorKind::from(e).into()
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use crate::types::*;
133    use crate::utils::load_simple;
134    use crate::{Context, Validate};
135    use std::convert::TryFrom;
136    #[test]
137    fn test_error_path() {
138        let yaml = load_simple(
139            r#"
140            items:
141              test:
142                type: integer
143              something:
144                type: object
145                items:
146                  level2:
147                    type: object
148                    items:
149                      leaf: 
150                        notype: hello
151            "#,
152        );
153
154        let err = SchemaObject::try_from(&yaml).unwrap_err();
155
156        assert_eq!(
157            format!("{}", err),
158            "#.items.something.items.level2.items.leaf: field \'type\' missing\n",
159        );
160    }
161
162    #[test]
163    fn test_error_path_validation() {
164        let yaml = load_simple(
165            r#"
166            items:
167              test:
168                type: integer
169              something:
170                type: object
171                items:
172                  level2:
173                    type: array
174                    items:
175                      type: object
176                      items:
177                        num:
178                          type: integer
179            "#,
180        );
181
182        let schema = SchemaObject::try_from(&yaml).unwrap();
183        let document = load_simple(
184            r#"
185            test: 20
186            something:
187              level2:
188                - num: abc
189                - num:
190                    hash: value
191                - num:
192                    - array: hello
193                - num: 10
194                - num: jkl
195            "#,
196        );
197        let ctx = Context::default();
198        let err = schema.validate(&ctx, &document).unwrap_err();
199
200        assert_eq!(
201            format!("{}", err),
202            r#"#.something.level2[0].num: wrong type, expected integer got string
203#.something.level2[1].num: wrong type, expected integer got hash
204#.something.level2[2].num: wrong type, expected integer got array
205#.something.level2[4].num: wrong type, expected integer got string
206"#
207        );
208    }
209}