Skip to main content

valico/json_schema/keywords/
format.rs

1use 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 // https://github.com/chronotope/chrono/issues/288
251    );
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}