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    /// Prevents the rule from activating itself in the same cycle
118    pub no_loop: bool,
119    /// The conditions that must be met for the rule to fire
120    pub conditions: ConditionGroup,
121    /// The actions to execute when the rule fires
122    pub actions: Vec<ActionType>,
123}
124
125impl Rule {
126    /// Create a new rule with the given name, conditions, and actions
127    pub fn new(name: String, conditions: ConditionGroup, actions: Vec<ActionType>) -> Self {
128        Self {
129            name,
130            description: None,
131            salience: 0,
132            enabled: true,
133            no_loop: false,
134            conditions,
135            actions,
136        }
137    }
138
139    /// Add a description to the rule
140    pub fn with_description(mut self, description: String) -> Self {
141        self.description = Some(description);
142        self
143    }
144
145    /// Set the salience (priority) of the rule
146    pub fn with_salience(mut self, salience: i32) -> Self {
147        self.salience = salience;
148        self
149    }
150
151    /// Set the priority of the rule (alias for salience)
152    pub fn with_priority(mut self, priority: i32) -> Self {
153        self.salience = priority;
154        self
155    }
156
157    /// Enable or disable no-loop behavior for this rule
158    pub fn with_no_loop(mut self, no_loop: bool) -> Self {
159        self.no_loop = no_loop;
160        self
161    }
162
163    /// Check if this rule matches the given facts
164    pub fn matches(&self, facts: &HashMap<String, Value>) -> bool {
165        self.enabled && self.conditions.evaluate(facts)
166    }
167}
168
169/// Result of rule execution
170#[derive(Debug, Clone)]
171pub struct RuleExecutionResult {
172    /// The name of the rule that was executed
173    pub rule_name: String,
174    /// Whether the rule's conditions matched and it fired
175    pub matched: bool,
176    /// List of actions that were executed
177    pub actions_executed: Vec<String>,
178    /// Time taken to execute the rule in milliseconds
179    pub execution_time_ms: f64,
180}
181
182impl RuleExecutionResult {
183    /// Create a new rule execution result
184    pub fn new(rule_name: String) -> Self {
185        Self {
186            rule_name,
187            matched: false,
188            actions_executed: Vec::new(),
189            execution_time_ms: 0.0,
190        }
191    }
192
193    /// Mark the rule as matched
194    pub fn matched(mut self) -> Self {
195        self.matched = true;
196        self
197    }
198
199    /// Set the actions that were executed
200    pub fn with_actions(mut self, actions: Vec<String>) -> Self {
201        self.actions_executed = actions;
202        self
203    }
204
205    /// Set the execution time in milliseconds
206    pub fn with_execution_time(mut self, time_ms: f64) -> Self {
207        self.execution_time_ms = time_ms;
208        self
209    }
210}
211
212/// Helper function to get nested values from a HashMap
213fn get_nested_value<'a>(data: &'a HashMap<String, Value>, path: &str) -> Option<&'a Value> {
214    let parts: Vec<&str> = path.split('.').collect();
215    let mut current = data.get(parts[0])?;
216
217    for part in parts.iter().skip(1) {
218        match current {
219            Value::Object(obj) => {
220                current = obj.get(*part)?;
221            }
222            _ => return None,
223        }
224    }
225
226    Some(current)
227}