valico/json_schema/keywords/
properties.rs
1use std::collections;
2use regex;
3use serde_json::{Value};
4
5use super::super::schema;
6use super::super::validators;
7use super::super::helpers;
8
9#[allow(missing_copy_implementations)]
10pub struct Properties;
11impl super::Keyword for Properties {
12 fn compile(&self, def: &Value, ctx: &schema::WalkContext) -> super::KeywordResult {
13 let maybe_properties = def.get("properties");
14 let maybe_additional = def.get("additionalProperties");
15 let maybe_pattern = def.get("patternProperties");
16
17 if maybe_properties.is_none() && maybe_additional.is_none() && maybe_pattern.is_none() {
18 return Ok(None)
19 }
20
21 let properties = if maybe_properties.is_some() {
22 let properties = maybe_properties.unwrap();
23 if properties.is_object() {
24 let mut schemes = collections::HashMap::new();
25 let properties = properties.as_object().unwrap();
26 for (key, value) in properties.iter() {
27 if value.is_object() {
28 schemes.insert(key.to_string(),
29 helpers::alter_fragment_path(ctx.url.clone(), [
30 ctx.escaped_fragment().as_ref(),
31 "properties",
32 helpers::encode(key).as_ref()
33 ].join("/"))
34 );
35 } else {
36 return Err(schema::SchemaError::Malformed {
37 path: ctx.fragment.join("/"),
38 detail: "Each value of this object MUST be an object".to_string()
39 })
40 }
41 }
42 schemes
43 } else {
44 return Err(schema::SchemaError::Malformed {
45 path: ctx.fragment.join("/"),
46 detail: "The value of `properties` MUST be an object.".to_string()
47 })
48 }
49 } else {
50 collections::HashMap::new()
51 };
52
53 let additional_properties = if maybe_additional.is_some() {
54 let additional_val = maybe_additional.unwrap();
55 if additional_val.is_boolean() {
56
57 validators::properties::AdditionalKind::Boolean(additional_val.as_bool().unwrap())
58
59 } else if additional_val.is_object() {
60
61 validators::properties::AdditionalKind::Schema(
62 helpers::alter_fragment_path(ctx.url.clone(), [
63 ctx.escaped_fragment().as_ref(),
64 "additionalProperties"
65 ].join("/"))
66 )
67
68 } else {
69
70 return Err(schema::SchemaError::Malformed {
71 path: ctx.fragment.join("/"),
72 detail: "The value of `additionalProperties` MUST be a boolean or an object.".to_string()
73 })
74
75 }
76 } else {
77 validators::properties::AdditionalKind::Boolean(true)
78 };
79
80 let patterns = if maybe_pattern.is_some() {
81 let pattern = maybe_pattern.unwrap();
82 if pattern.is_object() {
83 let pattern = pattern.as_object().unwrap();
84 let mut patterns = vec![];
85
86 for (key, value) in pattern.iter() {
87 if value.is_object() {
88
89 match regex::Regex::new(key.as_ref()) {
90 Ok(regex) => {
91 let url = helpers::alter_fragment_path(ctx.url.clone(), [
92 ctx.escaped_fragment().as_ref(),
93 "patternProperties",
94 helpers::encode(key).as_ref()
95 ].join("/"));
96 patterns.push((regex, url));
97 },
98 Err(_) => {
99 return Err(schema::SchemaError::Malformed {
100 path: ctx.fragment.join("/"),
101 detail: "Each property name of this object SHOULD be a valid regular expression.".to_string()
102 })
103 }
104 }
105
106 } else {
107 return Err(schema::SchemaError::Malformed {
108 path: ctx.fragment.join("/"),
109 detail: "Each value of this object MUST be an object".to_string()
110 })
111 }
112 }
113
114 patterns
115
116 } else {
117 return Err(schema::SchemaError::Malformed {
118 path: ctx.fragment.join("/"),
119 detail: "The value of `patternProperties` MUST be an object".to_string()
120 })
121 }
122 } else { vec![] };
123
124 Ok(Some(Box::new(validators::Properties {
125 properties: properties,
126 additional: additional_properties,
127 patterns: patterns
128 })))
129
130 }
131}
132
133#[cfg(test)] use super::super::scope;
134#[cfg(test)] use super::super::builder;
135#[cfg(test)] use jsonway;
136
137#[test]
138fn validate_properties() {
139 let mut scope = scope::Scope::new();
140 let schema = scope.compile_and_return(builder::schema(|s| {
141 s.properties(|props| {
142 props.insert("prop1", |prop1| {
143 prop1.maximum(10f64, false);
144 });
145 props.insert("prop2", |prop2| {
146 prop2.minimum(11f64, false);
147 });
148 });
149 }).into_json(), true).ok().unwrap();
150
151 assert_eq!(schema.validate(&jsonway::object(|obj| {
152 obj.set("prop1", 10);
153 obj.set("prop2", 11);
154 }).unwrap()).is_valid(), true);
155
156 assert_eq!(schema.validate(&jsonway::object(|obj| {
157 obj.set("prop1", 11);
158 obj.set("prop2", 11);
159 }).unwrap()).is_valid(), false);
160
161 assert_eq!(schema.validate(&jsonway::object(|obj| {
162 obj.set("prop1", 10);
163 obj.set("prop2", 10);
164 }).unwrap()).is_valid(), false);
165
166 assert_eq!(schema.validate(&jsonway::object(|obj| {
167 obj.set("prop1", 10);
168 obj.set("prop2", 11);
169 obj.set("prop3", 1000); }).unwrap()).is_valid(), true);
171}
172
173#[test]
174fn validate_kw_properties() {
175 let mut scope = scope::Scope::new();
176 let schema = scope.compile_and_return(builder::schema(|s| {
177 s.properties(|props| {
178 props.insert("id", |prop1| {
179 prop1.maximum(10f64, false);
180 });
181 props.insert("items", |prop2| {
182 prop2.minimum(11f64, false);
183 });
184 });
185 }).into_json(), true).ok().unwrap();
186
187 assert_eq!(schema.validate(&jsonway::object(|obj| {
188 obj.set("id", 10);
189 obj.set("items", 11);
190 }).unwrap()).is_valid(), true);
191
192 assert_eq!(schema.validate(&jsonway::object(|obj| {
193 obj.set("id", 11);
194 obj.set("items", 11);
195 }).unwrap()).is_valid(), false);
196
197}
198
199#[test]
200fn validate_pattern_properties() {
201 let mut scope = scope::Scope::new();
202 let schema = scope.compile_and_return(builder::schema(|s| {
203 s.properties(|properties| {
204 properties.insert("prop1", |prop1| {
205 prop1.maximum(10f64, false);
206 });
207 });
208 s.pattern_properties(|properties| {
209 properties.insert("prop.*", |prop| {
210 prop.maximum(1000f64, false);
211 });
212 });
213 }).into_json(), true).ok().unwrap();
214
215 assert_eq!(schema.validate(&jsonway::object(|obj| {
216 obj.set("prop1", 11);
217 }).unwrap()).is_valid(), false);
218
219 assert_eq!(schema.validate(&jsonway::object(|obj| {
220 obj.set("prop1", 10);
221 obj.set("prop2", 1000);
222 }).unwrap()).is_valid(), true);
223
224 assert_eq!(schema.validate(&jsonway::object(|obj| {
225 obj.set("prop1", 10);
226 obj.set("prop2", 1001);
227 }).unwrap()).is_valid(), false);
228}
229
230#[test]
231fn validate_additional_properties_false() {
232 let mut scope = scope::Scope::new();
233 let schema = scope.compile_and_return(builder::schema(|s| {
234 s.properties(|properties| {
235 properties.insert("prop1", |prop1| {
236 prop1.maximum(10f64, false);
237 });
238 });
239 s.pattern_properties(|properties| {
240 properties.insert("prop.*", |prop| {
241 prop.maximum(1000f64, false);
242 });
243 });
244 s.additional_properties(false);
245 }).into_json(), true).ok().unwrap();
246
247 assert_eq!(schema.validate(&jsonway::object(|obj| {
248 obj.set("prop1", 10);
249 obj.set("prop2", 1000);
250 }).unwrap()).is_valid(), true);
251
252 assert_eq!(schema.validate(&jsonway::object(|obj| {
253 obj.set("prop1", 10);
254 obj.set("prop2", 1000);
255 obj.set("some_other", 0);
256 }).unwrap()).is_valid(), false);
257}
258
259#[test]
260fn validate_additional_properties_schema() {
261 let mut scope = scope::Scope::new();
262 let schema = scope.compile_and_return(builder::schema(|s| {
263 s.properties(|properties| {
264 properties.insert("prop1", |prop1| {
265 prop1.maximum(10f64, false);
266 });
267 });
268 s.pattern_properties(|properties| {
269 properties.insert("prop.*", |prop| {
270 prop.maximum(1000f64, false);
271 });
272 });
273 s.additional_properties_schema(|additional| {
274 additional.maximum(5f64, false)
275 });
276 }).into_json(), true).ok().unwrap();
277
278 assert_eq!(schema.validate(&jsonway::object(|obj| {
279 obj.set("prop1", 10);
280 obj.set("prop2", 1000);
281 obj.set("some_other", 5);
282 }).unwrap()).is_valid(), true);
283
284 assert_eq!(schema.validate(&jsonway::object(|obj| {
285 obj.set("prop1", 10);
286 obj.set("prop2", 1000);
287 obj.set("some_other", 6);
288 }).unwrap()).is_valid(), false);
289}
290
291#[test]
292fn malformed() {
293 let mut scope = scope::Scope::new();
294
295 assert!(scope.compile_and_return(jsonway::object(|schema| {
296 schema.set("properties", false);
297 }).unwrap(), true).is_err());
298
299 assert!(scope.compile_and_return(jsonway::object(|schema| {
300 schema.set("patternProperties", false);
301 }).unwrap(), true).is_err());
302
303 assert!(scope.compile_and_return(jsonway::object(|schema| {
304 schema.object("patternProperties", |pattern| {
305 pattern.set("test", 1)
306 });
307 }).unwrap(), true).is_err());
308
309 assert!(scope.compile_and_return(jsonway::object(|schema| {
310 schema.object("patternProperties", |pattern| {
311 pattern.object("((", |_malformed| {})
312 });
313 }).unwrap(), true).is_err());
314
315 assert!(scope.compile_and_return(jsonway::object(|schema| {
316 schema.set("additionalProperties", 10);
317 }).unwrap(), true).is_err());
318}