1use crate::types::{ActionType, LogicalOperator, Operator, Value};
2use chrono::{DateTime, Utc};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
7pub enum ConditionExpression {
8 Field(String),
10 FunctionCall {
12 name: String,
14 args: Vec<String>,
16 },
17 Test {
20 name: String,
22 args: Vec<String>,
24 },
25}
26
27#[derive(Debug, Clone)]
29pub struct Condition {
30 pub expression: ConditionExpression,
32 pub operator: Operator,
34 pub value: Value,
36
37 #[deprecated(note = "Use expression instead")]
39 #[doc(hidden)]
40 pub field: String,
41}
42
43impl Condition {
44 pub fn new(field: String, operator: Operator, value: Value) -> Self {
46 Self {
47 expression: ConditionExpression::Field(field.clone()),
48 operator,
49 value,
50 field, }
52 }
53
54 pub fn with_function(
56 function_name: String,
57 args: Vec<String>,
58 operator: Operator,
59 value: Value,
60 ) -> Self {
61 Self {
62 expression: ConditionExpression::FunctionCall {
63 name: function_name.clone(),
64 args,
65 },
66 operator,
67 value,
68 field: function_name, }
70 }
71
72 pub fn with_test(function_name: String, args: Vec<String>) -> Self {
75 Self {
76 expression: ConditionExpression::Test {
77 name: function_name.clone(),
78 args,
79 },
80 operator: Operator::Equal, value: Value::Boolean(true), field: format!("test({})", function_name), }
84 }
85
86 pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
88 match &self.expression {
89 ConditionExpression::Field(field_name) => {
90 if let Some(field_value) = get_nested_value(facts, field_name) {
91 self.operator.evaluate(field_value, &self.value)
92 } else {
93 false
94 }
95 }
96 ConditionExpression::FunctionCall { .. } | ConditionExpression::Test { .. } => {
97 false
99 }
100 }
101 }
102
103 pub fn evaluate_with_engine(
106 &self,
107 facts: &HashMap<String, Value>,
108 function_registry: &HashMap<
109 String,
110 std::sync::Arc<dyn Fn(Vec<Value>, &HashMap<String, Value>) -> crate::errors::Result<Value> + Send + Sync>,
111 >,
112 ) -> bool {
113 match &self.expression {
114 ConditionExpression::Field(field_name) => {
115 if let Some(field_value) = get_nested_value(facts, field_name) {
116 self.operator.evaluate(field_value, &self.value)
117 } else {
118 false
119 }
120 }
121 ConditionExpression::FunctionCall { name, args } => {
122 if let Some(function) = function_registry.get(name) {
124 let arg_values: Vec<Value> = args
126 .iter()
127 .map(|arg| {
128 get_nested_value(facts, arg)
129 .cloned()
130 .unwrap_or(Value::String(arg.clone()))
131 })
132 .collect();
133
134 if let Ok(result) = function(arg_values, facts) {
136 return self.operator.evaluate(&result, &self.value);
138 }
139 }
140 false
141 }
142 ConditionExpression::Test { name, args } => {
143 if let Some(function) = function_registry.get(name) {
145 let arg_values: Vec<Value> = args
147 .iter()
148 .map(|arg| {
149 get_nested_value(facts, arg)
150 .cloned()
151 .unwrap_or(Value::String(arg.clone()))
152 })
153 .collect();
154
155 if let Ok(result) = function(arg_values, facts) {
157 match result {
159 Value::Boolean(b) => return b,
160 Value::Integer(i) => return i != 0,
161 Value::Number(f) => return f != 0.0,
162 Value::String(s) => return !s.is_empty(),
163 _ => return false,
164 }
165 }
166 }
167 false
168 }
169 }
170 }
171}
172
173#[derive(Debug, Clone)]
175pub enum ConditionGroup {
176 Single(Condition),
178 Compound {
180 left: Box<ConditionGroup>,
182 operator: LogicalOperator,
184 right: Box<ConditionGroup>,
186 },
187 Not(Box<ConditionGroup>),
189 Exists(Box<ConditionGroup>),
191 Forall(Box<ConditionGroup>),
193}
194
195impl ConditionGroup {
196 pub fn single(condition: Condition) -> Self {
198 ConditionGroup::Single(condition)
199 }
200
201 pub fn and(left: ConditionGroup, right: ConditionGroup) -> Self {
203 ConditionGroup::Compound {
204 left: Box::new(left),
205 operator: LogicalOperator::And,
206 right: Box::new(right),
207 }
208 }
209
210 pub fn or(left: ConditionGroup, right: ConditionGroup) -> Self {
212 ConditionGroup::Compound {
213 left: Box::new(left),
214 operator: LogicalOperator::Or,
215 right: Box::new(right),
216 }
217 }
218
219 #[allow(clippy::should_implement_trait)]
221 pub fn not(condition: ConditionGroup) -> Self {
222 ConditionGroup::Not(Box::new(condition))
223 }
224
225 pub fn exists(condition: ConditionGroup) -> Self {
227 ConditionGroup::Exists(Box::new(condition))
228 }
229
230 pub fn forall(condition: ConditionGroup) -> Self {
232 ConditionGroup::Forall(Box::new(condition))
233 }
234
235 pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
237 match self {
238 ConditionGroup::Single(condition) => condition.evaluate(facts),
239 ConditionGroup::Compound {
240 left,
241 operator,
242 right,
243 } => {
244 let left_result = left.evaluate(facts);
245 let right_result = right.evaluate(facts);
246 match operator {
247 LogicalOperator::And => left_result && right_result,
248 LogicalOperator::Or => left_result || right_result,
249 LogicalOperator::Not => !left_result, }
251 }
252 ConditionGroup::Not(condition) => !condition.evaluate(facts),
253 ConditionGroup::Exists(_) | ConditionGroup::Forall(_) => {
254 false
257 }
258 }
259 }
260
261 pub fn evaluate_with_facts(&self, facts: &crate::engine::facts::Facts) -> bool {
263 use crate::engine::pattern_matcher::PatternMatcher;
264
265 match self {
266 ConditionGroup::Single(condition) => {
267 let fact_map = facts.get_all_facts();
268 condition.evaluate(&fact_map)
269 }
270 ConditionGroup::Compound {
271 left,
272 operator,
273 right,
274 } => {
275 let left_result = left.evaluate_with_facts(facts);
276 let right_result = right.evaluate_with_facts(facts);
277 match operator {
278 LogicalOperator::And => left_result && right_result,
279 LogicalOperator::Or => left_result || right_result,
280 LogicalOperator::Not => !left_result,
281 }
282 }
283 ConditionGroup::Not(condition) => !condition.evaluate_with_facts(facts),
284 ConditionGroup::Exists(condition) => PatternMatcher::evaluate_exists(condition, facts),
285 ConditionGroup::Forall(condition) => PatternMatcher::evaluate_forall(condition, facts),
286 }
287 }
288}
289
290#[derive(Debug, Clone)]
292pub struct Rule {
293 pub name: String,
295 pub description: Option<String>,
297 pub salience: i32,
299 pub enabled: bool,
301 pub no_loop: bool,
303 pub lock_on_active: bool,
305 pub agenda_group: Option<String>,
307 pub activation_group: Option<String>,
309 pub date_effective: Option<DateTime<Utc>>,
311 pub date_expires: Option<DateTime<Utc>>,
313 pub conditions: ConditionGroup,
315 pub actions: Vec<ActionType>,
317}
318
319impl Rule {
320 pub fn new(name: String, conditions: ConditionGroup, actions: Vec<ActionType>) -> Self {
322 Self {
323 name,
324 description: None,
325 salience: 0,
326 enabled: true,
327 no_loop: false,
328 lock_on_active: false,
329 agenda_group: None,
330 activation_group: None,
331 date_effective: None,
332 date_expires: None,
333 conditions,
334 actions,
335 }
336 }
337
338 pub fn with_description(mut self, description: String) -> Self {
340 self.description = Some(description);
341 self
342 }
343
344 pub fn with_salience(mut self, salience: i32) -> Self {
346 self.salience = salience;
347 self
348 }
349
350 pub fn with_priority(mut self, priority: i32) -> Self {
352 self.salience = priority;
353 self
354 }
355
356 pub fn with_no_loop(mut self, no_loop: bool) -> Self {
358 self.no_loop = no_loop;
359 self
360 }
361
362 pub fn with_lock_on_active(mut self, lock_on_active: bool) -> Self {
364 self.lock_on_active = lock_on_active;
365 self
366 }
367
368 pub fn with_agenda_group(mut self, agenda_group: String) -> Self {
370 self.agenda_group = Some(agenda_group);
371 self
372 }
373
374 pub fn with_activation_group(mut self, activation_group: String) -> Self {
376 self.activation_group = Some(activation_group);
377 self
378 }
379
380 pub fn with_date_effective(mut self, date_effective: DateTime<Utc>) -> Self {
382 self.date_effective = Some(date_effective);
383 self
384 }
385
386 pub fn with_date_expires(mut self, date_expires: DateTime<Utc>) -> Self {
388 self.date_expires = Some(date_expires);
389 self
390 }
391
392 pub fn with_date_effective_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
394 let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
395 self.date_effective = Some(date);
396 Ok(self)
397 }
398
399 pub fn with_date_expires_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
401 let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
402 self.date_expires = Some(date);
403 Ok(self)
404 }
405
406 pub fn is_active_at(&self, timestamp: DateTime<Utc>) -> bool {
408 if let Some(effective) = self.date_effective {
410 if timestamp < effective {
411 return false;
412 }
413 }
414
415 if let Some(expires) = self.date_expires {
417 if timestamp >= expires {
418 return false;
419 }
420 }
421
422 true
423 }
424
425 pub fn is_active(&self) -> bool {
427 self.is_active_at(Utc::now())
428 }
429
430 pub fn matches(&self, facts: &HashMap<String, Value>) -> bool {
432 self.enabled && self.conditions.evaluate(facts)
433 }
434}
435
436#[derive(Debug, Clone)]
438pub struct RuleExecutionResult {
439 pub rule_name: String,
441 pub matched: bool,
443 pub actions_executed: Vec<String>,
445 pub execution_time_ms: f64,
447}
448
449impl RuleExecutionResult {
450 pub fn new(rule_name: String) -> Self {
452 Self {
453 rule_name,
454 matched: false,
455 actions_executed: Vec::new(),
456 execution_time_ms: 0.0,
457 }
458 }
459
460 pub fn matched(mut self) -> Self {
462 self.matched = true;
463 self
464 }
465
466 pub fn with_actions(mut self, actions: Vec<String>) -> Self {
468 self.actions_executed = actions;
469 self
470 }
471
472 pub fn with_execution_time(mut self, time_ms: f64) -> Self {
474 self.execution_time_ms = time_ms;
475 self
476 }
477}
478
479fn get_nested_value<'a>(data: &'a HashMap<String, Value>, path: &str) -> Option<&'a Value> {
481 let parts: Vec<&str> = path.split('.').collect();
482 let mut current = data.get(parts[0])?;
483
484 for part in parts.iter().skip(1) {
485 match current {
486 Value::Object(obj) => {
487 current = obj.get(*part)?;
488 }
489 _ => return None,
490 }
491 }
492
493 Some(current)
494}