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 Accumulate {
196 result_var: String,
198 source_pattern: String,
200 extract_field: String,
202 source_conditions: Vec<String>,
204 function: String,
206 function_arg: String,
208 },
209}
210
211impl ConditionGroup {
212 pub fn single(condition: Condition) -> Self {
214 ConditionGroup::Single(condition)
215 }
216
217 pub fn and(left: ConditionGroup, right: ConditionGroup) -> Self {
219 ConditionGroup::Compound {
220 left: Box::new(left),
221 operator: LogicalOperator::And,
222 right: Box::new(right),
223 }
224 }
225
226 pub fn or(left: ConditionGroup, right: ConditionGroup) -> Self {
228 ConditionGroup::Compound {
229 left: Box::new(left),
230 operator: LogicalOperator::Or,
231 right: Box::new(right),
232 }
233 }
234
235 #[allow(clippy::should_implement_trait)]
237 pub fn not(condition: ConditionGroup) -> Self {
238 ConditionGroup::Not(Box::new(condition))
239 }
240
241 pub fn exists(condition: ConditionGroup) -> Self {
243 ConditionGroup::Exists(Box::new(condition))
244 }
245
246 pub fn forall(condition: ConditionGroup) -> Self {
248 ConditionGroup::Forall(Box::new(condition))
249 }
250
251 pub fn accumulate(
253 result_var: String,
254 source_pattern: String,
255 extract_field: String,
256 source_conditions: Vec<String>,
257 function: String,
258 function_arg: String,
259 ) -> Self {
260 ConditionGroup::Accumulate {
261 result_var,
262 source_pattern,
263 extract_field,
264 source_conditions,
265 function,
266 function_arg,
267 }
268 }
269
270 pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
272 match self {
273 ConditionGroup::Single(condition) => condition.evaluate(facts),
274 ConditionGroup::Compound {
275 left,
276 operator,
277 right,
278 } => {
279 let left_result = left.evaluate(facts);
280 let right_result = right.evaluate(facts);
281 match operator {
282 LogicalOperator::And => left_result && right_result,
283 LogicalOperator::Or => left_result || right_result,
284 LogicalOperator::Not => !left_result, }
286 }
287 ConditionGroup::Not(condition) => !condition.evaluate(facts),
288 ConditionGroup::Exists(_) | ConditionGroup::Forall(_) | ConditionGroup::Accumulate { .. } => {
289 false
292 }
293 }
294 }
295
296 pub fn evaluate_with_facts(&self, facts: &crate::engine::facts::Facts) -> bool {
298 use crate::engine::pattern_matcher::PatternMatcher;
299
300 match self {
301 ConditionGroup::Single(condition) => {
302 let fact_map = facts.get_all_facts();
303 condition.evaluate(&fact_map)
304 }
305 ConditionGroup::Compound {
306 left,
307 operator,
308 right,
309 } => {
310 let left_result = left.evaluate_with_facts(facts);
311 let right_result = right.evaluate_with_facts(facts);
312 match operator {
313 LogicalOperator::And => left_result && right_result,
314 LogicalOperator::Or => left_result || right_result,
315 LogicalOperator::Not => !left_result,
316 }
317 }
318 ConditionGroup::Not(condition) => !condition.evaluate_with_facts(facts),
319 ConditionGroup::Exists(condition) => PatternMatcher::evaluate_exists(condition, facts),
320 ConditionGroup::Forall(condition) => PatternMatcher::evaluate_forall(condition, facts),
321 ConditionGroup::Accumulate { .. } => {
322 true
326 }
327 }
328 }
329}
330
331#[derive(Debug, Clone)]
333pub struct Rule {
334 pub name: String,
336 pub description: Option<String>,
338 pub salience: i32,
340 pub enabled: bool,
342 pub no_loop: bool,
344 pub lock_on_active: bool,
346 pub agenda_group: Option<String>,
348 pub activation_group: Option<String>,
350 pub date_effective: Option<DateTime<Utc>>,
352 pub date_expires: Option<DateTime<Utc>>,
354 pub conditions: ConditionGroup,
356 pub actions: Vec<ActionType>,
358}
359
360impl Rule {
361 pub fn new(name: String, conditions: ConditionGroup, actions: Vec<ActionType>) -> Self {
363 Self {
364 name,
365 description: None,
366 salience: 0,
367 enabled: true,
368 no_loop: false,
369 lock_on_active: false,
370 agenda_group: None,
371 activation_group: None,
372 date_effective: None,
373 date_expires: None,
374 conditions,
375 actions,
376 }
377 }
378
379 pub fn with_description(mut self, description: String) -> Self {
381 self.description = Some(description);
382 self
383 }
384
385 pub fn with_salience(mut self, salience: i32) -> Self {
387 self.salience = salience;
388 self
389 }
390
391 pub fn with_priority(mut self, priority: i32) -> Self {
393 self.salience = priority;
394 self
395 }
396
397 pub fn with_no_loop(mut self, no_loop: bool) -> Self {
399 self.no_loop = no_loop;
400 self
401 }
402
403 pub fn with_lock_on_active(mut self, lock_on_active: bool) -> Self {
405 self.lock_on_active = lock_on_active;
406 self
407 }
408
409 pub fn with_agenda_group(mut self, agenda_group: String) -> Self {
411 self.agenda_group = Some(agenda_group);
412 self
413 }
414
415 pub fn with_activation_group(mut self, activation_group: String) -> Self {
417 self.activation_group = Some(activation_group);
418 self
419 }
420
421 pub fn with_date_effective(mut self, date_effective: DateTime<Utc>) -> Self {
423 self.date_effective = Some(date_effective);
424 self
425 }
426
427 pub fn with_date_expires(mut self, date_expires: DateTime<Utc>) -> Self {
429 self.date_expires = Some(date_expires);
430 self
431 }
432
433 pub fn with_date_effective_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
435 let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
436 self.date_effective = Some(date);
437 Ok(self)
438 }
439
440 pub fn with_date_expires_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
442 let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
443 self.date_expires = Some(date);
444 Ok(self)
445 }
446
447 pub fn is_active_at(&self, timestamp: DateTime<Utc>) -> bool {
449 if let Some(effective) = self.date_effective {
451 if timestamp < effective {
452 return false;
453 }
454 }
455
456 if let Some(expires) = self.date_expires {
458 if timestamp >= expires {
459 return false;
460 }
461 }
462
463 true
464 }
465
466 pub fn is_active(&self) -> bool {
468 self.is_active_at(Utc::now())
469 }
470
471 pub fn matches(&self, facts: &HashMap<String, Value>) -> bool {
473 self.enabled && self.conditions.evaluate(facts)
474 }
475}
476
477#[derive(Debug, Clone)]
479pub struct RuleExecutionResult {
480 pub rule_name: String,
482 pub matched: bool,
484 pub actions_executed: Vec<String>,
486 pub execution_time_ms: f64,
488}
489
490impl RuleExecutionResult {
491 pub fn new(rule_name: String) -> Self {
493 Self {
494 rule_name,
495 matched: false,
496 actions_executed: Vec::new(),
497 execution_time_ms: 0.0,
498 }
499 }
500
501 pub fn matched(mut self) -> Self {
503 self.matched = true;
504 self
505 }
506
507 pub fn with_actions(mut self, actions: Vec<String>) -> Self {
509 self.actions_executed = actions;
510 self
511 }
512
513 pub fn with_execution_time(mut self, time_ms: f64) -> Self {
515 self.execution_time_ms = time_ms;
516 self
517 }
518}
519
520fn get_nested_value<'a>(data: &'a HashMap<String, Value>, path: &str) -> Option<&'a Value> {
522 let parts: Vec<&str> = path.split('.').collect();
523 let mut current = data.get(parts[0])?;
524
525 for part in parts.iter().skip(1) {
526 match current {
527 Value::Object(obj) => {
528 current = obj.get(*part)?;
529 }
530 _ => return None,
531 }
532 }
533
534 Some(current)
535}