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 if let Some(field_value) = get_nested_value(facts, field_name) {
197 self.operator.evaluate(field_value, &self.value)
198 } else {
199 false
200 }
201 }
202 ConditionExpression::FunctionCall { .. }
203 | ConditionExpression::Test { .. }
204 | ConditionExpression::MultiField { .. } => {
205 false
208 }
209 }
210 }
211
212 pub fn evaluate_with_engine(
215 &self,
216 facts: &HashMap<String, Value>,
217 function_registry: &HashMap<
218 String,
219 std::sync::Arc<dyn Fn(Vec<Value>, &HashMap<String, Value>) -> crate::errors::Result<Value> + Send + Sync>,
220 >,
221 ) -> bool {
222 match &self.expression {
223 ConditionExpression::Field(field_name) => {
224 if let Some(field_value) = get_nested_value(facts, field_name) {
225 self.operator.evaluate(field_value, &self.value)
226 } else {
227 false
228 }
229 }
230 ConditionExpression::FunctionCall { name, args } => {
231 if let Some(function) = function_registry.get(name) {
233 let arg_values: Vec<Value> = args
235 .iter()
236 .map(|arg| {
237 get_nested_value(facts, arg)
238 .cloned()
239 .unwrap_or(Value::String(arg.clone()))
240 })
241 .collect();
242
243 if let Ok(result) = function(arg_values, facts) {
245 return self.operator.evaluate(&result, &self.value);
247 }
248 }
249 false
250 }
251 ConditionExpression::Test { name, args } => {
252 if let Some(function) = function_registry.get(name) {
254 let arg_values: Vec<Value> = args
256 .iter()
257 .map(|arg| {
258 get_nested_value(facts, arg)
259 .cloned()
260 .unwrap_or(Value::String(arg.clone()))
261 })
262 .collect();
263
264 if let Ok(result) = function(arg_values, facts) {
266 match result {
268 Value::Boolean(b) => return b,
269 Value::Integer(i) => return i != 0,
270 Value::Number(f) => return f != 0.0,
271 Value::String(s) => return !s.is_empty(),
272 _ => return false,
273 }
274 }
275 }
276 false
277 }
278 ConditionExpression::MultiField { field, operation, variable: _ } => {
279 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 _ => {
302 true
305 }
306 }
307 } else {
308 false
309 }
310 }
311 }
312 }
313}
314
315#[derive(Debug, Clone)]
317pub enum ConditionGroup {
318 Single(Condition),
320 Compound {
322 left: Box<ConditionGroup>,
324 operator: LogicalOperator,
326 right: Box<ConditionGroup>,
328 },
329 Not(Box<ConditionGroup>),
331 Exists(Box<ConditionGroup>),
333 Forall(Box<ConditionGroup>),
335 Accumulate {
338 result_var: String,
340 source_pattern: String,
342 extract_field: String,
344 source_conditions: Vec<String>,
346 function: String,
348 function_arg: String,
350 },
351}
352
353impl ConditionGroup {
354 pub fn single(condition: Condition) -> Self {
356 ConditionGroup::Single(condition)
357 }
358
359 pub fn and(left: ConditionGroup, right: ConditionGroup) -> Self {
361 ConditionGroup::Compound {
362 left: Box::new(left),
363 operator: LogicalOperator::And,
364 right: Box::new(right),
365 }
366 }
367
368 pub fn or(left: ConditionGroup, right: ConditionGroup) -> Self {
370 ConditionGroup::Compound {
371 left: Box::new(left),
372 operator: LogicalOperator::Or,
373 right: Box::new(right),
374 }
375 }
376
377 #[allow(clippy::should_implement_trait)]
379 pub fn not(condition: ConditionGroup) -> Self {
380 ConditionGroup::Not(Box::new(condition))
381 }
382
383 pub fn exists(condition: ConditionGroup) -> Self {
385 ConditionGroup::Exists(Box::new(condition))
386 }
387
388 pub fn forall(condition: ConditionGroup) -> Self {
390 ConditionGroup::Forall(Box::new(condition))
391 }
392
393 pub fn accumulate(
395 result_var: String,
396 source_pattern: String,
397 extract_field: String,
398 source_conditions: Vec<String>,
399 function: String,
400 function_arg: String,
401 ) -> Self {
402 ConditionGroup::Accumulate {
403 result_var,
404 source_pattern,
405 extract_field,
406 source_conditions,
407 function,
408 function_arg,
409 }
410 }
411
412 pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
414 match self {
415 ConditionGroup::Single(condition) => condition.evaluate(facts),
416 ConditionGroup::Compound {
417 left,
418 operator,
419 right,
420 } => {
421 let left_result = left.evaluate(facts);
422 let right_result = right.evaluate(facts);
423 match operator {
424 LogicalOperator::And => left_result && right_result,
425 LogicalOperator::Or => left_result || right_result,
426 LogicalOperator::Not => !left_result, }
428 }
429 ConditionGroup::Not(condition) => !condition.evaluate(facts),
430 ConditionGroup::Exists(_) | ConditionGroup::Forall(_) | ConditionGroup::Accumulate { .. } => {
431 false
434 }
435 }
436 }
437
438 pub fn evaluate_with_facts(&self, facts: &crate::engine::facts::Facts) -> bool {
440 use crate::engine::pattern_matcher::PatternMatcher;
441
442 match self {
443 ConditionGroup::Single(condition) => {
444 let fact_map = facts.get_all_facts();
445 condition.evaluate(&fact_map)
446 }
447 ConditionGroup::Compound {
448 left,
449 operator,
450 right,
451 } => {
452 let left_result = left.evaluate_with_facts(facts);
453 let right_result = right.evaluate_with_facts(facts);
454 match operator {
455 LogicalOperator::And => left_result && right_result,
456 LogicalOperator::Or => left_result || right_result,
457 LogicalOperator::Not => !left_result,
458 }
459 }
460 ConditionGroup::Not(condition) => !condition.evaluate_with_facts(facts),
461 ConditionGroup::Exists(condition) => PatternMatcher::evaluate_exists(condition, facts),
462 ConditionGroup::Forall(condition) => PatternMatcher::evaluate_forall(condition, facts),
463 ConditionGroup::Accumulate { .. } => {
464 true
468 }
469 }
470 }
471}
472
473#[derive(Debug, Clone)]
475pub struct Rule {
476 pub name: String,
478 pub description: Option<String>,
480 pub salience: i32,
482 pub enabled: bool,
484 pub no_loop: bool,
486 pub lock_on_active: bool,
488 pub agenda_group: Option<String>,
490 pub activation_group: Option<String>,
492 pub date_effective: Option<DateTime<Utc>>,
494 pub date_expires: Option<DateTime<Utc>>,
496 pub conditions: ConditionGroup,
498 pub actions: Vec<ActionType>,
500}
501
502impl Rule {
503 pub fn new(name: String, conditions: ConditionGroup, actions: Vec<ActionType>) -> Self {
505 Self {
506 name,
507 description: None,
508 salience: 0,
509 enabled: true,
510 no_loop: false,
511 lock_on_active: false,
512 agenda_group: None,
513 activation_group: None,
514 date_effective: None,
515 date_expires: None,
516 conditions,
517 actions,
518 }
519 }
520
521 pub fn with_description(mut self, description: String) -> Self {
523 self.description = Some(description);
524 self
525 }
526
527 pub fn with_salience(mut self, salience: i32) -> Self {
529 self.salience = salience;
530 self
531 }
532
533 pub fn with_priority(mut self, priority: i32) -> Self {
535 self.salience = priority;
536 self
537 }
538
539 pub fn with_no_loop(mut self, no_loop: bool) -> Self {
541 self.no_loop = no_loop;
542 self
543 }
544
545 pub fn with_lock_on_active(mut self, lock_on_active: bool) -> Self {
547 self.lock_on_active = lock_on_active;
548 self
549 }
550
551 pub fn with_agenda_group(mut self, agenda_group: String) -> Self {
553 self.agenda_group = Some(agenda_group);
554 self
555 }
556
557 pub fn with_activation_group(mut self, activation_group: String) -> Self {
559 self.activation_group = Some(activation_group);
560 self
561 }
562
563 pub fn with_date_effective(mut self, date_effective: DateTime<Utc>) -> Self {
565 self.date_effective = Some(date_effective);
566 self
567 }
568
569 pub fn with_date_expires(mut self, date_expires: DateTime<Utc>) -> Self {
571 self.date_expires = Some(date_expires);
572 self
573 }
574
575 pub fn with_date_effective_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
577 let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
578 self.date_effective = Some(date);
579 Ok(self)
580 }
581
582 pub fn with_date_expires_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
584 let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
585 self.date_expires = Some(date);
586 Ok(self)
587 }
588
589 pub fn is_active_at(&self, timestamp: DateTime<Utc>) -> bool {
591 if let Some(effective) = self.date_effective {
593 if timestamp < effective {
594 return false;
595 }
596 }
597
598 if let Some(expires) = self.date_expires {
600 if timestamp >= expires {
601 return false;
602 }
603 }
604
605 true
606 }
607
608 pub fn is_active(&self) -> bool {
610 self.is_active_at(Utc::now())
611 }
612
613 pub fn matches(&self, facts: &HashMap<String, Value>) -> bool {
615 self.enabled && self.conditions.evaluate(facts)
616 }
617}
618
619#[derive(Debug, Clone)]
621pub struct RuleExecutionResult {
622 pub rule_name: String,
624 pub matched: bool,
626 pub actions_executed: Vec<String>,
628 pub execution_time_ms: f64,
630}
631
632impl RuleExecutionResult {
633 pub fn new(rule_name: String) -> Self {
635 Self {
636 rule_name,
637 matched: false,
638 actions_executed: Vec::new(),
639 execution_time_ms: 0.0,
640 }
641 }
642
643 pub fn matched(mut self) -> Self {
645 self.matched = true;
646 self
647 }
648
649 pub fn with_actions(mut self, actions: Vec<String>) -> Self {
651 self.actions_executed = actions;
652 self
653 }
654
655 pub fn with_execution_time(mut self, time_ms: f64) -> Self {
657 self.execution_time_ms = time_ms;
658 self
659 }
660}
661
662fn get_nested_value<'a>(data: &'a HashMap<String, Value>, path: &str) -> Option<&'a Value> {
664 let parts: Vec<&str> = path.split('.').collect();
665 let mut current = data.get(parts[0])?;
666
667 for part in parts.iter().skip(1) {
668 match current {
669 Value::Object(obj) => {
670 current = obj.get(*part)?;
671 }
672 _ => return None,
673 }
674 }
675
676 Some(current)
677}