1use crate::ConceptId;
2use once_cell::sync::OnceCell;
3use regex::Regex;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Pattern {
8 id: ConceptId,
9 name: String,
10 namespace: String,
11 regex: String,
12 #[serde(skip)]
13 #[serde(default = "Pattern::default_compiled")]
14 compiled: OnceCell<Regex>,
15}
16
17impl Pattern {
18 pub fn new(
19 name: impl Into<String>,
20 namespace: impl Into<String>,
21 regex: impl Into<String>,
22 ) -> Result<Self, String> {
23 let name = name.into();
24 let namespace = namespace.into();
25 let regex_string = regex.into();
26
27 Regex::new(®ex_string)
29 .map_err(|e| format!("Invalid regex for pattern '{}': {}", name, e))?;
30
31 Ok(Self {
32 id: ConceptId::from_concept(&namespace, &name),
33 name,
34 namespace,
35 regex: regex_string,
36 compiled: OnceCell::new(),
37 })
38 }
39
40 pub fn id(&self) -> &ConceptId {
41 &self.id
42 }
43
44 pub fn name(&self) -> &str {
45 &self.name
46 }
47
48 pub fn namespace(&self) -> &str {
49 &self.namespace
50 }
51
52 pub fn regex(&self) -> &str {
53 &self.regex
54 }
55
56 pub fn is_match(&self, candidate: &str) -> Result<bool, String> {
57 let compiled = self
58 .compiled
59 .get_or_try_init(|| Regex::new(&self.regex).map_err(|e| e.to_string()))?
60 .clone();
61
62 Ok(compiled.is_match(candidate))
63 }
64
65 fn default_compiled() -> OnceCell<Regex> {
66 OnceCell::new()
67 }
68}