url_cleaner_engine/glue/
regex.rs

1//! A lazily compiled wrapper around [`Regex`].
2
3use std::str::FromStr;
4use std::sync::OnceLock;
5
6use serde::{Serialize, Deserialize};
7use regex::Regex;
8
9use crate::util::*;
10
11pub mod regex_parts;
12pub use regex_parts::*;
13
14/// A lazily compiled [`Regex`].
15/// # Examples
16/// ```
17/// use url_cleaner_engine::glue::*;
18///
19/// let regex = RegexWrapper::from("abc");
20/// assert!(regex.get().unwrap().is_match("abc"));
21///
22/// // Trying to set flags like `/.../i` isn't supported by the regex crate, and so isn't supported here.
23/// assert_eq!(RegexWrapper::from("/abc/i").parts().pattern, "/abc/i");
24/// ```
25#[derive(Clone, Debug, Serialize, Deserialize, Suitability)]
26#[serde(from = "RegexParts", into = "RegexParts")]
27pub struct RegexWrapper {
28    /// The lazily initialized [`Regex`].
29    #[suitable(always)]
30    regex: OnceLock<Regex>,
31    /// The [`RegexParts`] to compile [`Self::regex`] from.
32    parts: RegexParts
33}
34
35impl RegexWrapper {
36    /// Gets the [`RegexParts`] this uses to compile its [`Regex`].
37    /// # Examples
38    /// ```
39    /// use url_cleaner_engine::glue::*;
40    ///
41    /// let regex = RegexWrapper::from(".*=.*");
42    /// assert_eq!(regex.parts(), &RegexParts {pattern: ".*=.*".into(), config: Default::default()});
43    /// ```
44    pub fn parts(&self) -> &RegexParts {
45        &self.parts
46    }
47
48    /// Get the compiled [`Regex`] if it's been compiled.
49    /// # Examples
50    /// ```
51    /// use url_cleaner_engine::glue::*;
52    ///
53    /// let regex = RegexWrapper::from(".*=.*");
54    /// assert!(regex.get_no_compile().is_none());
55    /// regex.get().unwrap();
56    /// assert!(regex.get_no_compile().is_some());
57    /// ```
58    pub fn get_no_compile(&self) -> Option<&Regex> {
59        self.regex.get()
60    }
61
62    /// Gets the compiled [`Regex`] or, if it hasn't been compiled, compiles it.
63    ///
64    /// Currently, if a call to [`Self::get`] returns an error, the next call will attempt to compile the [`Regex`] *again*.
65    ///
66    /// Given this should be pretty rare and the cost of storing the error is pretty high, I choose to consider a reasonable tradeoff.
67    /// # Errors
68    /// If the cache is unset and the call to [`RegexParts::build`] returns an error, that error is returned.
69    /// # Examples
70    /// ```
71    /// use url_cleaner_engine::glue::*;
72    ///
73    /// let regex = RegexWrapper::from(".*=.*");
74    /// assert!(regex.get_no_compile().is_none());
75    /// regex.get().unwrap();
76    /// assert!(regex.get_no_compile().is_some());
77    /// ```
78    pub fn get(&self) -> Result<&Regex, regex::Error> {
79        if let Some(regex) = self.regex.get() {
80            Ok(regex)
81        } else {
82            let temp = self.parts.build()?;
83            Ok(self.regex.get_or_init(|| temp))
84        }
85    }
86}
87
88impl From<RegexParts> for RegexWrapper {
89    fn from(parts: RegexParts) -> Self {
90        Self {
91            regex: OnceLock::new(),
92            parts
93        }
94    }
95}
96
97impl FromStr for RegexWrapper {
98    type Err = std::convert::Infallible;
99
100    /// Creates a new [`RegexParts`] and uses that.
101    ///
102    /// The regex is only compiled later.
103    fn from_str(s: &str) -> Result<Self, Self::Err> {
104        Ok(RegexParts::from(s).into())
105    }
106}
107
108impl From<&str> for RegexWrapper {
109
110    /// Creates a new [`RegexParts`] and uses that.
111    ///
112    /// The regex is only compiled later.
113    fn from(s: &str) -> Self {
114        RegexParts::from(s).into()
115    }
116}
117
118impl PartialEq for RegexWrapper {
119    /// Whether or not `self` and/or `other` have their [`Regex`] cached is not considered.
120    fn eq(&self, other: &Self) -> bool {
121        self.parts.eq(&other.parts)
122    }
123}
124impl Eq for RegexWrapper {}
125
126impl From<RegexWrapper> for RegexParts {
127    fn from(value: RegexWrapper) -> Self {
128        value.parts
129    }
130}
131
132impl AsRef<RegexParts> for RegexWrapper {
133    fn as_ref(&self) -> &RegexParts {
134        &self.parts
135    }
136}
137
138impl TryFrom<RegexWrapper> for Regex {
139    type Error = regex::Error;
140
141    /// Does not re-compile or clone the [`Regex`] if it's already cached. It simply takes it.
142    fn try_from(value: RegexWrapper) -> Result<Self, Self::Error> {
143        if let Some(regex) = value.regex.into_inner() {
144            Ok(regex)
145        } else {
146            value.parts.build()
147        }
148    }
149}