rust_rule_engine/engine/
rule.rs

1use crate::types::{ActionType, LogicalOperator, Operator, Value};
2use std::collections::HashMap;
3
4/// Represents a single condition in a rule
5#[derive(Debug, Clone)]
6pub struct Condition {
7    /// The field name to evaluate
8    pub field: String,
9    /// The comparison operator to use
10    pub operator: Operator,
11    /// The value to compare against
12    pub value: Value,
13}
14
15impl Condition {
16    /// Create a new condition
17    pub fn new(field: String, operator: Operator, value: Value) -> Self {
18        Self {
19            field,
20            operator,
21            value,
22        }
23    }
24
25    /// Evaluate this condition against the given facts
26    pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
27        if let Some(field_value) = get_nested_value(facts, &self.field) {
28            // Use the evaluate method from Operator
29            self.operator.evaluate(field_value, &self.value)
30        } else {
31            false
32        }
33    }
34}
35
36/// Group of conditions with logical operators
37#[derive(Debug, Clone)]
38pub enum ConditionGroup {
39    /// A single condition
40    Single(Condition),
41    /// A compound condition with two sub-conditions and a logical operator
42    Compound {
43        /// The left side condition
44        left: Box<ConditionGroup>,
45        /// The logical operator (AND, OR)
46        operator: LogicalOperator,
47        /// The right side condition
48        right: Box<ConditionGroup>,
49    },
50    /// A negated condition group
51    Not(Box<ConditionGroup>),
52}
53
54impl ConditionGroup {
55    /// Create a single condition group
56    pub fn single(condition: Condition) -> Self {
57        ConditionGroup::Single(condition)
58    }
59
60    /// Create a compound condition using logical AND operator
61    pub fn and(left: ConditionGroup, right: ConditionGroup) -> Self {
62        ConditionGroup::Compound {
63            left: Box::new(left),
64            operator: LogicalOperator::And,
65            right: Box::new(right),
66        }
67    }
68
69    /// Create a compound condition using logical OR operator
70    pub fn or(left: ConditionGroup, right: ConditionGroup) -> Self {
71        ConditionGroup::Compound {
72            left: Box::new(left),
73            operator: LogicalOperator::Or,
74            right: Box::new(right),
75        }
76    }
77
78    /// Create a negated condition using logical NOT operator
79    #[allow(clippy::should_implement_trait)]
80    pub fn not(condition: ConditionGroup) -> Self {
81        ConditionGroup::Not(Box::new(condition))
82    }
83
84    /// Evaluate this condition group against facts
85    pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
86        match self {
87            ConditionGroup::Single(condition) => condition.evaluate(facts),
88            ConditionGroup::Compound {
89                left,
90                operator,
91                right,
92            } => {
93                let left_result = left.evaluate(facts);
94                let right_result = right.evaluate(facts);
95                match operator {
96                    LogicalOperator::And => left_result && right_result,
97                    LogicalOperator::Or => left_result || right_result,
98                    LogicalOperator::Not => !left_result, // For Not, we ignore right side
99                }
100            }
101            ConditionGroup::Not(condition) => !condition.evaluate(facts),
102        }
103    }
104}
105
106/// A rule with conditions and actions
107#[derive(Debug, Clone)]
108pub struct Rule {
109    /// The unique name of the rule
110    pub name: String,
111    /// Optional description of what the rule does
112    pub description: Option<String>,
113    /// Priority of the rule (higher values execute first)
114    pub salience: i32,
115    /// Whether the rule is enabled for execution
116    pub enabled: bool,
117    /// The conditions that must be met for the rule to fire
118    pub conditions: ConditionGroup,
119    /// The actions to execute when the rule fires
120    pub actions: Vec<ActionType>,
121}
122
123impl Rule {
124    /// Create a new rule with the given name, conditions, and actions
125    pub fn new(name: String, conditions: ConditionGroup, actions: Vec<ActionType>) -> Self {
126        Self {
127            name,
128            description: None,
129            salience: 0,
130            enabled: true,
131            conditions,
132            actions,
133        }
134    }
135
136    /// Add a description to the rule
137    pub fn with_description(mut self, description: String) -> Self {
138        self.description = Some(description);
139        self
140    }
141
142    /// Set the salience (priority) of the rule
143    pub fn with_salience(mut self, salience: i32) -> Self {
144        self.salience = salience;
145        self
146    }
147
148    /// Set the priority of the rule (alias for salience)
149    pub fn with_priority(mut self, priority: i32) -> Self {
150        self.salience = priority;
151        self
152    }
153
154    /// Check if this rule matches the given facts
155    pub fn matches(&self, facts: &HashMap<String, Value>) -> bool {
156        self.enabled && self.conditions.evaluate(facts)
157    }
158}
159
160/// Result of rule execution
161#[derive(Debug, Clone)]
162pub struct RuleExecutionResult {
163    /// The name of the rule that was executed
164    pub rule_name: String,
165    /// Whether the rule's conditions matched and it fired
166    pub matched: bool,
167    /// List of actions that were executed
168    pub actions_executed: Vec<String>,
169    /// Time taken to execute the rule in milliseconds
170    pub execution_time_ms: f64,
171}
172
173impl RuleExecutionResult {
174    /// Create a new rule execution result
175    pub fn new(rule_name: String) -> Self {
176        Self {
177            rule_name,
178            matched: false,
179            actions_executed: Vec::new(),
180            execution_time_ms: 0.0,
181        }
182    }
183
184    /// Mark the rule as matched
185    pub fn matched(mut self) -> Self {
186        self.matched = true;
187        self
188    }
189
190    /// Set the actions that were executed
191    pub fn with_actions(mut self, actions: Vec<String>) -> Self {
192        self.actions_executed = actions;
193        self
194    }
195
196    /// Set the execution time in milliseconds
197    pub fn with_execution_time(mut self, time_ms: f64) -> Self {
198        self.execution_time_ms = time_ms;
199        self
200    }
201}
202
203/// Helper function to get nested values from a HashMap
204fn get_nested_value<'a>(data: &'a HashMap<String, Value>, path: &str) -> Option<&'a Value> {
205    let parts: Vec<&str> = path.split('.').collect();
206    let mut current = data.get(parts[0])?;
207
208    for part in parts.iter().skip(1) {
209        match current {
210            Value::Object(obj) => {
211                current = obj.get(*part)?;
212            }
213            _ => return None,
214        }
215    }
216
217    Some(current)
218}