surrealdb_sql/
regex.rs

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	// Deref would expose `regex::Regex::as_str` which wouldn't have the '/' delimiters.
25	pub fn regex(&self) -> &regex::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); // The minimum cache size is 10
35		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}