garde/rules/
pattern.rs

1//! Pattern validation.
2//!
3//! The pattern argument can be a regular expression provided as a string literal, which is then parsed by the [`regex`] crate (if the `regex` feature is enabled).
4//!
5//! ```rust
6//! #[derive(garde::Validate)]
7//! struct Test {
8//!     #[garde(pattern(r"[a-zA-Z0-9][a-zA-Z0-9_]+"))]
9//!     v: String,
10//! }
11//! ```
12//!
13//! Alternatively, it can be an expression of type implementing [`Matcher`] or one that dereferences to a [`Matcher`].
14//! [`Matcher`] is implemented for `regex::Regex` (if the `regex` feature is enabled) and `std::sync::LazyLock<T>` / `once_cell::sync::Lazy<T>` with any `T: Matcher`.
15//! Please note that the expression will be evaluated each time `validate` is called, so avoid doing any expensive work in the expression.
16//! If the work is unavoidable, at least try to amortize it, such as by using `std::sync::LazyLock` or `once_cell::Lazy`.
17//!
18//! ```rust
19//! use std::sync::LazyLock;
20//! use regex::Regex;
21//!
22//! static LAZY_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[a-zA-Z0-9][a-zA-Z0-9_]+").unwrap());
23//!
24//! #[derive(garde::Validate)]
25//! struct Test {
26//!     #[garde(pattern(LAZY_RE))]
27//!     v: String,
28//! }
29//! ```
30//!
31//! ```rust
32//! use once_cell::sync::Lazy;
33//! use regex::Regex;
34//!
35//! static LAZY_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[a-zA-Z0-9][a-zA-Z0-9_]+").unwrap());
36//!
37//! #[derive(garde::Validate)]
38//! struct Test {
39//!     #[garde(pattern(LAZY_RE))]
40//!     v: String,
41//! }
42//! ```
43//!
44//! The entrypoint is the [`Pattern`] trait. Implementing this trait for a type allows that type to be used with the `#[garde(pattern(...))]` rule.
45//!
46//! This trait has a blanket implementation for all `T: garde::rules::AsStr`.
47
48use super::AsStr;
49use crate::error::Error;
50
51pub fn apply<T: Pattern, M: Matcher>(v: &T, (pat,): (&M,)) -> Result<(), Error> {
52    if !v.validate_pattern(pat) {
53        return Err(Error::new(format!(
54            "does not match pattern /{}/",
55            pat.as_str()
56        )));
57    }
58    Ok(())
59}
60
61pub trait Matcher: AsStr {
62    /// Returns true if and only if there is a match for the pattern anywhere in the haystack given.
63    fn is_match(&self, haystack: &str) -> bool;
64}
65
66impl<T: Matcher> Matcher for std::sync::LazyLock<T> {
67    fn is_match(&self, haystack: &str) -> bool {
68        std::sync::LazyLock::force(self).is_match(haystack)
69    }
70}
71
72impl<T: AsStr> AsStr for std::sync::LazyLock<T> {
73    fn as_str(&self) -> &str {
74        std::sync::LazyLock::force(self).as_str()
75    }
76}
77
78pub trait Pattern {
79    fn validate_pattern<M: Matcher>(&self, matcher: &M) -> bool;
80}
81
82impl<T: AsStr> Pattern for T {
83    fn validate_pattern<M: Matcher>(&self, matcher: &M) -> bool {
84        matcher.is_match(self.as_str())
85    }
86}
87
88impl<T: Pattern> Pattern for Option<T> {
89    fn validate_pattern<M: Matcher>(&self, matcher: &M) -> bool {
90        match self {
91            Some(value) => value.validate_pattern(matcher),
92            None => true,
93        }
94    }
95}
96
97#[cfg(all(
98    feature = "regex",
99    feature = "js-sys",
100    target_arch = "wasm32",
101    target_os = "unknown"
102))]
103#[doc(hidden)]
104pub mod regex_js_sys {
105    pub use js_sys::RegExp;
106
107    use super::*;
108
109    impl Matcher for RegExp {
110        fn is_match(&self, haystack: &str) -> bool {
111            self.test(haystack)
112        }
113    }
114
115    impl AsStr for RegExp {
116        fn as_str(&self) -> &str {
117            "[Not supported in JS]"
118        }
119    }
120
121    pub struct SyncWrapper<T>(T);
122
123    impl<T> SyncWrapper<T> {
124        /// Safety: You have to ensure that this value is never shared or sent between threads unless the inner value supports it
125        pub const unsafe fn new(inner: T) -> Self {
126            Self(inner)
127        }
128    }
129
130    impl<T: AsStr> AsStr for SyncWrapper<T> {
131        fn as_str(&self) -> &str {
132            self.0.as_str()
133        }
134    }
135
136    impl<T: Matcher> Matcher for SyncWrapper<T> {
137        fn is_match(&self, haystack: &str) -> bool {
138            self.0.is_match(haystack)
139        }
140    }
141
142    unsafe impl<T> Send for SyncWrapper<T> {}
143    unsafe impl<T> Sync for SyncWrapper<T> {}
144
145    pub type StaticPattern = std::sync::LazyLock<SyncWrapper<RegExp>>;
146
147    #[macro_export]
148    macro_rules! __init_js_sys_pattern {
149        ($pat:literal) => {
150            $crate::rules::pattern::regex_js_sys::StaticPattern::new(|| {
151                // Safety: `wasm32-unknown-unknown` is inherently single-threaded. Therefore `Send` and `Sync` aren't really relevant
152                unsafe { $crate::rules::pattern::regex_js_sys::SyncWrapper::new(::js_sys::RegExp::new($pat, "u")) }
153            })
154        };
155    }
156    pub use crate::__init_js_sys_pattern as init_pattern;
157}
158
159#[cfg(feature = "regex")]
160#[doc(hidden)]
161pub mod regex {
162    pub use regex::Regex;
163
164    use super::{AsStr, Matcher};
165
166    impl Matcher for Regex {
167        fn is_match(&self, haystack: &str) -> bool {
168            self.is_match(haystack)
169        }
170    }
171
172    impl<T: Matcher> Matcher for once_cell::sync::Lazy<T> {
173        fn is_match(&self, haystack: &str) -> bool {
174            once_cell::sync::Lazy::force(self).is_match(haystack)
175        }
176    }
177
178    impl AsStr for Regex {
179        fn as_str(&self) -> &str {
180            self.as_str()
181        }
182    }
183
184    impl<T: AsStr> AsStr for once_cell::sync::Lazy<T> {
185        fn as_str(&self) -> &str {
186            once_cell::sync::Lazy::force(self).as_str()
187        }
188    }
189
190    pub type StaticPattern = std::sync::LazyLock<Regex>;
191
192    #[macro_export]
193    macro_rules! __init_pattern {
194        ($pat:literal) => {
195            $crate::rules::pattern::regex::StaticPattern::new(|| {
196                $crate::rules::pattern::regex::Regex::new($pat).unwrap()
197            })
198        };
199    }
200    pub use crate::__init_pattern as init_pattern;
201}