1use std::fmt::{Debug, Formatter};
16use std::sync::Arc;
17
18use crate::{HawDBError, Result};
19
20#[derive(Clone)]
21pub struct ValidatedRegex {
22 source: String,
23 compiled: Arc<regex::Regex>,
24}
25
26impl ValidatedRegex {
27 pub fn new(source: impl Into<String>) -> Result<Self> {
28 let source = source.into();
29 let compiled = regex::Regex::new(&source)
30 .map_err(|error| HawDBError::Semantic(format!("invalid regex pattern: {error}")))?;
31 Ok(Self {
32 source,
33 compiled: Arc::new(compiled),
34 })
35 }
36
37 pub fn as_str(&self) -> &str {
38 &self.source
39 }
40
41 pub fn is_match(&self, value: &str) -> bool {
42 self.compiled.is_match(value)
43 }
44}
45
46impl Debug for ValidatedRegex {
47 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
48 formatter
49 .debug_tuple("ValidatedRegex")
50 .field(&self.source)
51 .finish()
52 }
53}
54
55impl PartialEq for ValidatedRegex {
56 fn eq(&self, other: &Self) -> bool {
57 self.source == other.source
58 }
59}
60
61impl Eq for ValidatedRegex {}
62
63#[cfg(test)]
64mod tests {
65 use std::sync::Arc;
66
67 use super::ValidatedRegex;
68
69 #[test]
70 fn clone_reuses_compiled_regex() {
71 let pattern = ValidatedRegex::new("^memory-[0-9]+$").expect("valid regex");
72 let cloned = pattern.clone();
73
74 assert!(Arc::ptr_eq(&pattern.compiled, &cloned.compiled));
75 assert!(cloned.is_match("memory-42"));
76 assert!(!cloned.is_match("thread-42"));
77 }
78
79 #[test]
80 fn equality_and_debug_use_the_source_pattern() {
81 let left = ValidatedRegex::new("memory.*").expect("valid regex");
82 let right = ValidatedRegex::new("memory.*").expect("valid regex");
83
84 assert_eq!(left, right);
85 assert_eq!(left.as_str(), "memory.*");
86 assert_eq!(format!("{left:?}"), "ValidatedRegex(\"memory.*\")");
87 }
88
89 #[test]
90 fn invalid_patterns_fail_during_construction() {
91 assert!(ValidatedRegex::new("[").is_err());
92 }
93}