use regex::Regex;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
fn cache() -> &'static Mutex<HashMap<String, Arc<Regex>>> {
static CACHE: OnceLock<Mutex<HashMap<String, Arc<Regex>>>> = OnceLock::new();
CACHE.get_or_init(|| Mutex::new(HashMap::new()))
}
pub(crate) fn compiled_regex(pattern: &str) -> Option<Arc<Regex>> {
{
let guard = cache().lock().unwrap_or_else(|e| e.into_inner());
if let Some(re) = guard.get(pattern) {
return Some(Arc::clone(re));
}
}
let compiled = Arc::new(Regex::new(pattern).ok()?);
let mut guard = cache().lock().unwrap_or_else(|e| e.into_inner());
Some(Arc::clone(
guard.entry(pattern.to_string()).or_insert(compiled),
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_same_compiled_regex_across_calls() {
let first = compiled_regex(r"^foo\d+$").unwrap();
let second = compiled_regex(r"^foo\d+$").unwrap();
assert!(Arc::ptr_eq(&first, &second));
}
#[test]
fn caches_distinct_patterns_independently() {
let a = compiled_regex(r"^a+$").unwrap();
let b = compiled_regex(r"^b+$").unwrap();
assert!(!Arc::ptr_eq(&a, &b));
assert!(a.is_match("aaaa"));
assert!(b.is_match("bbbb"));
assert!(!a.is_match("bbbb"));
}
#[test]
fn invalid_pattern_returns_none_without_caching() {
assert!(compiled_regex(r"(unclosed").is_none());
assert!(compiled_regex(r"^x$").is_some());
}
}