sea_core/
patterns.rs

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        // Validate regex eagerly so parse-time errors are surfaced
28        Regex::new(&regex_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}