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 no_loop: bool,
119 pub conditions: ConditionGroup,
121 pub actions: Vec<ActionType>,
123}
124
125impl Rule {
126 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 pub fn with_description(mut self, description: String) -> Self {
141 self.description = Some(description);
142 self
143 }
144
145 pub fn with_salience(mut self, salience: i32) -> Self {
147 self.salience = salience;
148 self
149 }
150
151 pub fn with_priority(mut self, priority: i32) -> Self {
153 self.salience = priority;
154 self
155 }
156
157 pub fn with_no_loop(mut self, no_loop: bool) -> Self {
159 self.no_loop = no_loop;
160 self
161 }
162
163 pub fn matches(&self, facts: &HashMap<String, Value>) -> bool {
165 self.enabled && self.conditions.evaluate(facts)
166 }
167}
168
169#[derive(Debug, Clone)]
171pub struct RuleExecutionResult {
172 pub rule_name: String,
174 pub matched: bool,
176 pub actions_executed: Vec<String>,
178 pub execution_time_ms: f64,
180}
181
182impl RuleExecutionResult {
183 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 pub fn matched(mut self) -> Self {
195 self.matched = true;
196 self
197 }
198
199 pub fn with_actions(mut self, actions: Vec<String>) -> Self {
201 self.actions_executed = actions;
202 self
203 }
204
205 pub fn with_execution_time(mut self, time_ms: f64) -> Self {
207 self.execution_time_ms = time_ms;
208 self
209 }
210}
211
212fn 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}