rust_rule_engine/rete/
alpha.rs

1//! AlphaNode: checks single condition on a fact
2
3use super::facts::{FactValue, TypedFacts};
4
5#[derive(Debug, Clone)]
6pub struct AlphaNode {
7    pub field: String,
8    pub operator: String,
9    pub value: String,
10}
11
12
13impl AlphaNode {
14    /// Match with string-based facts (backward compatible)
15    pub fn matches(&self, fact_field: &str, fact_value: &str) -> bool {
16        if self.field != fact_field {
17            return false;
18        }
19        match self.operator.as_str() {
20            "==" => fact_value == self.value,
21            "!=" => fact_value != self.value,
22            ">" => parse_num(fact_value) > parse_num(&self.value),
23            "<" => parse_num(fact_value) < parse_num(&self.value),
24            ">=" => parse_num(fact_value) >= parse_num(&self.value),
25            "<=" => parse_num(fact_value) <= parse_num(&self.value),
26            "contains" => fact_value.contains(&self.value),
27            "startsWith" => fact_value.starts_with(&self.value),
28            "endsWith" => fact_value.ends_with(&self.value),
29            "matches" => wildcard_match(fact_value, &self.value),
30            _ => false,
31        }
32    }
33
34    /// Match with typed facts (new!)
35    pub fn matches_typed(&self, facts: &TypedFacts) -> bool {
36        // Parse the value string into appropriate FactValue
37        let expected_value = self.parse_value_string(&self.value);
38        facts.evaluate_condition(&self.field, &self.operator, &expected_value)
39    }
40
41    /// Parse value string into FactValue
42    fn parse_value_string(&self, s: &str) -> FactValue {
43        // Try to parse as different types
44        if let Ok(i) = s.parse::<i64>() {
45            FactValue::Integer(i)
46        } else if let Ok(f) = s.parse::<f64>() {
47            FactValue::Float(f)
48        } else if let Ok(b) = s.parse::<bool>() {
49            FactValue::Boolean(b)
50        } else if s == "null" {
51            FactValue::Null
52        } else {
53            FactValue::String(s.to_string())
54        }
55    }
56
57    /// Create with typed value
58    pub fn with_typed_value(field: String, operator: String, value: FactValue) -> Self {
59        Self {
60            field,
61            operator,
62            value: value.as_string(),
63        }
64    }
65}
66
67fn parse_num(s: &str) -> f64 {
68    s.parse::<f64>().unwrap_or(0.0)
69}
70
71/// Simple wildcard pattern matching (for backward compatibility)
72fn wildcard_match(text: &str, pattern: &str) -> bool {
73    let text_chars: Vec<char> = text.chars().collect();
74    let pattern_chars: Vec<char> = pattern.chars().collect();
75    wildcard_match_impl(&text_chars, &pattern_chars, 0, 0)
76}
77
78fn wildcard_match_impl(text: &[char], pattern: &[char], ti: usize, pi: usize) -> bool {
79    if pi == pattern.len() {
80        return ti == text.len();
81    }
82
83    if pattern[pi] == '*' {
84        for i in ti..=text.len() {
85            if wildcard_match_impl(text, pattern, i, pi + 1) {
86                return true;
87            }
88        }
89        false
90    } else if ti < text.len() && (pattern[pi] == '?' || pattern[pi] == text[ti]) {
91        wildcard_match_impl(text, pattern, ti + 1, pi + 1)
92    } else {
93        false
94    }
95}