1#![feature(arbitrary_self_types_pointers)]
2
3use std::vec;
4
5use anyhow::{Result, bail};
6use bincode::{
7 Decode, Encode,
8 de::Decoder,
9 enc::Encoder,
10 error::{DecodeError, EncodeError},
11 impl_borrow_decode,
12};
13
14#[derive(Debug, Clone)]
18#[turbo_tasks::value(eq = "manual", shared, serialization = "custom")]
19pub struct EsRegex {
20 #[turbo_tasks(trace_ignore)]
21 delegate: EsRegexImpl,
22 pub pattern: String,
25 pub flags: String,
26}
27
28#[derive(Debug, Clone)]
29enum EsRegexImpl {
30 Regex(regex::Regex),
31 Regress(regress::Regex),
32}
33
34impl PartialEq for EsRegex {
39 fn eq(&self, other: &Self) -> bool {
40 self.pattern == other.pattern && self.flags == other.flags
41 }
42}
43impl Eq for EsRegex {}
44
45impl Encode for EsRegex {
46 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
47 self.pattern.encode(encoder)?;
48 self.flags.encode(encoder)?;
49 Ok(())
50 }
51}
52
53impl<Context> Decode<Context> for EsRegex {
54 fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
55 let pattern: String = Decode::decode(decoder)?;
56 let flags: String = Decode::decode(decoder)?;
57 EsRegex::new(&pattern, &flags).map_err(|err| DecodeError::OtherString(err.to_string()))
59 }
60}
61
62impl_borrow_decode!(EsRegex);
63
64impl EsRegex {
65 pub fn new(pattern: &str, flags: &str) -> Result<Self> {
68 let pattern = pattern.replace("\\/", "/");
70
71 let mut applied_flags = String::new();
72 for flag in flags.chars() {
73 match flag {
74 'd' => {}
76 'g' => {}
78 'i' => applied_flags.push('i'),
80 'm' => applied_flags.push('m'),
82 's' => applied_flags.push('s'),
84 'u' => applied_flags.push('u'),
86 'y' => {}
88 _ => bail!("unsupported flag `{flag}` in regex: `{pattern}` with flags: `{flags}`"),
89 }
90 }
91
92 let regex = if !applied_flags.is_empty() {
93 regex::Regex::new(&format!("(?{applied_flags}){pattern}"))
94 } else {
95 regex::Regex::new(&pattern)
96 };
97
98 let delegate = match regex {
99 Ok(reg) => Ok(EsRegexImpl::Regex(reg)),
100 Err(_e) => {
101 match regress::Regex::with_flags(&pattern, regress::Flags::from(flags)) {
104 Ok(reg) => Ok(EsRegexImpl::Regress(reg)),
105 Err(e) => Err(e),
107 }
108 }
109 }?;
110 Ok(Self {
111 delegate,
112 pattern,
113 flags: flags.to_string(),
114 })
115 }
116
117 pub fn is_match(&self, haystack: &str) -> bool {
119 match &self.delegate {
120 EsRegexImpl::Regex(r) => r.is_match(haystack),
121 EsRegexImpl::Regress(r) => r.find(haystack).is_some(),
122 }
123 }
124
125 pub fn as_regex_str(&self) -> Option<&str> {
129 match &self.delegate {
130 EsRegexImpl::Regex(r) => Some(r.as_str()),
131 EsRegexImpl::Regress(_) => None,
132 }
133 }
134
135 pub fn captures<'h>(&self, haystack: &'h str) -> Option<Captures<'h>> {
146 let delegate = match &self.delegate {
147 EsRegexImpl::Regex(r) => CapturesImpl::Regex {
148 captures: r.captures(haystack)?,
149 idx: 0,
150 },
151 EsRegexImpl::Regress(r) => {
152 let re_match = r.find(haystack)?;
153 CapturesImpl::Regress {
154 captures_iter: re_match.captures.into_iter(),
155 haystack,
156 match_range: Some(re_match.range),
157 }
158 }
159 };
160 Some(Captures { delegate })
161 }
162}
163
164#[derive(Debug, Clone)]
170#[turbo_tasks::value(eq = "manual", shared, serialization = "custom")]
171pub struct EsRegexSet {
172 regexes: Vec<EsRegex>,
175 #[turbo_tasks(trace_ignore)]
177 set: Option<regex::RegexSet>,
178 individual: Vec<u32>,
180}
181
182impl PartialEq for EsRegexSet {
183 fn eq(&self, other: &Self) -> bool {
184 self.regexes == other.regexes
185 }
186}
187impl Eq for EsRegexSet {}
188
189impl Encode for EsRegexSet {
190 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
191 self.regexes.encode(encoder)
192 }
193}
194
195impl<Context> Decode<Context> for EsRegexSet {
196 fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
197 let regexes: Vec<EsRegex> = Decode::decode(decoder)?;
198 Ok(EsRegexSet::new(regexes))
199 }
200}
201
202impl_borrow_decode!(EsRegexSet);
203
204impl Default for EsRegexSet {
205 fn default() -> Self {
206 Self::new(Vec::new())
207 }
208}
209
210impl EsRegexSet {
211 pub fn new(regexes: Vec<EsRegex>) -> Self {
215 let set = regex::RegexSet::new(regexes.iter().filter_map(EsRegex::as_regex_str)).ok();
216 let individual = regexes
217 .iter()
218 .enumerate()
219 .filter(|(_, regex)| set.is_none() || regex.as_regex_str().is_none())
220 .map(|(index, _)| index as u32)
221 .collect();
222 Self {
223 regexes,
224 set,
225 individual,
226 }
227 }
228
229 pub fn is_match(&self, haystack: &str) -> bool {
231 if let Some(set) = &self.set
232 && set.is_match(haystack)
233 {
234 return true;
235 }
236 self.individual
237 .iter()
238 .any(|&index| self.regexes[index as usize].is_match(haystack))
239 }
240
241 pub fn is_empty(&self) -> bool {
243 self.regexes.is_empty()
244 }
245}
246
247pub struct Captures<'h> {
248 delegate: CapturesImpl<'h>,
249}
250
251enum CapturesImpl<'h> {
252 Regex {
259 captures: regex::Captures<'h>,
260 idx: usize,
261 },
262 Regress {
264 captures_iter: vec::IntoIter<Option<regress::Range>>,
265 haystack: &'h str,
266 match_range: Option<regress::Range>,
267 },
268}
269
270impl<'h> Iterator for Captures<'h> {
271 type Item = Option<&'h str>;
272
273 fn next(&mut self) -> Option<Self::Item> {
274 match &mut self.delegate {
275 CapturesImpl::Regex { captures, idx } => {
276 if *idx >= captures.len() {
277 None
278 } else {
279 let capture = Some(captures.get(*idx).map(|sub_match| sub_match.as_str()));
280 *idx += 1;
281 capture
282 }
283 }
284 CapturesImpl::Regress {
285 captures_iter,
286 haystack,
287 match_range,
288 } => {
289 if let Some(range) = match_range.take() {
290 Some(Some(&haystack[range]))
292 } else {
293 Some(captures_iter.next()?.map(|range| &haystack[range]))
294 }
295 }
296 }
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::{EsRegex, EsRegexImpl, EsRegexSet};
303
304 #[test]
305 fn es_regex_set_matches_either_delegate() {
306 let set = EsRegexSet::new(vec![
308 EsRegex::new("^/docs", "").unwrap(),
309 EsRegex::new("a(?!b)", "").unwrap(),
310 ]);
311 assert_eq!(set.individual, vec![1]);
312 assert!(set.is_match("/docs/getting-started"));
313 assert!(set.is_match("ac"));
314 assert!(!set.is_match("/blog"));
315 assert!(!set.is_match("ab"));
316 }
317
318 #[test]
319 fn es_regex_set_combines_every_member_when_it_can() {
320 let set = EsRegexSet::new(vec![
321 EsRegex::new("^/docs", "").unwrap(),
322 EsRegex::new("^/blog", "").unwrap(),
323 ]);
324 assert!(set.individual.is_empty());
326 assert!(set.is_match("/docs"));
327 assert!(set.is_match("/blog"));
328 assert!(!set.is_match("/about"));
329 }
330
331 #[test]
332 fn empty_es_regex_set_never_matches() {
333 let set = EsRegexSet::default();
334 assert!(set.is_empty());
335 assert!(!set.is_match(""));
336 assert!(!set.is_match("/docs"));
337 }
338
339 #[test]
340 fn oversized_es_regex_set_falls_back_to_matching_individually() {
341 const N: usize = 60_000;
343 let regexes = vec![
344 EsRegex::new(&format!("^/docs/[0-9a-zA-Z]{{{N}}}"), "").unwrap(),
345 EsRegex::new(&format!("^/blog/[0-9a-zA-Z]{{{N}}}"), "").unwrap(),
346 ];
347 assert!(regexes.iter().all(|regex| regex.as_regex_str().is_some()));
348 let set = EsRegexSet::new(regexes);
349 assert!(set.set.is_none());
350 assert_eq!(set.individual, vec![0, 1]);
351 assert!(set.is_match(&format!("/docs/{}", "a".repeat(N))));
352 assert!(set.is_match(&format!("/blog/{}", "a".repeat(N))));
353 assert!(!set.is_match("/about"));
354 }
355
356 #[test]
357 fn es_regex_set_round_trip_bincode() {
358 let set = EsRegexSet::new(vec![
359 EsRegex::new("^/docs", "").unwrap(),
360 EsRegex::new("a(?!b)", "").unwrap(),
361 ]);
362 let config = bincode::config::standard();
363 let encoded = bincode::encode_to_vec(&set, config).unwrap();
364 let (decoded, len) = bincode::decode_from_slice::<EsRegexSet, _>(&encoded, config).unwrap();
365 assert_eq!(set, decoded);
366 assert_eq!(len, encoded.len());
367 assert!(decoded.is_match("/docs"));
369 assert!(decoded.is_match("ac"));
370 }
371
372 #[test]
373 fn round_trip_bincode() {
374 let regex = EsRegex::new("[a-z]", "i").unwrap();
375 let config = bincode::config::standard();
376 let encoded = bincode::encode_to_vec(®ex, config).unwrap();
377 let (decoded, len) = bincode::decode_from_slice::<EsRegex, _>(&encoded, config).unwrap();
378 assert_eq!(regex, decoded);
379 assert_eq!(len, encoded.len());
380 }
381
382 #[test]
383 fn es_regex_matches_simple() {
384 let regex = EsRegex::new("a", "").unwrap();
385 assert!(matches!(regex.delegate, EsRegexImpl::Regex { .. }));
386 assert!(regex.is_match("a"));
387 }
388
389 #[test]
390 fn es_regex_matches_negative_lookahead() {
391 let regex = EsRegex::new("a(?!b)", "").unwrap();
393 assert!(matches!(regex.delegate, EsRegexImpl::Regress { .. }));
394 assert!(!regex.is_match("ab"));
395 assert!(regex.is_match("ac"));
396 }
397
398 #[test]
399 fn invalid_regex() {
400 assert!(matches!(EsRegex::new("*", ""), Err { .. }))
404 }
405
406 #[test]
407 fn captures_with_regex() {
408 let regex = EsRegex::new(r"(notmatched)|(\d{4})-(\d{2})-(\d{2})", "").unwrap();
409 assert!(matches!(regex.delegate, EsRegexImpl::Regex { .. }));
410
411 let captures = regex.captures("Today is 2024-01-15");
412 assert!(captures.is_some());
413 let caps: Vec<_> = captures.unwrap().collect();
414 assert_eq!(caps.len(), 5); assert_eq!(caps[0], Some("2024-01-15")); assert_eq!(caps[1], None); assert_eq!(caps[2], Some("2024")); assert_eq!(caps[3], Some("01")); assert_eq!(caps[4], Some("15")); }
421
422 #[test]
423 fn captures_with_regress() {
424 let regex = EsRegex::new(r"(\w+)(?=baz)", "").unwrap();
425 assert!(matches!(regex.delegate, EsRegexImpl::Regress { .. }));
426
427 let captures = regex.captures("foobar");
428 assert!(captures.is_none());
429
430 let captures = regex.captures("foobaz");
431 assert!(captures.is_some());
432 let caps: Vec<_> = captures.unwrap().collect();
433 assert_eq!(caps.len(), 2); assert_eq!(caps[0], Some("foo")); assert_eq!(caps[1], Some("foo")); }
437}