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 MultiField {
34 field: String,
36 operation: String, variable: Option<String>,
40 },
41}
42
43#[derive(Debug, Clone)]
45pub struct Condition {
46 pub expression: ConditionExpression,
48 pub operator: Operator,
50 pub value: Value,
52
53 #[deprecated(note = "Use expression instead")]
55 #[doc(hidden)]
56 pub field: String,
57}
58
59impl Condition {
60 pub fn new(field: String, operator: Operator, value: Value) -> Self {
62 Self {
63 expression: ConditionExpression::Field(field.clone()),
64 operator,
65 value,
66 field, }
68 }
69
70 pub fn with_function(
72 function_name: String,
73 args: Vec<String>,
74 operator: Operator,
75 value: Value,
76 ) -> Self {
77 Self {
78 expression: ConditionExpression::FunctionCall {
79 name: function_name.clone(),
80 args,
81 },
82 operator,
83 value,
84 field: function_name, }
86 }
87
88 pub fn with_test(function_name: String, args: Vec<String>) -> Self {
91 Self {
92 expression: ConditionExpression::Test {
93 name: function_name.clone(),
94 args,
95 },
96 operator: Operator::Equal, value: Value::Boolean(true), field: format!("test({})", function_name), }
100 }
101
102 pub fn with_multifield_collect(field: String, variable: String) -> Self {
105 Self {
106 expression: ConditionExpression::MultiField {
107 field: field.clone(),
108 operation: "collect".to_string(),
109 variable: Some(variable),
110 },
111 operator: Operator::Equal, value: Value::Boolean(true), field, }
115 }
116
117 pub fn with_multifield_count(field: String, operator: Operator, value: Value) -> Self {
120 Self {
121 expression: ConditionExpression::MultiField {
122 field: field.clone(),
123 operation: "count".to_string(),
124 variable: None,
125 },
126 operator,
127 value,
128 field, }
130 }
131
132 pub fn with_multifield_first(field: String, variable: Option<String>) -> Self {
135 Self {
136 expression: ConditionExpression::MultiField {
137 field: field.clone(),
138 operation: "first".to_string(),
139 variable,
140 },
141 operator: Operator::Equal, value: Value::Boolean(true), field, }
145 }
146
147 pub fn with_multifield_last(field: String, variable: Option<String>) -> Self {
150 Self {
151 expression: ConditionExpression::MultiField {
152 field: field.clone(),
153 operation: "last".to_string(),
154 variable,
155 },
156 operator: Operator::Equal, value: Value::Boolean(true), field, }
160 }
161
162 pub fn with_multifield_empty(field: String) -> Self {
165 Self {
166 expression: ConditionExpression::MultiField {
167 field: field.clone(),
168 operation: "empty".to_string(),
169 variable: None,
170 },
171 operator: Operator::Equal, value: Value::Boolean(true), field, }
175 }
176
177 pub fn with_multifield_not_empty(field: String) -> Self {
180 Self {
181 expression: ConditionExpression::MultiField {
182 field: field.clone(),
183 operation: "not_empty".to_string(),
184 variable: None,
185 },
186 operator: Operator::Equal, value: Value::Boolean(true), field, }
190 }
191
192 pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
194 match &self.expression {
195 ConditionExpression::Field(field_name) => {
196 let field_value = get_nested_value(facts, field_name)
198 .cloned()
199 .unwrap_or(Value::Null);
200
201 self.operator.evaluate(&field_value, &self.value)
202 }
203 ConditionExpression::FunctionCall { .. }
204 | ConditionExpression::Test { .. }
205 | ConditionExpression::MultiField { .. } => {
206 false
209 }
210 }
211 }
212
213 pub fn evaluate_with_engine(
216 &self,
217 facts: &HashMap<String, Value>,
218 function_registry: &HashMap<
219 String,
220 std::sync::Arc<dyn Fn(Vec<Value>, &HashMap<String, Value>) -> crate::errors::Result<Value> + Send + Sync>,
221 >,
222 ) -> bool {
223 match &self.expression {
224 ConditionExpression::Field(field_name) => {
225 let field_value = get_nested_value(facts, field_name)
227 .cloned()
228 .unwrap_or(Value::Null);
229
230 self.operator.evaluate(&field_value, &self.value)
231 }
232 ConditionExpression::FunctionCall { name, args } => {
233 if let Some(function) = function_registry.get(name) {
235 let arg_values: Vec<Value> = args
237 .iter()
238 .map(|arg| {
239 get_nested_value(facts, arg)
240 .cloned()
241 .unwrap_or(Value::String(arg.clone()))
242 })
243 .collect();
244
245 if let Ok(result) = function(arg_values, facts) {
247 return self.operator.evaluate(&result, &self.value);
249 }
250 }
251 false
252 }
253 ConditionExpression::Test { name, args } => {
254 if let Some(function) = function_registry.get(name) {
256 let arg_values: Vec<Value> = args
258 .iter()
259 .map(|arg| {
260 get_nested_value(facts, arg)
261 .cloned()
262 .unwrap_or(Value::String(arg.clone()))
263 })
264 .collect();
265
266 if let Ok(result) = function(arg_values, facts) {
268 match result {
270 Value::Boolean(b) => return b,
271 Value::Integer(i) => return i != 0,
272 Value::Number(f) => return f != 0.0,
273 Value::String(s) => return !s.is_empty(),
274 _ => return false,
275 }
276 }
277 }
278 false
279 }
280 ConditionExpression::MultiField { field, operation, variable: _ } => {
281 if let Some(field_value) = get_nested_value(facts, field) {
283 match operation.as_str() {
284 "empty" => {
285 matches!(field_value, Value::Array(arr) if arr.is_empty())
287 }
288 "not_empty" => {
289 matches!(field_value, Value::Array(arr) if !arr.is_empty())
291 }
292 "count" => {
293 if let Value::Array(arr) = field_value {
295 let count = Value::Integer(arr.len() as i64);
296 self.operator.evaluate(&count, &self.value)
297 } else {
298 false
299 }
300 }
301 "first" => {
302 if let Value::Array(arr) = field_value {
304 if let Some(first) = arr.first() {
305 self.operator.evaluate(first, &self.value)
306 } else {
307 false
308 }
309 } else {
310 false
311 }
312 }
313 "last" => {
314 if let Value::Array(arr) = field_value {
316 if let Some(last) = arr.last() {
317 self.operator.evaluate(last, &self.value)
318 } else {
319 false
320 }
321 } else {
322 false
323 }
324 }
325 "contains" => {
326 if let Value::Array(arr) = field_value {
328 arr.iter().any(|item| self.operator.evaluate(item, &self.value))
329 } else {
330 false
331 }
332 }
333 "collect" => {
334 matches!(field_value, Value::Array(arr) if !arr.is_empty())
337 }
338 _ => {
339 false
341 }
342 }
343 } else {
344 false
345 }
346 }
347 }
348 }
349}
350
351#[derive(Debug, Clone)]
353pub enum ConditionGroup {
354 Single(Condition),
356 Compound {
358 left: Box<ConditionGroup>,
360 operator: LogicalOperator,
362 right: Box<ConditionGroup>,
364 },
365 Not(Box<ConditionGroup>),
367 Exists(Box<ConditionGroup>),
369 Forall(Box<ConditionGroup>),
371 Accumulate {
374 result_var: String,
376 source_pattern: String,
378 extract_field: String,
380 source_conditions: Vec<String>,
382 function: String,
384 function_arg: String,
386 },
387}
388
389impl ConditionGroup {
390 pub fn single(condition: Condition) -> Self {
392 ConditionGroup::Single(condition)
393 }
394
395 pub fn and(left: ConditionGroup, right: ConditionGroup) -> Self {
397 ConditionGroup::Compound {
398 left: Box::new(left),
399 operator: LogicalOperator::And,
400 right: Box::new(right),
401 }
402 }
403
404 pub fn or(left: ConditionGroup, right: ConditionGroup) -> Self {
406 ConditionGroup::Compound {
407 left: Box::new(left),
408 operator: LogicalOperator::Or,
409 right: Box::new(right),
410 }
411 }
412
413 #[allow(clippy::should_implement_trait)]
415 pub fn not(condition: ConditionGroup) -> Self {
416 ConditionGroup::Not(Box::new(condition))
417 }
418
419 pub fn exists(condition: ConditionGroup) -> Self {
421 ConditionGroup::Exists(Box::new(condition))
422 }
423
424 pub fn forall(condition: ConditionGroup) -> Self {
426 ConditionGroup::Forall(Box::new(condition))
427 }
428
429 pub fn accumulate(
431 result_var: String,
432 source_pattern: String,
433 extract_field: String,
434 source_conditions: Vec<String>,
435 function: String,
436 function_arg: String,
437 ) -> Self {
438 ConditionGroup::Accumulate {
439 result_var,
440 source_pattern,
441 extract_field,
442 source_conditions,
443 function,
444 function_arg,
445 }
446 }
447
448 pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
450 match self {
451 ConditionGroup::Single(condition) => condition.evaluate(facts),
452 ConditionGroup::Compound {
453 left,
454 operator,
455 right,
456 } => {
457 let left_result = left.evaluate(facts);
458 let right_result = right.evaluate(facts);
459 match operator {
460 LogicalOperator::And => left_result && right_result,
461 LogicalOperator::Or => left_result || right_result,
462 LogicalOperator::Not => !left_result, }
464 }
465 ConditionGroup::Not(condition) => !condition.evaluate(facts),
466 ConditionGroup::Exists(_) | ConditionGroup::Forall(_) | ConditionGroup::Accumulate { .. } => {
467 false
470 }
471 }
472 }
473
474 pub fn evaluate_with_facts(&self, facts: &crate::engine::facts::Facts) -> bool {
476 use crate::engine::pattern_matcher::PatternMatcher;
477
478 match self {
479 ConditionGroup::Single(condition) => {
480 let fact_map = facts.get_all_facts();
481 condition.evaluate(&fact_map)
482 }
483 ConditionGroup::Compound {
484 left,
485 operator,
486 right,
487 } => {
488 let left_result = left.evaluate_with_facts(facts);
489 let right_result = right.evaluate_with_facts(facts);
490 match operator {
491 LogicalOperator::And => left_result && right_result,
492 LogicalOperator::Or => left_result || right_result,
493 LogicalOperator::Not => !left_result,
494 }
495 }
496 ConditionGroup::Not(condition) => !condition.evaluate_with_facts(facts),
497 ConditionGroup::Exists(condition) => PatternMatcher::evaluate_exists(condition, facts),
498 ConditionGroup::Forall(condition) => PatternMatcher::evaluate_forall(condition, facts),
499 ConditionGroup::Accumulate { .. } => {
500 true
504 }
505 }
506 }
507}
508
509#[derive(Debug, Clone)]
511pub struct Rule {
512 pub name: String,
514 pub description: Option<String>,
516 pub salience: i32,
518 pub enabled: bool,
520 pub no_loop: bool,
522 pub lock_on_active: bool,
524 pub agenda_group: Option<String>,
526 pub activation_group: Option<String>,
528 pub date_effective: Option<DateTime<Utc>>,
530 pub date_expires: Option<DateTime<Utc>>,
532 pub conditions: ConditionGroup,
534 pub actions: Vec<ActionType>,
536}
537
538impl Rule {
539 pub fn new(name: String, conditions: ConditionGroup, actions: Vec<ActionType>) -> Self {
541 Self {
542 name,
543 description: None,
544 salience: 0,
545 enabled: true,
546 no_loop: false,
547 lock_on_active: false,
548 agenda_group: None,
549 activation_group: None,
550 date_effective: None,
551 date_expires: None,
552 conditions,
553 actions,
554 }
555 }
556
557 pub fn with_description(mut self, description: String) -> Self {
559 self.description = Some(description);
560 self
561 }
562
563 pub fn with_salience(mut self, salience: i32) -> Self {
565 self.salience = salience;
566 self
567 }
568
569 pub fn with_priority(mut self, priority: i32) -> Self {
571 self.salience = priority;
572 self
573 }
574
575 pub fn with_no_loop(mut self, no_loop: bool) -> Self {
577 self.no_loop = no_loop;
578 self
579 }
580
581 pub fn with_lock_on_active(mut self, lock_on_active: bool) -> Self {
583 self.lock_on_active = lock_on_active;
584 self
585 }
586
587 pub fn with_agenda_group(mut self, agenda_group: String) -> Self {
589 self.agenda_group = Some(agenda_group);
590 self
591 }
592
593 pub fn with_activation_group(mut self, activation_group: String) -> Self {
595 self.activation_group = Some(activation_group);
596 self
597 }
598
599 pub fn with_date_effective(mut self, date_effective: DateTime<Utc>) -> Self {
601 self.date_effective = Some(date_effective);
602 self
603 }
604
605 pub fn with_date_expires(mut self, date_expires: DateTime<Utc>) -> Self {
607 self.date_expires = Some(date_expires);
608 self
609 }
610
611 pub fn with_date_effective_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
613 let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
614 self.date_effective = Some(date);
615 Ok(self)
616 }
617
618 pub fn with_date_expires_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
620 let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
621 self.date_expires = Some(date);
622 Ok(self)
623 }
624
625 pub fn is_active_at(&self, timestamp: DateTime<Utc>) -> bool {
627 if let Some(effective) = self.date_effective {
629 if timestamp < effective {
630 return false;
631 }
632 }
633
634 if let Some(expires) = self.date_expires {
636 if timestamp >= expires {
637 return false;
638 }
639 }
640
641 true
642 }
643
644 pub fn is_active(&self) -> bool {
646 self.is_active_at(Utc::now())
647 }
648
649 pub fn matches(&self, facts: &HashMap<String, Value>) -> bool {
651 self.enabled && self.conditions.evaluate(facts)
652 }
653}
654
655#[derive(Debug, Clone)]
657pub struct RuleExecutionResult {
658 pub rule_name: String,
660 pub matched: bool,
662 pub actions_executed: Vec<String>,
664 pub execution_time_ms: f64,
666}
667
668impl RuleExecutionResult {
669 pub fn new(rule_name: String) -> Self {
671 Self {
672 rule_name,
673 matched: false,
674 actions_executed: Vec::new(),
675 execution_time_ms: 0.0,
676 }
677 }
678
679 pub fn matched(mut self) -> Self {
681 self.matched = true;
682 self
683 }
684
685 pub fn with_actions(mut self, actions: Vec<String>) -> Self {
687 self.actions_executed = actions;
688 self
689 }
690
691 pub fn with_execution_time(mut self, time_ms: f64) -> Self {
693 self.execution_time_ms = time_ms;
694 self
695 }
696}
697
698fn get_nested_value<'a>(data: &'a HashMap<String, Value>, path: &str) -> Option<&'a Value> {
700 let parts: Vec<&str> = path.split('.').collect();
701 let mut current = data.get(parts[0])?;
702
703 for part in parts.iter().skip(1) {
704 match current {
705 Value::Object(obj) => {
706 current = obj.get(*part)?;
707 }
708 _ => return None,
709 }
710 }
711
712 Some(current)
713}