rust_rule_engine/engine/
rule.rs1use crate::types::{ActionType, LogicalOperator, Operator, Value};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone)]
6pub struct Condition {
7    pub field: String,
9    pub operator: Operator,
11    pub value: Value,
13}
14
15impl Condition {
16    pub fn new(field: String, operator: Operator, value: Value) -> Self {
18        Self {
19            field,
20            operator,
21            value,
22        }
23    }
24
25    pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
27        if let Some(field_value) = get_nested_value(facts, &self.field) {
28            self.operator.evaluate(field_value, &self.value)
30        } else {
31            false
32        }
33    }
34}
35
36#[derive(Debug, Clone)]
38pub enum ConditionGroup {
39    Single(Condition),
41    Compound {
43        left: Box<ConditionGroup>,
45        operator: LogicalOperator,
47        right: Box<ConditionGroup>,
49    },
50    Not(Box<ConditionGroup>),
52}
53
54impl ConditionGroup {
55    pub fn single(condition: Condition) -> Self {
57        ConditionGroup::Single(condition)
58    }
59
60    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    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    #[allow(clippy::should_implement_trait)]
80    pub fn not(condition: ConditionGroup) -> Self {
81        ConditionGroup::Not(Box::new(condition))
82    }
83
84    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, }
100            }
101            ConditionGroup::Not(condition) => !condition.evaluate(facts),
102        }
103    }
104}
105
106#[derive(Debug, Clone)]
108pub struct Rule {
109    pub name: String,
111    pub description: Option<String>,
113    pub salience: i32,
115    pub enabled: bool,
117    pub conditions: ConditionGroup,
119    pub actions: Vec<ActionType>,
121}
122
123impl Rule {
124    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    pub fn with_description(mut self, description: String) -> Self {
138        self.description = Some(description);
139        self
140    }
141
142    pub fn with_salience(mut self, salience: i32) -> Self {
144        self.salience = salience;
145        self
146    }
147
148    pub fn with_priority(mut self, priority: i32) -> Self {
150        self.salience = priority;
151        self
152    }
153
154    pub fn matches(&self, facts: &HashMap<String, Value>) -> bool {
156        self.enabled && self.conditions.evaluate(facts)
157    }
158}
159
160#[derive(Debug, Clone)]
162pub struct RuleExecutionResult {
163    pub rule_name: String,
165    pub matched: bool,
167    pub actions_executed: Vec<String>,
169    pub execution_time_ms: f64,
171}
172
173impl RuleExecutionResult {
174    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    pub fn matched(mut self) -> Self {
186        self.matched = true;
187        self
188    }
189
190    pub fn with_actions(mut self, actions: Vec<String>) -> Self {
192        self.actions_executed = actions;
193        self
194    }
195
196    pub fn with_execution_time(mut self, time_ms: f64) -> Self {
198        self.execution_time_ms = time_ms;
199        self
200    }
201}
202
203fn 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}