use aho_corasick::{AhoCorasick, AhoCorasickBuilder, MatchKind};
use crate::classify::errors::{ClassifyError, Result};
use crate::classify::rules::Rule;
pub struct ExactMatcher {
automaton: Option<AhoCorasick>,
pattern_rule_idx: Vec<usize>,
rules: Vec<Rule>,
}
impl ExactMatcher {
pub fn new(rules: &[Rule]) -> Result<Self> {
let mut patterns: Vec<String> = Vec::new();
let mut pattern_rule_idx: Vec<usize> = Vec::new();
for (idx, rule) in rules.iter().enumerate() {
for kw in &rule.keywords {
if kw.is_empty() {
continue;
}
patterns.push(kw.clone());
pattern_rule_idx.push(idx);
}
}
let automaton = if patterns.is_empty() {
None
} else {
let ac = AhoCorasickBuilder::new()
.ascii_case_insensitive(true)
.match_kind(MatchKind::LeftmostLongest)
.build(&patterns)
.map_err(|e| ClassifyError::RuleLoad(format!("aho-corasick build: {e}")))?;
Some(ac)
};
Ok(Self {
automaton,
pattern_rule_idx,
rules: rules.to_vec(),
})
}
pub fn classify(&self, message: &str) -> Option<&Rule> {
let ac = self.automaton.as_ref()?;
let mut best: Option<&Rule> = None;
for m in ac.find_iter(message) {
let rule_idx = self.pattern_rule_idx[m.pattern().as_usize()];
let rule = &self.rules[rule_idx];
best = match best {
Some(prev) if prev.priority >= rule.priority => Some(prev),
_ => Some(rule),
};
}
best
}
}