1use crate::types::{ActionType, LogicalOperator, Operator, Value};
2use chrono::{DateTime, Utc};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
7pub struct Condition {
8 pub field: String,
10 pub operator: Operator,
12 pub value: Value,
14}
15
16impl Condition {
17 pub fn new(field: String, operator: Operator, value: Value) -> Self {
19 Self {
20 field,
21 operator,
22 value,
23 }
24 }
25
26 pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
28 if let Some(field_value) = get_nested_value(facts, &self.field) {
29 self.operator.evaluate(field_value, &self.value)
31 } else {
32 false
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
39pub enum ConditionGroup {
40 Single(Condition),
42 Compound {
44 left: Box<ConditionGroup>,
46 operator: LogicalOperator,
48 right: Box<ConditionGroup>,
50 },
51 Not(Box<ConditionGroup>),
53}
54
55impl ConditionGroup {
56 pub fn single(condition: Condition) -> Self {
58 ConditionGroup::Single(condition)
59 }
60
61 pub fn and(left: ConditionGroup, right: ConditionGroup) -> Self {
63 ConditionGroup::Compound {
64 left: Box::new(left),
65 operator: LogicalOperator::And,
66 right: Box::new(right),
67 }
68 }
69
70 pub fn or(left: ConditionGroup, right: ConditionGroup) -> Self {
72 ConditionGroup::Compound {
73 left: Box::new(left),
74 operator: LogicalOperator::Or,
75 right: Box::new(right),
76 }
77 }
78
79 #[allow(clippy::should_implement_trait)]
81 pub fn not(condition: ConditionGroup) -> Self {
82 ConditionGroup::Not(Box::new(condition))
83 }
84
85 pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
87 match self {
88 ConditionGroup::Single(condition) => condition.evaluate(facts),
89 ConditionGroup::Compound {
90 left,
91 operator,
92 right,
93 } => {
94 let left_result = left.evaluate(facts);
95 let right_result = right.evaluate(facts);
96 match operator {
97 LogicalOperator::And => left_result && right_result,
98 LogicalOperator::Or => left_result || right_result,
99 LogicalOperator::Not => !left_result, }
101 }
102 ConditionGroup::Not(condition) => !condition.evaluate(facts),
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
109pub struct Rule {
110 pub name: String,
112 pub description: Option<String>,
114 pub salience: i32,
116 pub enabled: bool,
118 pub no_loop: bool,
120 pub lock_on_active: bool,
122 pub agenda_group: Option<String>,
124 pub activation_group: Option<String>,
126 pub date_effective: Option<DateTime<Utc>>,
128 pub date_expires: Option<DateTime<Utc>>,
130 pub conditions: ConditionGroup,
132 pub actions: Vec<ActionType>,
134}
135
136impl Rule {
137 pub fn new(name: String, conditions: ConditionGroup, actions: Vec<ActionType>) -> Self {
139 Self {
140 name,
141 description: None,
142 salience: 0,
143 enabled: true,
144 no_loop: false,
145 lock_on_active: false,
146 agenda_group: None,
147 activation_group: None,
148 date_effective: None,
149 date_expires: None,
150 conditions,
151 actions,
152 }
153 }
154
155 pub fn with_description(mut self, description: String) -> Self {
157 self.description = Some(description);
158 self
159 }
160
161 pub fn with_salience(mut self, salience: i32) -> Self {
163 self.salience = salience;
164 self
165 }
166
167 pub fn with_priority(mut self, priority: i32) -> Self {
169 self.salience = priority;
170 self
171 }
172
173 pub fn with_no_loop(mut self, no_loop: bool) -> Self {
175 self.no_loop = no_loop;
176 self
177 }
178
179 pub fn with_lock_on_active(mut self, lock_on_active: bool) -> Self {
181 self.lock_on_active = lock_on_active;
182 self
183 }
184
185 pub fn with_agenda_group(mut self, agenda_group: String) -> Self {
187 self.agenda_group = Some(agenda_group);
188 self
189 }
190
191 pub fn with_activation_group(mut self, activation_group: String) -> Self {
193 self.activation_group = Some(activation_group);
194 self
195 }
196
197 pub fn with_date_effective(mut self, date_effective: DateTime<Utc>) -> Self {
199 self.date_effective = Some(date_effective);
200 self
201 }
202
203 pub fn with_date_expires(mut self, date_expires: DateTime<Utc>) -> Self {
205 self.date_expires = Some(date_expires);
206 self
207 }
208
209 pub fn with_date_effective_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
211 let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
212 self.date_effective = Some(date);
213 Ok(self)
214 }
215
216 pub fn with_date_expires_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
218 let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
219 self.date_expires = Some(date);
220 Ok(self)
221 }
222
223 pub fn is_active_at(&self, timestamp: DateTime<Utc>) -> bool {
225 if let Some(effective) = self.date_effective {
227 if timestamp < effective {
228 return false;
229 }
230 }
231
232 if let Some(expires) = self.date_expires {
234 if timestamp >= expires {
235 return false;
236 }
237 }
238
239 true
240 }
241
242 pub fn is_active(&self) -> bool {
244 self.is_active_at(Utc::now())
245 }
246
247 pub fn matches(&self, facts: &HashMap<String, Value>) -> bool {
249 self.enabled && self.conditions.evaluate(facts)
250 }
251}
252
253#[derive(Debug, Clone)]
255pub struct RuleExecutionResult {
256 pub rule_name: String,
258 pub matched: bool,
260 pub actions_executed: Vec<String>,
262 pub execution_time_ms: f64,
264}
265
266impl RuleExecutionResult {
267 pub fn new(rule_name: String) -> Self {
269 Self {
270 rule_name,
271 matched: false,
272 actions_executed: Vec::new(),
273 execution_time_ms: 0.0,
274 }
275 }
276
277 pub fn matched(mut self) -> Self {
279 self.matched = true;
280 self
281 }
282
283 pub fn with_actions(mut self, actions: Vec<String>) -> Self {
285 self.actions_executed = actions;
286 self
287 }
288
289 pub fn with_execution_time(mut self, time_ms: f64) -> Self {
291 self.execution_time_ms = time_ms;
292 self
293 }
294}
295
296fn get_nested_value<'a>(data: &'a HashMap<String, Value>, path: &str) -> Option<&'a Value> {
298 let parts: Vec<&str> = path.split('.').collect();
299 let mut current = data.get(parts[0])?;
300
301 for part in parts.iter().skip(1) {
302 match current {
303 Value::Object(obj) => {
304 current = obj.get(*part)?;
305 }
306 _ => return None,
307 }
308 }
309
310 Some(current)
311}