serde_valid/validation/string/
pattern.rs

1use crate::{traits::IsMatch, PatternError};
2use regex::Regex;
3
4/// Pattern validation of the string.
5///
6/// See <https://json-schema.org/understanding-json-schema/reference/string.html#regular-expressions>
7///
8/// ```rust
9/// use serde_json::json;
10/// use serde_valid::{Validate, ValidatePattern};
11///
12/// struct MyType(String);
13///
14/// impl ValidatePattern for MyType {
15///     fn validate_pattern(
16///         &self,
17///         pattern: &regex::Regex,
18///     ) -> Result<(), serde_valid::PatternError> {
19///         self.0.validate_pattern(pattern)
20///     }
21/// }
22///
23/// #[derive(Validate)]
24/// struct TestStruct {
25///     #[validate(pattern = r"^\d{4}-\d{2}-\d{2}$")]
26///     val: MyType,
27/// }
28///
29/// let s = TestStruct {
30///     val: MyType(String::from("2020/09/10")),
31/// };
32///
33/// assert_eq!(
34///     s.validate().unwrap_err().to_string(),
35///     json!({
36///         "errors": [],
37///         "properties": {
38///             "val": {
39///                 "errors": [r#"The value must match the pattern of "^\d{4}-\d{2}-\d{2}$"."#]
40///             }
41///         }
42///     })
43///     .to_string()
44/// );
45/// ```
46pub trait ValidatePattern {
47    fn validate_pattern(&self, pattern: &Regex) -> Result<(), PatternError>;
48}
49
50impl<T> ValidatePattern for T
51where
52    T: IsMatch + ?Sized,
53{
54    fn validate_pattern(&self, pattern: &Regex) -> Result<(), PatternError> {
55        if self.is_match(pattern) {
56            Ok(())
57        } else {
58            Err(PatternError::new(pattern.to_string()))
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use std::borrow::Cow;
67    use std::ffi::{OsStr, OsString};
68    use std::path::{Path, PathBuf};
69
70    #[test]
71    fn test_validate_string_pattern_str_type() {
72        assert!(ValidatePattern::validate_pattern(
73            "2020-09-10",
74            &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap()
75        )
76        .is_ok());
77    }
78
79    #[test]
80    fn test_validate_string_pattern_string_type() {
81        assert!(ValidatePattern::validate_pattern(
82            &String::from("2020-09-10"),
83            &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap()
84        )
85        .is_ok());
86    }
87
88    #[test]
89    fn test_validate_string_pattern_cow_str_type() {
90        assert!(ValidatePattern::validate_pattern(
91            &Cow::from("2020-09-10"),
92            &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap()
93        )
94        .is_ok());
95    }
96
97    #[test]
98    fn test_validate_string_pattern_os_str_type() {
99        assert!(ValidatePattern::validate_pattern(
100            OsStr::new("2020-09-10"),
101            &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap()
102        )
103        .is_ok());
104    }
105
106    #[test]
107    fn test_validate_string_pattern_os_string_type() {
108        assert!(ValidatePattern::validate_pattern(
109            &OsString::from("2020-09-10"),
110            &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap()
111        )
112        .is_ok());
113    }
114
115    #[test]
116    fn test_validate_string_pattern_path_type() {
117        assert!(ValidatePattern::validate_pattern(
118            Path::new("./foo/bar.txt"),
119            &Regex::new(r"^*.txt$").unwrap()
120        )
121        .is_ok());
122    }
123
124    #[test]
125    fn test_validate_string_pattern_path_buf_type() {
126        assert!(ValidatePattern::validate_pattern(
127            &PathBuf::from("./foo/bar.txt"),
128            &Regex::new(r"^*.txt$").unwrap()
129        )
130        .is_ok());
131    }
132
133    #[test]
134    fn test_validate_string_pattern_is_false() {
135        assert!(ValidatePattern::validate_pattern(
136            "2020/09/10",
137            &Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap()
138        )
139        .is_err());
140    }
141}