1use lru::LruCache;
2use once_cell::sync::Lazy;
3use revision::revisioned;
4use serde::{
5 de::{self, Visitor},
6 Deserialize, Deserializer, Serialize, Serializer,
7};
8use std::cmp::Ordering;
9use std::fmt::Debug;
10use std::fmt::{self, Display, Formatter};
11use std::hash::{Hash, Hasher};
12use std::num::NonZeroUsize;
13use std::str::FromStr;
14use std::sync::Mutex;
15use std::{env, str};
16
17pub(crate) const TOKEN: &str = "$surrealdb::private::crate::Regex";
18
19#[derive(Clone)]
20#[revisioned(revision = 1)]
21pub struct Regex(pub regex::Regex);
22
23impl Regex {
24 pub fn regex(&self) -> ®ex::Regex {
26 &self.0
27 }
28}
29
30fn regex_new(str: &str) -> Result<regex::Regex, regex::Error> {
31 static REGEX_CACHE: Lazy<Mutex<LruCache<String, regex::Regex>>> = Lazy::new(|| {
32 let cache_size: usize = env::var("SURREAL_REGEX_CACHE_SIZE")
33 .map_or(1000, |v| v.parse().unwrap_or(1000))
34 .max(10); Mutex::new(LruCache::new(NonZeroUsize::new(cache_size).unwrap()))
36 });
37 let mut cache = match REGEX_CACHE.lock() {
38 Ok(guard) => guard,
39 Err(poisoned) => poisoned.into_inner(),
40 };
41 if let Some(re) = cache.get(str) {
42 return Ok(re.clone());
43 }
44 let re = regex::Regex::new(str)?;
45 cache.put(str.to_owned(), re.clone());
46 Ok(re)
47}
48
49impl FromStr for Regex {
50 type Err = <regex::Regex as FromStr>::Err;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 if s.contains('\0') {
54 Err(regex::Error::Syntax("regex contained NUL byte".to_owned()))
55 } else {
56 regex_new(&s.replace("\\/", "/")).map(Self)
57 }
58 }
59}
60
61impl PartialEq for Regex {
62 fn eq(&self, other: &Self) -> bool {
63 self.0.as_str().eq(other.0.as_str())
64 }
65}
66
67impl Eq for Regex {}
68
69impl Ord for Regex {
70 fn cmp(&self, other: &Self) -> Ordering {
71 self.0.as_str().cmp(other.0.as_str())
72 }
73}
74
75impl PartialOrd for Regex {
76 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
77 Some(self.cmp(other))
78 }
79}
80
81impl Hash for Regex {
82 fn hash<H: Hasher>(&self, state: &mut H) {
83 self.0.as_str().hash(state);
84 }
85}
86
87impl Debug for Regex {
88 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
89 Display::fmt(self, f)
90 }
91}
92
93impl Display for Regex {
94 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
95 write!(f, "/{}/", &self.0)
96 }
97}
98
99impl Serialize for Regex {
100 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
101 where
102 S: Serializer,
103 {
104 serializer.serialize_newtype_struct(TOKEN, self.0.as_str())
105 }
106}
107
108impl<'de> Deserialize<'de> for Regex {
109 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
110 where
111 D: Deserializer<'de>,
112 {
113 struct RegexNewtypeVisitor;
114
115 impl<'de> Visitor<'de> for RegexNewtypeVisitor {
116 type Value = Regex;
117
118 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
119 formatter.write_str("a regex newtype")
120 }
121
122 fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
123 where
124 D: Deserializer<'de>,
125 {
126 struct RegexVisitor;
127
128 impl<'de> Visitor<'de> for RegexVisitor {
129 type Value = Regex;
130
131 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
132 formatter.write_str("a regex str")
133 }
134
135 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
136 where
137 E: de::Error,
138 {
139 Regex::from_str(value).map_err(|_| de::Error::custom("invalid regex"))
140 }
141 }
142
143 deserializer.deserialize_str(RegexVisitor)
144 }
145 }
146
147 deserializer.deserialize_newtype_struct(TOKEN, RegexNewtypeVisitor)
148 }
149}