refined_type/rule/string/
regex.rs

1pub use regex::Regex;
2
3#[macro_export]
4/// A macro to declare a rule that validates a `String` based on a regex pattern
5/// ```rust
6/// use refined_type::rule::Rule;
7/// use refined_type::rule::Regex;
8/// use refined_type::result::Error;
9/// use refined_type::declare_regex_rule;
10/// use refined_type::Refined;
11///
12/// declare_regex_rule![
13///    EmailRule,
14///    r"^[a-zA-Z0-9_.+-]+@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$"
15/// ];
16///
17/// let valid = String::from("sample@example.com");
18/// assert!(EmailRule::validate(&valid).is_ok());
19///
20/// let invalid = String::from("example.com");
21/// assert!(EmailRule::validate(&invalid).is_err());
22/// ```
23macro_rules! declare_regex_rule {
24    ($vis:vis $rule:ident, $regex:literal) => {
25        $crate::paste::item! {
26            #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
27            $vis struct $rule<STRING> {
28                _phantom: std::marker::PhantomData<STRING>,
29            }
30
31            impl<STRING: AsRef<str>> $crate::rule::Rule for $rule<STRING> {
32                type Item = STRING;
33
34                fn validate(target: Self::Item) -> Result<Self::Item, $crate::result::Error<Self::Item>> {
35                    let target_as_ref = target.as_ref();
36                    let regex = $crate::rule::Regex::new($regex).expect("invalid regex pattern");
37                    if regex.is_match(target_as_ref) {
38                        Ok(target)
39                    } else {
40                        let message = format!("\"{target_as_ref}\" does not match the regex pattern {regex}");
41                        Err($crate::result::Error::new(target, message))
42                    }
43                }
44            }
45        }
46    };
47    ($(($vis:vis $rule:ident, $regex:literal)),+) => {
48        $(declare_regex_rule!($rule, $regex);)+
49    };
50}
51
52#[cfg(test)]
53mod tests {
54    use crate::rule::Rule;
55
56    declare_regex_rule![
57        EmailRule,
58        r"^[a-zA-Z0-9_.+-]+@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$"
59    ];
60
61    declare_regex_rule![(FooRule, r"foo"), (BarRule, r"bar")];
62
63    #[test]
64    fn test_valid_email() {
65        let valid = "sample@example.com";
66        assert!(EmailRule::validate(&valid).is_ok())
67    }
68
69    #[test]
70    fn test_invalid_email() {
71        let invalid = "example.com";
72        assert!(EmailRule::validate(&invalid).is_err())
73    }
74
75    #[test]
76    fn test_valid_foo() {
77        let valid = "foo";
78        assert!(FooRule::validate(&valid).is_ok())
79    }
80
81    #[test]
82    fn test_invalid_foo() {
83        let invalid = String::from("bar");
84        assert!(FooRule::validate(&invalid).is_err())
85    }
86
87    #[test]
88    fn test_valid_bar() {
89        let valid = String::from("bar");
90        assert!(BarRule::validate(&valid).is_ok())
91    }
92
93    #[test]
94    fn test_invalid_bar() {
95        let invalid = String::from("foo");
96        assert!(BarRule::validate(&invalid).is_err())
97    }
98}