gatekpr_patterns/
registry.rs1use regex::Regex;
6use std::collections::HashMap;
7use thiserror::Error;
8
9#[derive(Error, Debug)]
11pub enum PatternError {
12 #[error("Invalid regex pattern '{pattern}': {message}")]
14 InvalidPattern { pattern: String, message: String },
15
16 #[error("Pattern not found: {0}")]
18 NotFound(String),
19}
20
21pub type Result<T> = std::result::Result<T, PatternError>;
23
24#[derive(Debug, Clone)]
26pub struct PatternMatch {
27 pub pattern_key: String,
29 pub start: usize,
31 pub end: usize,
33 pub text: String,
35}
36
37#[derive(Default)]
52pub struct PatternRegistry {
53 patterns: HashMap<String, Regex>,
54}
55
56impl PatternRegistry {
57 pub fn new() -> Self {
59 Self::default()
60 }
61
62 pub fn register(&mut self, key: &str, pattern: &str) -> Result<()> {
66 let regex = Regex::new(pattern).map_err(|e| PatternError::InvalidPattern {
67 pattern: pattern.to_string(),
68 message: e.to_string(),
69 })?;
70 self.patterns.insert(key.to_string(), regex);
71 Ok(())
72 }
73
74 pub fn register_many(&mut self, patterns: &[(&str, &str)]) -> Result<()> {
76 for (key, pattern) in patterns {
77 self.register(key, pattern)?;
78 }
79 Ok(())
80 }
81
82 pub fn is_match(&self, key: &str, text: &str) -> bool {
84 self.patterns
85 .get(key)
86 .map(|regex| regex.is_match(text))
87 .unwrap_or(false)
88 }
89
90 pub fn any_match(&self, keys: &[&str], text: &str) -> bool {
92 keys.iter().any(|key| self.is_match(key, text))
93 }
94
95 pub fn all_match(&self, keys: &[&str], text: &str) -> bool {
97 keys.iter().all(|key| self.is_match(key, text))
98 }
99
100 pub fn find_all(&self, key: &str, text: &str) -> Vec<PatternMatch> {
102 self.patterns
103 .get(key)
104 .map(|regex| {
105 regex
106 .find_iter(text)
107 .map(|m| PatternMatch {
108 pattern_key: key.to_string(),
109 start: m.start(),
110 end: m.end(),
111 text: m.as_str().to_string(),
112 })
113 .collect()
114 })
115 .unwrap_or_default()
116 }
117
118 pub fn find_any(&self, keys: &[&str], text: &str) -> Vec<PatternMatch> {
120 keys.iter()
121 .flat_map(|key| self.find_all(key, text))
122 .collect()
123 }
124
125 pub fn find_first(&self, key: &str, text: &str) -> Option<PatternMatch> {
127 self.patterns.get(key).and_then(|regex| {
128 regex.find(text).map(|m| PatternMatch {
129 pattern_key: key.to_string(),
130 start: m.start(),
131 end: m.end(),
132 text: m.as_str().to_string(),
133 })
134 })
135 }
136
137 pub fn get(&self, key: &str) -> Option<&Regex> {
139 self.patterns.get(key)
140 }
141
142 pub fn contains(&self, key: &str) -> bool {
144 self.patterns.contains_key(key)
145 }
146
147 pub fn len(&self) -> usize {
149 self.patterns.len()
150 }
151
152 pub fn is_empty(&self) -> bool {
154 self.patterns.is_empty()
155 }
156
157 pub fn keys(&self) -> impl Iterator<Item = &str> {
159 self.patterns.keys().map(|s| s.as_str())
160 }
161
162 pub fn merge(&mut self, other: PatternRegistry) {
164 self.patterns.extend(other.patterns);
165 }
166
167 pub fn merged(registries: Vec<PatternRegistry>) -> Self {
169 let mut result = Self::new();
170 for registry in registries {
171 result.merge(registry);
172 }
173 result
174 }
175}
176
177pub struct PatternSet {
193 name: String,
194 patterns: Vec<(String, String)>,
195}
196
197impl PatternSet {
198 pub fn new(name: &str) -> Self {
200 Self {
201 name: name.to_string(),
202 patterns: Vec::new(),
203 }
204 }
205
206 pub fn with_pattern(mut self, key: &str, pattern: &str) -> Self {
208 self.patterns.push((key.to_string(), pattern.to_string()));
209 self
210 }
211
212 pub fn with_patterns(mut self, patterns: &[(&str, &str)]) -> Self {
214 for (key, pattern) in patterns {
215 self.patterns.push((key.to_string(), pattern.to_string()));
216 }
217 self
218 }
219
220 pub fn build(self) -> Result<PatternRegistry> {
222 let mut registry = PatternRegistry::new();
223 for (key, pattern) in self.patterns {
224 registry.register(&key, &pattern)?;
225 }
226 Ok(registry)
227 }
228
229 pub fn name(&self) -> &str {
231 &self.name
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_register_and_match() {
241 let mut registry = PatternRegistry::new();
242 registry.register("test", r"\d+").unwrap();
243
244 assert!(registry.is_match("test", "abc123"));
245 assert!(!registry.is_match("test", "abc"));
246 assert!(!registry.is_match("nonexistent", "123"));
247 }
248
249 #[test]
250 fn test_find_all() {
251 let mut registry = PatternRegistry::new();
252 registry.register("digits", r"\d+").unwrap();
253
254 let matches = registry.find_all("digits", "a1b22c333");
255 assert_eq!(matches.len(), 3);
256 assert_eq!(matches[0].text, "1");
257 assert_eq!(matches[1].text, "22");
258 assert_eq!(matches[2].text, "333");
259 }
260
261 #[test]
262 fn test_any_match() {
263 let mut registry = PatternRegistry::new();
264 registry.register("a", r"aaa").unwrap();
265 registry.register("b", r"bbb").unwrap();
266 registry.register("c", r"ccc").unwrap();
267
268 assert!(registry.any_match(&["a", "b"], "xxxaaayyy"));
269 assert!(!registry.any_match(&["a", "b"], "xxxcccyyy"));
270 }
271
272 #[test]
273 fn test_pattern_set() {
274 let registry = PatternSet::new("test")
275 .with_pattern("email", r"[\w\.-]+@[\w\.-]+")
276 .with_pattern("phone", r"\d{3}-\d{4}")
277 .build()
278 .unwrap();
279
280 assert!(registry.is_match("email", "test@example.com"));
281 assert!(registry.is_match("phone", "123-4567"));
282 }
283
284 #[test]
285 fn test_invalid_pattern() {
286 let mut registry = PatternRegistry::new();
287 let result = registry.register("invalid", r"[invalid");
288 assert!(result.is_err());
289 }
290}