valico/json_schema/keywords/
format.rs1use serde_json::Value;
2use std::collections;
3
4use super::super::schema;
5use super::super::validators;
6
7pub type FormatBuilders = collections::HashMap<String, Box<dyn super::Keyword + Send + Sync>>;
8
9fn default_formats() -> FormatBuilders {
10 let mut map: FormatBuilders = collections::HashMap::new();
11
12 let date_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
13 Ok(Some(
14 Box::new(validators::formats::Date) as validators::BoxedValidator
15 ))
16 });
17 map.insert("date".to_string(), date_builder);
18
19 let date_time_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
20 Ok(Some(
21 Box::new(validators::formats::DateTime) as validators::BoxedValidator
22 ))
23 });
24 map.insert("date-time".to_string(), date_time_builder);
25
26 let email_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
27 Ok(Some(
28 Box::new(validators::formats::Email) as validators::BoxedValidator
29 ))
30 });
31 map.insert("email".to_string(), email_builder);
32
33 let hostname_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
34 Ok(Some(
35 Box::new(validators::formats::Hostname) as validators::BoxedValidator
36 ))
37 });
38 map.insert("hostname".to_string(), hostname_builder);
39
40 let idn_email_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
41 Ok(Some(
42 Box::new(validators::formats::Email) as validators::BoxedValidator
43 ))
44 });
45 map.insert("idn-email".to_string(), idn_email_builder);
46
47 let idn_hostname_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
48 Ok(Some(
49 Box::new(validators::formats::Hostname) as validators::BoxedValidator
50 ))
51 });
52 map.insert("idn-hostname".to_string(), idn_hostname_builder);
53
54 let ipv4_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
55 Ok(Some(
56 Box::new(validators::formats::Ipv4) as validators::BoxedValidator
57 ))
58 });
59 map.insert("ipv4".to_string(), ipv4_builder);
60
61 let ipv6_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
62 Ok(Some(
63 Box::new(validators::formats::Ipv6) as validators::BoxedValidator
64 ))
65 });
66 map.insert("ipv6".to_string(), ipv6_builder);
67
68 let iri_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
69 Ok(Some(
70 Box::new(validators::formats::IRI) as validators::BoxedValidator
71 ))
72 });
73 map.insert("iri".to_string(), iri_builder);
74
75 let iri_reference_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
76 Ok(Some(
77 Box::new(validators::formats::IRIReference) as validators::BoxedValidator
78 ))
79 });
80 map.insert("iri-reference".to_string(), iri_reference_builder);
81
82 let json_pointer_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
83 Ok(Some(
84 Box::new(validators::formats::JsonPointer) as validators::BoxedValidator
85 ))
86 });
87 map.insert("json-pointer".to_string(), json_pointer_builder);
88
89 let regex_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
90 Ok(Some(
91 Box::new(validators::formats::Regex) as validators::BoxedValidator
92 ))
93 });
94 map.insert("regex".to_string(), regex_builder);
95
96 let relative_json_pointer_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
97 Ok(Some(
98 Box::new(validators::formats::RelativeJsonPointer) as validators::BoxedValidator
99 ))
100 });
101 map.insert(
102 "relative-json-pointer".to_string(),
103 relative_json_pointer_builder,
104 );
105
106 let time_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
107 Ok(Some(
108 Box::new(validators::formats::Time) as validators::BoxedValidator
109 ))
110 });
111 map.insert("time".to_string(), time_builder);
112
113 let uri_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
114 Ok(Some(
115 Box::new(validators::formats::Uri) as validators::BoxedValidator
116 ))
117 });
118 map.insert("uri".to_string(), uri_builder);
119
120 let uri_reference_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
121 Ok(Some(
122 Box::new(validators::formats::UriReference) as validators::BoxedValidator
123 ))
124 });
125 map.insert("uri-reference".to_string(), uri_reference_builder);
126
127 let uri_template_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
128 Ok(Some(
129 Box::new(validators::formats::UriTemplate) as validators::BoxedValidator
130 ))
131 });
132 map.insert("uri-template".to_string(), uri_template_builder);
133
134 let uuid_builder = Box::new(|_def: &Value, _ctx: &schema::WalkContext<'_>| {
135 Ok(Some(
136 Box::new(validators::formats::Uuid) as validators::BoxedValidator
137 ))
138 });
139 map.insert("uuid".to_string(), uuid_builder);
140
141 map
142}
143
144#[allow(missing_copy_implementations)]
145pub struct Format {
146 pub formats: FormatBuilders,
147}
148
149impl Format {
150 pub fn new() -> Format {
151 Format {
152 formats: default_formats(),
153 }
154 }
155
156 pub fn with<F>(build_formats: F) -> Format
157 where
158 F: FnOnce(&mut FormatBuilders),
159 {
160 let mut formats = default_formats();
161 build_formats(&mut formats);
162 Format { formats }
163 }
164}
165
166impl super::Keyword for Format {
167 fn compile(&self, def: &Value, ctx: &schema::WalkContext<'_>) -> super::KeywordResult {
168 let format = keyword_key_exists!(def, "format");
169
170 if format.is_string() {
171 let format = format.as_str().unwrap();
172 match self.formats.get(format) {
173 Some(keyword) => keyword.compile(def, ctx),
174 None => Ok(None),
175 }
176 } else {
177 Err(schema::SchemaError::Malformed {
178 path: ctx.fragment.join("/"),
179 detail: "The value of format MUST be a string".to_string(),
180 })
181 }
182 }
183}
184
185#[cfg(test)]
186use super::super::builder;
187#[cfg(test)]
188use super::super::scope;
189#[cfg(test)]
190use serde_json::to_value;
191
192#[test]
193fn validate_date_time() {
194 let mut scope = scope::Scope::new();
195 let schema = scope
196 .compile_and_return(
197 builder::schema(|s| {
198 s.format("date-time");
199 })
200 .into_json(),
201 true,
202 )
203 .ok()
204 .unwrap();
205
206 assert_eq!(
207 schema
208 .validate(&to_value("2015-01-20T17:35:20-08:00").unwrap())
209 .is_valid(),
210 true
211 );
212 assert_eq!(
213 schema
214 .validate(&to_value("1944-06-06T04:04:00Z").unwrap())
215 .is_valid(),
216 true
217 );
218 assert_eq!(
219 schema
220 .validate(&to_value("Tue, 20 Jan 2015 17:35:20 -0800").unwrap())
221 .is_valid(),
222 false
223 );
224}
225
226#[test]
227fn validate_time() {
228 let mut scope = scope::Scope::new();
229 let schema = scope
230 .compile_and_return(
231 builder::schema(|s| {
232 s.format("time");
233 })
234 .into_json(),
235 true,
236 )
237 .ok()
238 .unwrap();
239
240 assert_eq!(
241 schema
242 .validate(&to_value("17:35:20-08:00").unwrap())
243 .is_valid(),
244 false
245 );
246 assert_eq!(
247 schema
248 .validate(&to_value("04:04:00.040404Z").unwrap())
249 .is_valid(),
250 false );
252 assert_eq!(
253 schema
254 .validate(&to_value("17:35:20 -0800").unwrap())
255 .is_valid(),
256 false
257 );
258}
259
260#[test]
261fn validate_date() {
262 let mut scope = scope::Scope::new();
263 let schema = scope
264 .compile_and_return(
265 builder::schema(|s| {
266 s.format("date");
267 })
268 .into_json(),
269 true,
270 )
271 .ok()
272 .unwrap();
273
274 assert_eq!(
275 schema.validate(&to_value("2015-01-20").unwrap()).is_valid(),
276 true
277 );
278 assert_eq!(
279 schema
280 .validate(&to_value("Tue, 20 Jan 2015").unwrap())
281 .is_valid(),
282 false
283 );
284}
285
286#[test]
287fn validate_email() {
288 let mut scope = scope::Scope::new();
289 let schema = scope
290 .compile_and_return(
291 builder::schema(|s| {
292 s.format("email");
293 })
294 .into_json(),
295 true,
296 )
297 .ok()
298 .unwrap();
299
300 assert_eq!(
301 schema.validate(&to_value("ad@ress").unwrap()).is_valid(),
302 true
303 );
304 assert_eq!(
305 schema
306 .validate(&to_value("add.ress+fd@domain.co.com").unwrap())
307 .is_valid(),
308 true
309 );
310 assert_eq!(
311 schema.validate(&to_value("add:re").unwrap()).is_valid(),
312 false
313 );
314}
315
316#[test]
317fn validate_hostname() {
318 let mut scope = scope::Scope::new();
319 let schema = scope
320 .compile_and_return(
321 builder::schema(|s| {
322 s.format("hostname");
323 })
324 .into_json(),
325 true,
326 )
327 .ok()
328 .unwrap();
329
330 assert_eq!(
331 schema.validate(&to_value("example").unwrap()).is_valid(),
332 true
333 );
334 assert_eq!(
335 schema
336 .validate(&to_value("example.com").unwrap())
337 .is_valid(),
338 true
339 );
340 assert_eq!(
341 schema.validate(&to_value("ex:ample").unwrap()).is_valid(),
342 false
343 );
344}
345
346#[test]
347fn validate_ipv4() {
348 let mut scope = scope::Scope::new();
349 let schema = scope
350 .compile_and_return(
351 builder::schema(|s| {
352 s.format("ipv4");
353 })
354 .into_json(),
355 true,
356 )
357 .ok()
358 .unwrap();
359
360 assert_eq!(
361 schema.validate(&to_value("127.0.0.1").unwrap()).is_valid(),
362 true
363 );
364 assert_eq!(
365 schema.validate(&to_value("8.8.8.8").unwrap()).is_valid(),
366 true
367 );
368 assert_eq!(
369 schema
370 .validate(&to_value("::::0.0.0.0").unwrap())
371 .is_valid(),
372 false
373 );
374}
375
376#[test]
377fn validate_ipv6() {
378 let mut scope = scope::Scope::new();
379 let schema = scope
380 .compile_and_return(
381 builder::schema(|s| {
382 s.format("ipv6");
383 })
384 .into_json(),
385 true,
386 )
387 .ok()
388 .unwrap();
389
390 assert_eq!(
391 schema
392 .validate(&to_value("FE80:0000:0000:0000:0202:B3FF:FE1E:8329").unwrap())
393 .is_valid(),
394 true
395 );
396 assert_eq!(
397 schema.validate(&to_value("127.0.0.1").unwrap()).is_valid(),
398 false
399 );
400}
401
402#[test]
403fn validate_json_pointer() {
404 let mut scope = scope::Scope::new();
405 let schema = scope
406 .compile_and_return(
407 builder::schema(|s| {
408 s.format("json-pointer");
409 })
410 .into_json(),
411 true,
412 )
413 .ok()
414 .unwrap();
415
416 assert_eq!(
417 schema.validate(&to_value("/foo/bar").unwrap()).is_valid(),
418 true
419 );
420 assert_eq!(
421 schema.validate(&to_value("pointer").unwrap()).is_valid(),
422 false
423 );
424}
425
426#[test]
427fn validate_uri() {
428 let mut scope = scope::Scope::new();
429 let schema = scope
430 .compile_and_return(
431 builder::schema(|s| {
432 s.format("uri");
433 })
434 .into_json(),
435 true,
436 )
437 .ok()
438 .unwrap();
439
440 assert_eq!(
441 schema
442 .validate(&to_value("http://example.com").unwrap())
443 .is_valid(),
444 true
445 );
446 assert_eq!(
447 schema.validate(&to_value("some-wrong").unwrap()).is_valid(),
448 false
449 );
450}
451
452#[test]
453fn validate_uuid() {
454 let mut scope = scope::Scope::new();
455 let schema = scope
456 .compile_and_return(
457 builder::schema(|s| {
458 s.format("uuid");
459 })
460 .into_json(),
461 true,
462 )
463 .ok()
464 .unwrap();
465
466 assert_eq!(
467 schema
468 .validate(&to_value("2f5a2593-7481-49e2-9911-8fe2ad069aac").unwrap())
469 .is_valid(),
470 true
471 );
472 assert_eq!(
473 schema
474 .validate(&to_value("2f5a2593748149e299118fe2ad069aac").unwrap())
475 .is_valid(),
476 true
477 );
478 assert_eq!(
479 schema
480 .validate(&to_value("2f5a2593-7481-49e2-9911-8fe2ad06").unwrap())
481 .is_valid(),
482 false
483 );
484}