yaml_validator/errors/
schema.rs1#![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#[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}