Skip to main content

valico/json_schema/validators/
formats.rs

1use addr::parser::{DomainName, EmailAddress};
2use addr::psl::List;
3use chrono;
4use json_pointer;
5use serde_json::Value;
6use std::net;
7use uritemplate;
8use url;
9use uuid;
10
11use super::super::errors;
12use super::super::scope;
13
14#[allow(missing_copy_implementations)]
15pub struct Date;
16
17impl super::Validator for Date {
18    fn validate(
19        &self,
20        val: &Value,
21        path: &str,
22        _scope: &scope::Scope,
23        _: &super::ValidationState,
24    ) -> super::ValidationState {
25        let string = nonstrict_process!(val.as_str(), path);
26
27        match chrono::NaiveDate::parse_from_str(string, "%Y-%m-%d") {
28            Ok(_) => {
29                if string.len() == 10 {
30                    super::ValidationState::new()
31                } else {
32                    val_error!(errors::Format {
33                        path: path.to_string(),
34                        detail: "Malformed Date".to_string()
35                    })
36                }
37            }
38            Err(_) => val_error!(errors::Format {
39                path: path.to_string(),
40                detail: "Malformed date".to_string()
41            }),
42        }
43    }
44}
45
46#[allow(missing_copy_implementations)]
47pub struct DateTime;
48
49impl super::Validator for DateTime {
50    fn validate(
51        &self,
52        val: &Value,
53        path: &str,
54        _scope: &scope::Scope,
55        _: &super::ValidationState,
56    ) -> super::ValidationState {
57        let string = nonstrict_process!(val.as_str(), path);
58
59        match chrono::DateTime::parse_from_rfc3339(string) {
60            Ok(_) => super::ValidationState::new(),
61            Err(_) => val_error!(errors::Format {
62                path: path.to_string(),
63                detail: "Malformed date time".to_string()
64            }),
65        }
66    }
67}
68
69#[allow(missing_copy_implementations)]
70pub struct Email;
71
72impl super::Validator for Email {
73    fn validate(
74        &self,
75        val: &Value,
76        path: &str,
77        _scope: &scope::Scope,
78        _: &super::ValidationState,
79    ) -> super::ValidationState {
80        let string = nonstrict_process!(val.as_str(), path);
81
82        match List.parse_email_address(string) {
83            Ok(_) => super::ValidationState::new(),
84            Err(_) => val_error!(errors::Format {
85                path: path.to_string(),
86                detail: "Malformed email address".to_string()
87            }),
88        }
89    }
90}
91
92#[allow(missing_copy_implementations)]
93pub struct Hostname;
94
95impl super::Validator for Hostname {
96    fn validate(
97        &self,
98        val: &Value,
99        path: &str,
100        _scope: &scope::Scope,
101        _: &super::ValidationState,
102    ) -> super::ValidationState {
103        let string = nonstrict_process!(val.as_str(), path);
104
105        match List.parse_domain_name(string) {
106            Ok(_) => super::ValidationState::new(),
107            Err(_) => val_error!(errors::Format {
108                path: path.to_string(),
109                detail: "Malformed hostname".to_string()
110            }),
111        }
112    }
113}
114
115#[allow(missing_copy_implementations)]
116pub struct Ipv4;
117
118impl super::Validator for Ipv4 {
119    fn validate(
120        &self,
121        val: &Value,
122        path: &str,
123        _scope: &scope::Scope,
124        _: &super::ValidationState,
125    ) -> super::ValidationState {
126        let string = nonstrict_process!(val.as_str(), path);
127
128        match string.parse::<net::Ipv4Addr>() {
129            Ok(_) => super::ValidationState::new(),
130            Err(_) => val_error!(errors::Format {
131                path: path.to_string(),
132                detail: "Malformed IP address".to_string()
133            }),
134        }
135    }
136}
137
138#[allow(missing_copy_implementations)]
139pub struct Ipv6;
140
141impl super::Validator for Ipv6 {
142    fn validate(
143        &self,
144        val: &Value,
145        path: &str,
146        _scope: &scope::Scope,
147        _: &super::ValidationState,
148    ) -> super::ValidationState {
149        let string = nonstrict_process!(val.as_str(), path);
150
151        match string.parse::<net::Ipv6Addr>() {
152            Ok(_) => super::ValidationState::new(),
153            Err(_) => val_error!(errors::Format {
154                path: path.to_string(),
155                detail: "Malformed IP address".to_string()
156            }),
157        }
158    }
159}
160
161#[allow(missing_copy_implementations)]
162pub struct IRI;
163
164impl super::Validator for IRI {
165    fn validate(
166        &self,
167        val: &Value,
168        path: &str,
169        _scope: &scope::Scope,
170        _: &super::ValidationState,
171    ) -> super::ValidationState {
172        let string = nonstrict_process!(val.as_str(), path);
173
174        match url::Url::parse(string) {
175            Ok(_) => super::ValidationState::new(),
176            Err(err) => val_error!(errors::Format {
177                path: path.to_string(),
178                detail: format!("Malformed IRI: {err}")
179            }),
180        }
181    }
182}
183
184#[allow(missing_copy_implementations)]
185pub struct IRIReference;
186
187impl super::Validator for IRIReference {
188    fn validate(
189        &self,
190        val: &Value,
191        path: &str,
192        _scope: &scope::Scope,
193        _: &super::ValidationState,
194    ) -> super::ValidationState {
195        let string = nonstrict_process!(val.as_str(), path);
196
197        let base_url = url::Url::parse("http://example.com/").unwrap();
198
199        match base_url.join(string) {
200            Ok(_) => super::ValidationState::new(),
201            Err(err) => val_error!(errors::Format {
202                path: path.to_string(),
203                detail: format!("Malformed IRI reference: {err}")
204            }),
205        }
206    }
207}
208
209#[allow(missing_copy_implementations)]
210pub struct JsonPointer;
211
212impl super::Validator for JsonPointer {
213    fn validate(
214        &self,
215        val: &Value,
216        path: &str,
217        _scope: &scope::Scope,
218        _: &super::ValidationState,
219    ) -> super::ValidationState {
220        let string = nonstrict_process!(val.as_str(), path);
221
222        match string.parse::<json_pointer::JsonPointer<_, _>>() {
223            Ok(_) => super::ValidationState::new(),
224            Err(_) => val_error!(errors::Format {
225                path: path.to_string(),
226                detail: "Malformed JSON pointer".to_string()
227            }),
228        }
229    }
230}
231
232#[allow(missing_copy_implementations)]
233pub struct Regex;
234
235impl super::Validator for Regex {
236    fn validate(
237        &self,
238        val: &Value,
239        path: &str,
240        _scope: &scope::Scope,
241        _: &super::ValidationState,
242    ) -> super::ValidationState {
243        let string = nonstrict_process!(val.as_str(), path);
244
245        // Forward slash ('/') is prefixed with double backslash ('\\')
246        // in a JSON string. Although this is valid in JSON, this will fail
247        // regex parsing in the rust Regex library. This string replacement
248        // ensures that escaped forward slashes do not fail validation.
249        let string = string.replace(r"\/", "/");
250
251        match fancy_regex::Regex::new(&string) {
252            Ok(_) => super::ValidationState::new(),
253            Err(er) => {
254                val_error!(errors::Format {
255                    path: path.to_string(),
256                    detail: format!("Malformed regex - {er}")
257                })
258            }
259        }
260    }
261}
262
263#[allow(missing_copy_implementations)]
264pub struct RelativeJsonPointer;
265
266impl super::Validator for RelativeJsonPointer {
267    fn validate(
268        &self,
269        val: &Value,
270        path: &str,
271        _scope: &scope::Scope,
272        _: &super::ValidationState,
273    ) -> super::ValidationState {
274        let string = nonstrict_process!(val.as_str(), path);
275
276        match string.parse::<json_pointer::JsonPointer<_, _>>() {
277            Ok(_) => super::ValidationState::new(),
278            Err(_) => val_error!(errors::Format {
279                path: path.to_string(),
280                detail: "Malformed relative JSON pointer".to_string()
281            }),
282        }
283    }
284}
285
286#[allow(missing_copy_implementations)]
287pub struct Time;
288
289impl super::Validator for Time {
290    fn validate(
291        &self,
292        val: &Value,
293        path: &str,
294        _scope: &scope::Scope,
295        _: &super::ValidationState,
296    ) -> super::ValidationState {
297        let string = nonstrict_process!(val.as_str(), path);
298
299        match chrono::NaiveTime::parse_from_str(string, "%H:%M:%S%.f") {
300            Ok(_) => super::ValidationState::new(),
301            Err(_) => val_error!(errors::Format {
302                path: path.to_string(),
303                detail: "Malformed time".to_string()
304            }),
305        }
306    }
307}
308
309#[allow(missing_copy_implementations)]
310pub struct Uuid;
311
312impl super::Validator for Uuid {
313    fn validate(
314        &self,
315        val: &Value,
316        path: &str,
317        _scope: &scope::Scope,
318        _: &super::ValidationState,
319    ) -> super::ValidationState {
320        let string = nonstrict_process!(val.as_str(), path);
321
322        match string.parse::<uuid::Uuid>() {
323            Ok(_) => super::ValidationState::new(),
324            Err(err) => val_error!(errors::Format {
325                path: path.to_string(),
326                detail: format!("Malformed UUID: {err:?}")
327            }),
328        }
329    }
330}
331
332#[allow(missing_copy_implementations)]
333pub struct Uri;
334
335impl super::Validator for Uri {
336    fn validate(
337        &self,
338        val: &Value,
339        path: &str,
340        _scope: &scope::Scope,
341        _: &super::ValidationState,
342    ) -> super::ValidationState {
343        let string = nonstrict_process!(val.as_str(), path);
344
345        match url::Url::parse(string) {
346            Ok(_) => super::ValidationState::new(),
347            Err(err) => val_error!(errors::Format {
348                path: path.to_string(),
349                detail: format!("Malformed URI: {err}")
350            }),
351        }
352    }
353}
354
355#[allow(missing_copy_implementations)]
356pub struct UriReference;
357
358impl super::Validator for UriReference {
359    fn validate(
360        &self,
361        val: &Value,
362        path: &str,
363        _scope: &scope::Scope,
364        _: &super::ValidationState,
365    ) -> super::ValidationState {
366        let string = nonstrict_process!(val.as_str(), path);
367
368        let base_url = url::Url::parse("http://example.com/").unwrap();
369
370        match base_url.join(string) {
371            Ok(_) => super::ValidationState::new(),
372            Err(err) => val_error!(errors::Format {
373                path: path.to_string(),
374                detail: format!("Malformed URI reference: {err}")
375            }),
376        }
377    }
378}
379
380#[allow(missing_copy_implementations)]
381pub struct UriTemplate;
382
383impl super::Validator for UriTemplate {
384    fn validate(
385        &self,
386        val: &Value,
387        _path: &str,
388        _scope: &scope::Scope,
389        _: &super::ValidationState,
390    ) -> super::ValidationState {
391        let string = nonstrict_process!(val.as_str(), path);
392
393        let _ = uritemplate::UriTemplate::new(string);
394        super::ValidationState::new()
395    }
396}
397
398#[cfg(test)]
399pub mod tests {
400    use super::Regex;
401    use crate::json_schema::validators::Validator;
402    use crate::json_schema::{Scope, ValidationState};
403
404    #[test]
405    fn validate_valid_empty_regex() {
406        let result = validate_regex("");
407        assert!(result.errors.is_empty())
408    }
409
410    #[test]
411    fn validate_valid_regex_simple() {
412        let result = validate_regex("^[a-z][a-z0-9]{0,10}$");
413        assert!(result.errors.is_empty())
414    }
415
416    #[test]
417    fn validate_valid_regex_with_double_escaped_forward_slash() {
418        let result = validate_regex("\\w+:(\\/?\\/?)[^\\s]+");
419        assert!(result.errors.is_empty())
420    }
421
422    #[test]
423    fn validate_invalid_regex() {
424        let result = validate_regex("FOO\\");
425        assert_eq!(result.errors.len(), 1);
426
427        let only_err = result.errors.get(0);
428        assert!(only_err.is_some());
429
430        let err = only_err.unwrap();
431        assert!(err.get_detail().is_some());
432        assert!(err.get_detail().unwrap().contains("Malformed regex"))
433    }
434
435    fn validate_regex(json_string: &str) -> ValidationState {
436        let value = serde_json::value::Value::String(json_string.into());
437        let scope = Scope::new();
438        Regex {}.validate(&value, "/", &scope, &Default::default())
439    }
440}