valico/json_schema/keywords/
items.rs1use serde_json::Value;
2
3use super::super::helpers;
4use super::super::schema;
5use super::super::validators;
6
7#[allow(missing_copy_implementations)]
8pub struct Items;
9impl super::Keyword for Items {
10 fn compile(&self, def: &Value, ctx: &schema::WalkContext<'_>) -> super::KeywordResult {
11 let maybe_items = def.get("items");
12 let maybe_additional = def.get("additionalItems");
13
14 if !(maybe_items.is_some() || maybe_additional.is_some()) {
15 return Ok(None);
16 }
17
18 let items = if let Some(items_val) = maybe_items {
19 Some(if items_val.is_object() || items_val.is_boolean() {
20 validators::items::ItemsKind::Schema(helpers::alter_fragment_path(
21 ctx.url.clone(),
22 [ctx.escaped_fragment().as_ref(), "items"].join("/"),
23 ))
24 } else if items_val.is_array() {
25 let mut schemas = vec![];
26 for (idx, item) in items_val.as_array().unwrap().iter().enumerate() {
27 if item.is_object() || item.is_boolean() {
28 schemas.push(helpers::alter_fragment_path(
29 ctx.url.clone(),
30 [
31 ctx.escaped_fragment().as_ref(),
32 "items",
33 idx.to_string().as_ref(),
34 ]
35 .join("/"),
36 ))
37 } else {
38 return Err(schema::SchemaError::Malformed {
39 path: ctx.fragment.join("/"),
40 detail: "Items of this array MUST be objects or booleans".to_string(),
41 });
42 }
43 }
44
45 validators::items::ItemsKind::Array(schemas)
46 } else {
47 return Err(schema::SchemaError::Malformed {
48 path: ctx.fragment.join("/"),
49 detail: "`items` must be an object, an array or a boolean".to_string(),
50 });
51 })
52 } else {
53 None
54 };
55
56 let additional_items = if let Some(additional_val) = maybe_additional {
57 Some(if additional_val.is_boolean() {
58 validators::items::AdditionalKind::Boolean(additional_val.as_bool().unwrap())
59 } else if additional_val.is_object() {
60 validators::items::AdditionalKind::Schema(helpers::alter_fragment_path(
61 ctx.url.clone(),
62 [ctx.escaped_fragment().as_ref(), "additionalItems"].join("/"),
63 ))
64 } else {
65 return Err(schema::SchemaError::Malformed {
66 path: ctx.fragment.join("/"),
67 detail: "`additionalItems` must be a boolean or an object".to_string(),
68 });
69 })
70 } else {
71 None
72 };
73
74 Ok(Some(Box::new(validators::Items {
75 items,
76 additional: additional_items,
77 })))
78 }
79
80 fn place_first(&self) -> bool {
81 true
82 }
83}
84
85#[cfg(test)]
86use super::super::builder;
87#[cfg(test)]
88use super::super::scope;
89#[cfg(test)]
90use serde_json::to_value;
91
92#[cfg(test)]
93fn mk_schema() -> Value {
94 json!({
95 "properties": {
96 "a": {
97 "items": {
98 "properties": {
99 "x": { "type": "number", "default": 42 }
100 }
101 }
102 },
103 "b": {
104 "items": [
105 {
106 "properties": {
107 "x": { "type": "boolean", "default": true }
108 }
109 },
110 { "type": "number", "default": 42 }
111 ]
112 }
113 }
114 })
115}
116
117#[test]
118fn default_for_schema() {
119 let mut scope = scope::Scope::new().supply_defaults();
120 let schema = scope.compile_and_return(mk_schema(), true).unwrap();
121 assert_eq!(schema.get_default(), Some(json!({"b": [{"x": true}, 42]})));
123}
124
125#[test]
126fn default_when_needed() {
127 let mut scope = scope::Scope::new().supply_defaults();
128 let schema = scope.compile_and_return(mk_schema(), true).unwrap();
129 let result = schema.validate(&json!({"a": [{}, {"x": 43}], "b": [{"x": false}]}));
130 assert!(result.is_strictly_valid());
131 assert_eq!(
132 result.replacement,
133 Some(json!({"a": [{"x": 42}, {"x": 43}], "b": [{"x": false}, 42]}))
134 );
135}
136
137#[test]
138fn no_default_otherwise() {
139 let mut scope = scope::Scope::new().supply_defaults();
140 let schema = scope.compile_and_return(mk_schema(), true).unwrap();
141 let result = schema.validate(&json!({"a": [], "b": [{"x": false}, 45]}));
142 assert!(result.is_strictly_valid());
143 assert_eq!(result.replacement, None);
144}
145
146#[test]
147fn validate_items_with_schema() {
148 let mut scope = scope::Scope::new();
149 let schema = scope
150 .compile_and_return(
151 builder::schema(|s| {
152 s.items_schema(|items| {
153 items.minimum(5f64);
154 items.maximum(10f64);
155 });
156 })
157 .into_json(),
158 true,
159 )
160 .ok()
161 .unwrap();
162
163 assert_eq!(
164 schema
165 .validate(&to_value([5, 6, 7, 8, 9, 10]).unwrap())
166 .is_valid(),
167 true
168 );
169 assert_eq!(
170 schema
171 .validate(&to_value([4, 5, 6, 7, 8, 9, 10]).unwrap())
172 .is_valid(),
173 false
174 );
175 assert_eq!(
176 schema
177 .validate(&to_value([5, 6, 7, 8, 9, 10, 11]).unwrap())
178 .is_valid(),
179 false
180 );
181}
182
183#[test]
184fn validate_items_with_array_of_schemes() {
185 let mut scope = scope::Scope::new();
186 let schema = scope
187 .compile_and_return(
188 builder::schema(|s| {
189 s.items_array(|items| {
190 items.push(|item| {
191 item.minimum(1f64);
192 item.maximum(3f64);
193 });
194 items.push(|item| {
195 item.minimum(3f64);
196 item.maximum(6f64);
197 });
198 })
199 })
200 .into_json(),
201 true,
202 )
203 .ok()
204 .unwrap();
205
206 assert_eq!(schema.validate(&to_value([1]).unwrap()).is_valid(), true);
207 assert_eq!(schema.validate(&to_value([1, 3]).unwrap()).is_valid(), true);
208 assert_eq!(
209 schema.validate(&to_value([1, 3, 100]).unwrap()).is_valid(),
210 true
211 );
212 assert_eq!(
213 schema.validate(&to_value([4, 3]).unwrap()).is_valid(),
214 false
215 );
216 assert_eq!(
217 schema.validate(&to_value([1, 7]).unwrap()).is_valid(),
218 false
219 );
220 assert_eq!(
221 schema.validate(&to_value([4, 7]).unwrap()).is_valid(),
222 false
223 );
224}
225
226#[test]
227fn validate_items_with_array_of_schemes_with_additional_bool() {
228 let mut scope = scope::Scope::new();
229 let schema = scope
230 .compile_and_return(
231 builder::schema(|s| {
232 s.items_array(|items| {
233 items.push(|item| {
234 item.minimum(1f64);
235 item.maximum(3f64);
236 });
237 items.push(|item| {
238 item.minimum(3f64);
239 item.maximum(6f64);
240 });
241 });
242 s.additional_items(false);
243 })
244 .into_json(),
245 true,
246 )
247 .ok()
248 .unwrap();
249
250 assert_eq!(
251 schema.validate(&to_value([1, 3, 100]).unwrap()).is_valid(),
252 false
253 );
254}
255
256#[test]
257fn validate_items_with_array_of_schemes_with_additional_schema() {
258 let mut scope = scope::Scope::new();
259 let schema = scope
260 .compile_and_return(
261 builder::schema(|s| {
262 s.items_array(|items| {
263 items.push(|item| {
264 item.minimum(1f64);
265 item.maximum(3f64);
266 });
267 items.push(|item| {
268 item.minimum(3f64);
269 item.maximum(6f64);
270 });
271 });
272 s.additional_items_schema(|add| add.maximum(100f64));
273 })
274 .into_json(),
275 true,
276 )
277 .ok()
278 .unwrap();
279
280 assert_eq!(
281 schema.validate(&to_value([1, 3, 100]).unwrap()).is_valid(),
282 true
283 );
284 assert_eq!(
285 schema.validate(&to_value([1, 3, 101]).unwrap()).is_valid(),
286 false
287 );
288}