1use std::{ops::Deref, str::FromStr, sync::Arc};
4
5pub use anyhow::Error;
6use anyhow::{Context, Result};
7use dashmap::DashMap;
8use once_cell::sync::Lazy;
9use regex::Regex;
10use rustc_hash::FxBuildHasher;
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone)]
18pub struct CachedRegex {
19 regex: Arc<Regex>,
20}
21
22impl Deref for CachedRegex {
23 type Target = Regex;
24
25 fn deref(&self) -> &Self::Target {
26 &self.regex
27 }
28}
29
30impl CachedRegex {
31 pub fn new(input: &str) -> Result<Self> {
34 static CACHE: Lazy<DashMap<String, Arc<Regex>, FxBuildHasher>> =
35 Lazy::new(Default::default);
36
37 if let Some(cache) = CACHE.get(input).as_deref().cloned() {
38 return Ok(Self { regex: cache });
39 }
40
41 let regex =
42 Regex::new(input).with_context(|| format!("failed to parse `{}` as regex", input))?;
43 let regex = Arc::new(regex);
44
45 CACHE.insert(input.to_owned(), regex.clone());
46
47 Ok(CachedRegex { regex })
48 }
49}
50
51impl<'de> Deserialize<'de> for CachedRegex {
52 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
53 where
54 D: serde::Deserializer<'de>,
55 {
56 use serde::de::Error;
57
58 let s = String::deserialize(deserializer)?;
59
60 Self::new(&s).map_err(|err| D::Error::custom(err.to_string()))
61 }
62}
63
64impl Serialize for CachedRegex {
65 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
66 where
67 S: serde::Serializer,
68 {
69 let s = self.regex.as_str();
70
71 serializer.serialize_str(s)
72 }
73}
74
75impl From<&'_ str> for CachedRegex {
77 fn from(s: &'_ str) -> Self {
78 Self::new(s).unwrap()
79 }
80}
81
82impl FromStr for CachedRegex {
83 type Err = Error;
84
85 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 Self::new(s)
87 }
88}