rust_rule_engine/backward/
rule_executor.rs

1//! Rule execution for backward chaining
2//!
3//! This module provides proper condition evaluation and action execution for backward
4//! chaining queries. It integrates with the Truth Maintenance System (TMS) to support
5//! logical fact insertion and justification-based retraction.
6//!
7//! # Features
8//!
9//! - **Condition evaluation** - Evaluate rule conditions against current facts
10//! - **Action execution** - Execute rule actions (Set, MethodCall, Log, Retract)
11//! - **TMS integration** - Optional logical fact insertion with justifications
12//! - **Function support** - Built-in functions (len, isEmpty, exists, etc.)
13//! - **Type conversion** - Convert between Facts (string-based) and TypedFacts (RETE)
14//!
15//! # Architecture
16//!
17//! ```text
18//! ┌──────────────────┐
19//! │  RuleExecutor    │
20//! │                  │
21//! │  ┌────────────┐  │
22//! │  │ Condition  │──┼──> ConditionEvaluator
23//! │  │ Evaluator  │  │       │
24//! │  └────────────┘  │       ├─> Built-in functions
25//! │                  │       └─> Field comparison
26//! │  ┌────────────┐  │
27//! │  │   Action   │──┼──> Set, MethodCall, Log
28//! │  │ Executor   │  │       │
29//! │  └────────────┘  │       └─> TMS Inserter (optional)
30//! └──────────────────┘
31//! ```
32//!
33//! # Example: Basic Rule Execution
34//!
35//! ```rust
36//! use rust_rule_engine::backward::rule_executor::RuleExecutor;
37//! use rust_rule_engine::engine::rule::{Rule, Condition, ConditionGroup};
38//! use rust_rule_engine::types::{Operator, ActionType, Value};
39//! use rust_rule_engine::{KnowledgeBase, Facts};
40//!
41//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
42//! let kb = KnowledgeBase::new("test");
43//! let executor = RuleExecutor::new(kb);
44//!
45//! // Define a rule: If User.Age > 18, then User.IsAdult = true
46//! let conditions = ConditionGroup::Single(
47//!     Condition::new(
48//!         "User.Age".to_string(),
49//!         Operator::GreaterThan,
50//!         Value::Number(18.0),
51//!     )
52//! );
53//! let actions = vec![ActionType::Set {
54//!     field: "User.IsAdult".to_string(),
55//!     value: Value::Boolean(true),
56//! }];
57//! let rule = Rule::new("CheckAdult".to_string(), conditions, actions);
58//!
59//! // Execute rule
60//! let mut facts = Facts::new();
61//! facts.set("User.Age", Value::Number(25.0));
62//!
63//! let executed = executor.try_execute_rule(&rule, &mut facts)?;
64//! assert!(executed); // Rule should execute successfully
65//! assert_eq!(facts.get("User.IsAdult"), Some(Value::Boolean(true)));
66//! # Ok(())
67//! # }
68//! ```
69//!
70//! # Example: TMS Integration
71//!
72//! ```rust
73//! use rust_rule_engine::backward::rule_executor::RuleExecutor;
74//! use rust_rule_engine::rete::propagation::IncrementalEngine;
75//! use rust_rule_engine::{KnowledgeBase, Facts};
76//! use std::sync::{Arc, Mutex};
77//!
78//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
79//! let kb = KnowledgeBase::new("test");
80//! let rete_engine = Arc::new(Mutex::new(IncrementalEngine::new()));
81//!
82//! // Create TMS inserter callback
83//! let inserter = {
84//!     let eng = rete_engine.clone();
85//!     Arc::new(move |fact_type: String, data, rule_name: String, premises: Vec<String>| {
86//!         if let Ok(mut e) = eng.lock() {
87//!             let handles = e.resolve_premise_keys(premises);
88//!             let _ = e.insert_logical(fact_type, data, rule_name, handles);
89//!         }
90//!     })
91//! };
92//!
93//! let executor = RuleExecutor::new_with_inserter(kb, Some(inserter));
94//! // Now rule executions will insert facts logically with justifications
95//! # Ok(())
96//! # }
97//! ```
98//!
99//! # Supported Action Types
100//!
101//! - **Set** - Set a fact value: `field: value`
102//! - **MethodCall** - Call a method on an object: `object.method(args)`
103//! - **Log** - Log a message: `log("message")`
104//! - **Retract** - Retract a fact: `retract(field)`
105//!
106//! # Built-in Functions
107//!
108//! The executor supports these built-in functions for condition evaluation:
109//! - `len(field)` - Get string/array length
110//! - `isEmpty(field)` - Check if string/array is empty
111//! - `exists(field)` - Check if field exists
112//! - `count(field)` - Count array elements
113
114use crate::engine::rule::{Condition, ConditionGroup, Rule};
115use crate::engine::condition_evaluator::ConditionEvaluator;
116use crate::types::{ActionType, Value};
117use crate::{Facts, KnowledgeBase};
118use crate::errors::{Result, RuleEngineError};
119
120/// Rule executor for backward chaining
121pub struct RuleExecutor {
122    evaluator: ConditionEvaluator,
123    /// Optional TMS inserter callback: (fact_type, typed_data, source_rule, premise_keys)
124    /// premise_keys are strings in the format: "Type.field=value" which the inserter
125    /// can use to resolve to working-memory FactHandles.
126    tms_inserter: Option<std::sync::Arc<dyn Fn(String, crate::rete::TypedFacts, String, Vec<String>) + Send + Sync>>,
127}
128
129impl RuleExecutor {
130    /// Create a new rule executor
131    ///
132    /// Note: The knowledge_base parameter is kept for API compatibility but is not used.
133    /// Rule evaluation is done through the ConditionEvaluator.
134    pub fn new(_knowledge_base: KnowledgeBase) -> Self {
135        Self::new_with_inserter(_knowledge_base, None)
136    }
137
138    /// Create a new executor with optional TMS inserter callback
139    ///
140    /// Note: The knowledge_base parameter is kept for API compatibility but is not used.
141    /// Rule evaluation is done through the ConditionEvaluator.
142    pub fn new_with_inserter(
143        _knowledge_base: KnowledgeBase,
144        inserter: Option<std::sync::Arc<dyn Fn(String, crate::rete::TypedFacts, String, Vec<String>) + Send + Sync>>,
145    ) -> Self {
146        Self {
147            evaluator: ConditionEvaluator::with_builtin_functions(),
148            tms_inserter: inserter,
149        }
150    }
151
152    /// Check if rule conditions are satisfied and execute if they are
153    ///
154    /// Returns:
155    /// - Ok(true) if rule executed successfully
156    /// - Ok(false) if conditions not satisfied
157    /// - Err if execution failed
158    pub fn try_execute_rule(
159        &self,
160        rule: &Rule,
161        facts: &mut Facts,
162    ) -> Result<bool> {
163        // Check if all conditions are satisfied
164        if !self.evaluate_conditions(&rule.conditions, facts)? {
165            return Ok(false);
166        }
167
168        // Conditions satisfied - execute actions
169        self.execute_actions(rule, facts)?;
170
171        Ok(true)
172    }
173
174    /// Evaluate condition group
175    pub fn evaluate_conditions(
176        &self,
177        group: &ConditionGroup,
178        facts: &Facts,
179    ) -> Result<bool> {
180        // Delegate to shared evaluator
181        self.evaluator.evaluate_conditions(group, facts)
182    }
183
184    /// Evaluate a single condition
185    pub fn evaluate_condition(&self, condition: &Condition, facts: &Facts) -> Result<bool> {
186        // Delegate to shared evaluator
187        self.evaluator.evaluate_condition(condition, facts)
188    }
189
190    /// Execute rule actions
191    fn execute_actions(&self, rule: &Rule, facts: &mut Facts) -> Result<()> {
192        for action in &rule.actions {
193            self.execute_action(Some(rule), action, facts)?;
194        }
195
196        Ok(())
197    }
198
199    /// Execute a single action (has access to rule for TMS justifications)
200    fn execute_action(&self, rule: Option<&Rule>, action: &ActionType, facts: &mut Facts) -> Result<()> {
201        match action {
202            ActionType::Set { field, value } => {
203                // Evaluate value expression if needed
204                let evaluated_value = self.evaluate_value_expression(value, facts)?;
205
206                // If we have a TMS inserter and the field looks like "Type.field",
207                // attempt to create a TypedFacts wrapper and call the inserter as a logical assertion.
208                if let Some(inserter) = &self.tms_inserter {
209                    if let Some(dot_pos) = field.find('.') {
210                        let fact_type = field[..dot_pos].to_string();
211                        let field_name = field[dot_pos + 1..].to_string();
212
213                        // Build TypedFacts with this single field
214                        let mut typed = crate::rete::TypedFacts::new();
215                        // Map crate::types::Value -> rete::FactValue
216                        let fv = match &evaluated_value {
217                            crate::types::Value::String(s) => crate::rete::FactValue::String(s.clone()),
218                            crate::types::Value::Integer(i) => crate::rete::FactValue::Integer(*i),
219                            crate::types::Value::Number(n) => crate::rete::FactValue::Float(*n),
220                            crate::types::Value::Boolean(b) => crate::rete::FactValue::Boolean(*b),
221                            _ => crate::rete::FactValue::String(format!("{:?}", evaluated_value)),
222                        };
223
224                        typed.set(field_name, fv);
225
226                        // Build premise keys from the rule's conditions (best-effort):
227                        // format: "Type.field=value" so the RETE engine can map to handles.
228                        let premises = match rule {
229                            Some(r) => self.collect_premise_keys_from_rule(r, facts),
230                            None => Vec::new(),
231                        };
232
233                        // Call inserter with rule name (string-based premises)
234                        let source_name = rule.map(|r| r.name.clone()).unwrap_or_else(|| "<unknown>".to_string());
235                        (inserter)(fact_type, typed, source_name, premises);
236                        // Also apply to local Facts representation so backward search sees it
237                        facts.set(field, evaluated_value);
238                        return Ok(());
239                    }
240                }
241
242                // Fallback: just set into Facts
243                facts.set(field, evaluated_value);
244                Ok(())
245            }
246
247            ActionType::MethodCall { object, method, args } => {
248                // Execute method call
249                if let Some(obj_value) = facts.get(object) {
250                    let mut obj_value = obj_value.clone();
251                    // Evaluate arguments
252                    let mut arg_values = Vec::new();
253                    for arg in args {
254                        let val = self.evaluate_value_expression(arg, facts)?;
255                        arg_values.push(val);
256                    }
257
258                    // Call method
259                    let result = obj_value.call_method(method, arg_values)
260                        .map_err(|e| RuleEngineError::ExecutionError(e))?;
261
262                    // Update object
263                    facts.set(object, obj_value);
264
265                    // Store result if there's a return value
266                    if result != Value::Null {
267                        facts.set(&format!("{}._return", object), result);
268                    }
269
270                    Ok(())
271                } else {
272                    Err(RuleEngineError::ExecutionError(
273                        format!("Object not found: {}", object)
274                    ))
275                }
276            }
277
278            ActionType::Retract { object } => {
279                // Retract fact from working memory
280                // In backward chaining, we just remove the fact
281                facts.remove(object);
282                Ok(())
283            }
284
285            ActionType::Log { message } => {
286                // Just log for now
287                println!("[BC Action] {}", message);
288                Ok(())
289            }
290
291            ActionType::Custom { .. } => {
292                // Custom actions not supported in backward chaining yet
293                Ok(())
294            }
295
296            ActionType::ActivateAgendaGroup { .. } => {
297                // Agenda groups not supported in backward chaining
298                Ok(())
299            }
300
301            ActionType::ScheduleRule { .. } => {
302                // Rule scheduling not supported in backward chaining
303                Ok(())
304            }
305
306            ActionType::CompleteWorkflow { .. } => {
307                // Workflows not supported in backward chaining
308                Ok(())
309            }
310
311            ActionType::SetWorkflowData { .. } => {
312                // Workflow data not supported in backward chaining
313                Ok(())
314            }
315        }
316    }
317
318    /// Collect a best-effort list of premise keys from the rule's conditions.
319    /// Each entry has the format: "Type.field=value" when possible. This is
320    /// intentionally conservative: only field-based conditions with a dotted
321    /// "Type.field" expression are collected.
322    fn collect_premise_keys_from_rule(&self, rule: &Rule, facts: &Facts) -> Vec<String> {
323        use crate::engine::rule::{ConditionGroup, ConditionExpression};
324
325        let mut keys = Vec::new();
326
327        fn collect_from_group(group: &ConditionGroup, keys: &mut Vec<String>, facts: &Facts) {
328            match group {
329                ConditionGroup::Single(cond) => {
330                    if let ConditionExpression::Field(f) = &cond.expression {
331                        if let Some(dot_pos) = f.find('.') {
332                            let fact_type = &f[..dot_pos];
333                            let field_name = &f[dot_pos + 1..];
334
335                            // Try to get value from facts
336                            if let Some(val) = facts.get(f).or_else(|| facts.get_nested(f)) {
337                                let value_str = match val {
338                                    crate::types::Value::String(s) => s.clone(),
339                                    crate::types::Value::Integer(i) => i.to_string(),
340                                    crate::types::Value::Number(n) => n.to_string(),
341                                    crate::types::Value::Boolean(b) => b.to_string(),
342                                    _ => format!("{:?}", val),
343                                };
344
345                                keys.push(format!("{}.{}={}", fact_type, field_name, value_str));
346                            } else {
347                                // If we don't have a value at this time, still record the key without value
348                                keys.push(format!("{}.{}=", fact_type, field_name));
349                            }
350                        }
351                    }
352                }
353                ConditionGroup::Compound { left, right, .. } => {
354                    collect_from_group(left, keys, facts);
355                    collect_from_group(right, keys, facts);
356                }
357                // For other complex groups, skip
358                _ => {}
359            }
360        }
361
362        collect_from_group(&rule.conditions, &mut keys, facts);
363        keys
364    }
365
366    /// Evaluate value expression
367    fn evaluate_value_expression(&self, value: &Value, facts: &Facts) -> Result<Value> {
368        match value {
369            Value::Expression(expr) => {
370                // Try simple field lookup first
371                if let Some(val) = facts.get(expr).or_else(|| facts.get_nested(expr)) {
372                    return Ok(val);
373                }
374
375                // Try arithmetic expression evaluation
376                if let Some(result) = self.try_evaluate_arithmetic(expr, facts) {
377                    return Ok(result);
378                }
379
380                // Try to parse as literal
381                if expr == "true" {
382                    Ok(Value::Boolean(true))
383                } else if expr == "false" {
384                    Ok(Value::Boolean(false))
385                } else if expr == "null" {
386                    Ok(Value::Null)
387                } else if let Ok(n) = expr.parse::<f64>() {
388                    Ok(Value::Number(n))
389                } else if let Ok(i) = expr.parse::<i64>() {
390                    Ok(Value::Integer(i))
391                } else {
392                    // Try to parse as literal using simple parsing
393                    if expr == "true" {
394                        Ok(Value::Boolean(true))
395                    } else if expr == "false" {
396                        Ok(Value::Boolean(false))
397                    } else if expr == "null" {
398                        Ok(Value::Null)
399                    } else if let Ok(n) = expr.parse::<f64>() {
400                        Ok(Value::Number(n))
401                    } else if let Ok(i) = expr.parse::<i64>() {
402                        Ok(Value::Integer(i))
403                    } else {
404                        Ok(value.clone())
405                    }
406                }
407            }
408            _ => Ok(value.clone()),
409        }
410    }
411
412    /// Try to evaluate simple arithmetic expressions
413    /// Supports: +, -, *, /
414    fn try_evaluate_arithmetic(&self, expr: &str, facts: &Facts) -> Option<Value> {
415        // Check for division
416        if let Some(div_pos) = expr.find(" / ") {
417            let left = expr[..div_pos].trim();
418            let right = expr[div_pos + 3..].trim();
419
420            let left_val = self.get_numeric_value(left, facts)?;
421            let right_val = self.get_numeric_value(right, facts)?;
422
423            if right_val != 0.0 {
424                return Some(Value::Number(left_val / right_val));
425            }
426            return None;
427        }
428
429        // Check for multiplication
430        if let Some(mul_pos) = expr.find(" * ") {
431            let left = expr[..mul_pos].trim();
432            let right = expr[mul_pos + 3..].trim();
433
434            let left_val = self.get_numeric_value(left, facts)?;
435            let right_val = self.get_numeric_value(right, facts)?;
436
437            return Some(Value::Number(left_val * right_val));
438        }
439
440        // Check for addition
441        if let Some(add_pos) = expr.find(" + ") {
442            let left = expr[..add_pos].trim();
443            let right = expr[add_pos + 3..].trim();
444
445            let left_val = self.get_numeric_value(left, facts)?;
446            let right_val = self.get_numeric_value(right, facts)?;
447
448            return Some(Value::Number(left_val + right_val));
449        }
450
451        // Check for subtraction
452        if let Some(sub_pos) = expr.find(" - ") {
453            let left = expr[..sub_pos].trim();
454            let right = expr[sub_pos + 3..].trim();
455
456            let left_val = self.get_numeric_value(left, facts)?;
457            let right_val = self.get_numeric_value(right, facts)?;
458
459            return Some(Value::Number(left_val - right_val));
460        }
461
462        None
463    }
464
465    /// Get numeric value from field name or literal
466    fn get_numeric_value(&self, s: &str, facts: &Facts) -> Option<f64> {
467        // Try parsing as number first
468        if let Ok(n) = s.parse::<f64>() {
469            return Some(n);
470        }
471
472        // Try getting from facts
473        if let Some(val) = facts.get(s).or_else(|| facts.get_nested(s)) {
474            match val {
475                Value::Number(n) => Some(n),
476                Value::Integer(i) => Some(i as f64),
477                _ => None,
478            }
479        } else {
480            None
481        }
482    }
483}
484
485#[cfg(test)]
486mod tests {
487    use super::*;
488    use crate::types::Operator;
489
490    #[test]
491    fn test_evaluate_simple_condition() {
492        let kb = KnowledgeBase::new("test");
493        let executor = RuleExecutor::new(kb);
494
495        let mut facts = Facts::new();
496        facts.set("User.Age", Value::Number(25.0));
497
498        let condition = Condition::new(
499            "User.Age".to_string(),
500            Operator::GreaterThan,
501            Value::Number(18.0),
502        );
503
504        let result = executor.evaluate_condition(&condition, &facts).unwrap();
505        assert!(result);
506    }
507
508    #[test]
509    fn test_evaluate_function_call_len() {
510        let kb = KnowledgeBase::new("test");
511        let executor = RuleExecutor::new(kb);
512
513        let mut facts = Facts::new();
514        facts.set("User.Name", Value::String("John".to_string()));
515
516        let condition = Condition::with_function(
517            "len".to_string(),
518            vec!["User.Name".to_string()],
519            Operator::GreaterThan,
520            Value::Number(3.0),
521        );
522
523        let result = executor.evaluate_condition(&condition, &facts).unwrap();
524        assert!(result); // "John".len() = 4 > 3
525    }
526
527    #[test]
528    fn test_execute_set_action() {
529        let kb = KnowledgeBase::new("test");
530        let executor = RuleExecutor::new(kb);
531
532        let mut facts = Facts::new();
533
534        let action = ActionType::Set {
535            field: "User.IsVIP".to_string(),
536            value: Value::Boolean(true),
537        };
538
539        executor.execute_action(None, &action, &mut facts).unwrap();
540
541        assert_eq!(facts.get("User.IsVIP"), Some(Value::Boolean(true)));
542    }
543
544    #[test]
545    fn test_evaluate_compound_and_condition() {
546        let kb = KnowledgeBase::new("test");
547        let executor = RuleExecutor::new(kb);
548
549        let mut facts = Facts::new();
550        facts.set("User.Age", Value::Number(25.0));
551        facts.set("User.Country", Value::String("US".to_string()));
552
553        let conditions = ConditionGroup::Compound {
554            left: Box::new(ConditionGroup::Single(Condition::new(
555                "User.Age".to_string(),
556                Operator::GreaterThan,
557                Value::Number(18.0),
558            ))),
559            operator: crate::types::LogicalOperator::And,
560            right: Box::new(ConditionGroup::Single(Condition::new(
561                "User.Country".to_string(),
562                Operator::Equal,
563                Value::String("US".to_string()),
564            ))),
565        };
566
567        let result = executor.evaluate_conditions(&conditions, &facts).unwrap();
568        assert!(result);
569    }
570
571    #[test]
572    fn test_evaluate_compound_or_condition() {
573        let kb = KnowledgeBase::new("test");
574        let executor = RuleExecutor::new(kb);
575
576        let mut facts = Facts::new();
577        facts.set("User.Age", Value::Number(15.0));
578        facts.set("User.HasParentalConsent", Value::Boolean(true));
579
580        let conditions = ConditionGroup::Compound {
581            left: Box::new(ConditionGroup::Single(Condition::new(
582                "User.Age".to_string(),
583                Operator::GreaterThan,
584                Value::Number(18.0),
585            ))),
586            operator: crate::types::LogicalOperator::Or,
587            right: Box::new(ConditionGroup::Single(Condition::new(
588                "User.HasParentalConsent".to_string(),
589                Operator::Equal,
590                Value::Boolean(true),
591            ))),
592        };
593
594        let result = executor.evaluate_conditions(&conditions, &facts).unwrap();
595        assert!(result); // True because HasParentalConsent is true
596    }
597
598    #[test]
599    fn test_evaluate_not_condition() {
600        let kb = KnowledgeBase::new("test");
601        let executor = RuleExecutor::new(kb);
602
603        let mut facts = Facts::new();
604        facts.set("User.IsBanned", Value::Boolean(false));
605
606        let conditions = ConditionGroup::Not(Box::new(ConditionGroup::Single(
607            Condition::new(
608                "User.IsBanned".to_string(),
609                Operator::Equal,
610                Value::Boolean(true),
611            )
612        )));
613
614        let result = executor.evaluate_conditions(&conditions, &facts).unwrap();
615        assert!(result); // True because NOT banned
616    }
617
618    #[test]
619    fn test_evaluate_function_isempty() {
620        let kb = KnowledgeBase::new("test");
621        let executor = RuleExecutor::new(kb);
622
623        let mut facts = Facts::new();
624        facts.set("User.Description", Value::String("".to_string()));
625
626        let condition = Condition::with_function(
627            "isEmpty".to_string(),
628            vec!["User.Description".to_string()],
629            Operator::Equal,
630            Value::Boolean(true),
631        );
632
633        let result = executor.evaluate_condition(&condition, &facts).unwrap();
634        assert!(result); // Empty string
635    }
636
637    #[test]
638    fn test_evaluate_test_expression_exists() {
639        let kb = KnowledgeBase::new("test");
640        let executor = RuleExecutor::new(kb);
641
642        let mut facts = Facts::new();
643        facts.set("User.Email", Value::String("user@example.com".to_string()));
644
645        let condition = Condition {
646            field: "User.Email".to_string(),
647            expression: crate::engine::rule::ConditionExpression::Test {
648                name: "exists".to_string(),
649                args: vec!["User.Email".to_string()],
650            },
651            operator: Operator::Equal,
652            value: Value::Boolean(true),
653        };
654
655        let result = executor.evaluate_condition(&condition, &facts).unwrap();
656        assert!(result);
657    }
658
659    #[test]
660    fn test_execute_log_action() {
661        let kb = KnowledgeBase::new("test");
662        let executor = RuleExecutor::new(kb);
663
664        let mut facts = Facts::new();
665
666        let action = ActionType::Log {
667            message: "Test log message".to_string(),
668        };
669
670        // Should not panic
671        executor.execute_action(None, &action, &mut facts).unwrap();
672    }
673
674    #[test]
675    fn test_try_execute_rule_success() {
676        let kb = KnowledgeBase::new("test");
677        let executor = RuleExecutor::new(kb);
678
679        let mut facts = Facts::new();
680        facts.set("User.Age", Value::Number(25.0));
681
682        let conditions = ConditionGroup::Single(Condition::new(
683            "User.Age".to_string(),
684            Operator::GreaterThan,
685            Value::Number(18.0),
686        ));
687
688        let actions = vec![ActionType::Set {
689            field: "User.IsAdult".to_string(),
690            value: Value::Boolean(true),
691        }];
692
693        let rule = Rule::new("CheckAdult".to_string(), conditions, actions);
694
695        let executed = executor.try_execute_rule(&rule, &mut facts).unwrap();
696        assert!(executed);
697        assert_eq!(facts.get("User.IsAdult"), Some(Value::Boolean(true)));
698    }
699
700    #[test]
701    fn test_try_execute_rule_failure() {
702        let kb = KnowledgeBase::new("test");
703        let executor = RuleExecutor::new(kb);
704
705        let mut facts = Facts::new();
706        facts.set("User.Age", Value::Number(15.0));
707
708        let conditions = ConditionGroup::Single(Condition::new(
709            "User.Age".to_string(),
710            Operator::GreaterThan,
711            Value::Number(18.0),
712        ));
713
714        let actions = vec![ActionType::Set {
715            field: "User.IsAdult".to_string(),
716            value: Value::Boolean(true),
717        }];
718
719        let rule = Rule::new("CheckAdult".to_string(), conditions, actions);
720
721        let executed = executor.try_execute_rule(&rule, &mut facts).unwrap();
722        assert!(!executed); // Conditions not met
723        assert_eq!(facts.get("User.IsAdult"), None); // Action not executed
724    }
725
726    #[test]
727    fn test_evaluate_string_operators() {
728        let kb = KnowledgeBase::new("test");
729        let executor = RuleExecutor::new(kb);
730
731        let mut facts = Facts::new();
732        facts.set("User.Email", Value::String("user@example.com".to_string()));
733
734        // Test Contains
735        let condition = Condition::new(
736            "User.Email".to_string(),
737            Operator::Contains,
738            Value::String("@example".to_string()),
739        );
740        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
741
742        // Test StartsWith
743        let condition = Condition::new(
744            "User.Email".to_string(),
745            Operator::StartsWith,
746            Value::String("user".to_string()),
747        );
748        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
749
750        // Test EndsWith
751        let condition = Condition::new(
752            "User.Email".to_string(),
753            Operator::EndsWith,
754            Value::String(".com".to_string()),
755        );
756        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
757    }
758
759    #[test]
760    fn test_evaluate_numeric_operators() {
761        let kb = KnowledgeBase::new("test");
762        let executor = RuleExecutor::new(kb);
763
764        let mut facts = Facts::new();
765        facts.set("Order.Amount", Value::Number(1500.0));
766
767        // Test GreaterThanOrEqual
768        let condition = Condition::new(
769            "Order.Amount".to_string(),
770            Operator::GreaterThanOrEqual,
771            Value::Number(1500.0),
772        );
773        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
774
775        // Test LessThan
776        let condition = Condition::new(
777            "Order.Amount".to_string(),
778            Operator::LessThan,
779            Value::Number(2000.0),
780        );
781        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
782
783        // Test NotEqual
784        let condition = Condition::new(
785            "Order.Amount".to_string(),
786            Operator::NotEqual,
787            Value::Number(1000.0),
788        );
789        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
790    }
791
792    #[test]
793    fn test_evaluate_missing_field() {
794        let kb = KnowledgeBase::new("test");
795        let executor = RuleExecutor::new(kb);
796
797        let facts = Facts::new(); // Empty facts
798
799        let condition = Condition::new(
800            "User.Age".to_string(),
801            Operator::GreaterThan,
802            Value::Number(18.0),
803        );
804
805        let result = executor.evaluate_condition(&condition, &facts).unwrap();
806        assert!(!result); // Missing field evaluates to false
807    }
808
809    #[test]
810    fn test_execute_multiple_actions() {
811        let kb = KnowledgeBase::new("test");
812        let executor = RuleExecutor::new(kb);
813
814        let mut facts = Facts::new();
815        facts.set("User.Points", Value::Number(150.0));
816
817        let conditions = ConditionGroup::Single(Condition::new(
818            "User.Points".to_string(),
819            Operator::GreaterThan,
820            Value::Number(100.0),
821        ));
822
823        let actions = vec![
824            ActionType::Set {
825                field: "User.IsVIP".to_string(),
826                value: Value::Boolean(true),
827            },
828            ActionType::Log {
829                message: "User promoted to VIP".to_string(),
830            },
831            ActionType::Set {
832                field: "User.Discount".to_string(),
833                value: Value::Number(0.2),
834            },
835        ];
836
837        let rule = Rule::new("PromoteToVIP".to_string(), conditions, actions);
838
839        let executed = executor.try_execute_rule(&rule, &mut facts).unwrap();
840        assert!(executed);
841        assert_eq!(facts.get("User.IsVIP"), Some(Value::Boolean(true)));
842        assert_eq!(facts.get("User.Discount"), Some(Value::Number(0.2)));
843    }
844
845    #[test]
846    fn test_evaluate_endswith_operator() {
847        let kb = KnowledgeBase::new("test");
848        let executor = RuleExecutor::new(kb);
849
850        let mut facts = Facts::new();
851        facts.set("User.Email", Value::String("user@example.com".to_string()));
852        facts.set("File.Name", Value::String("document.pdf".to_string()));
853        facts.set("Domain.URL", Value::String("https://api.example.org".to_string()));
854
855        // Test EndsWith with .com suffix
856        let condition = Condition::new(
857            "User.Email".to_string(),
858            Operator::EndsWith,
859            Value::String(".com".to_string()),
860        );
861        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
862
863        // Test EndsWith with .pdf suffix
864        let condition = Condition::new(
865            "File.Name".to_string(),
866            Operator::EndsWith,
867            Value::String(".pdf".to_string()),
868        );
869        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
870
871        // Test EndsWith with .org suffix
872        let condition = Condition::new(
873            "Domain.URL".to_string(),
874            Operator::EndsWith,
875            Value::String(".org".to_string()),
876        );
877        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
878
879        // Test EndsWith that should fail
880        let condition = Condition::new(
881            "User.Email".to_string(),
882            Operator::EndsWith,
883            Value::String(".net".to_string()),
884        );
885        assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
886
887        // Test EndsWith with full string match
888        let condition = Condition::new(
889            "File.Name".to_string(),
890            Operator::EndsWith,
891            Value::String("document.pdf".to_string()),
892        );
893        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
894    }
895
896    #[test]
897    fn test_evaluate_endswith_edge_cases() {
898        let kb = KnowledgeBase::new("test");
899        let executor = RuleExecutor::new(kb);
900
901        let mut facts = Facts::new();
902        facts.set("Empty.String", Value::String("".to_string()));
903        facts.set("Single.Char", Value::String("a".to_string()));
904        facts.set("Number.Value", Value::Number(123.0));
905
906        // Test EndsWith with empty string (should match everything)
907        let condition = Condition::new(
908            "Empty.String".to_string(),
909            Operator::EndsWith,
910            Value::String("".to_string()),
911        );
912        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
913
914        // Test EndsWith on single character
915        let condition = Condition::new(
916            "Single.Char".to_string(),
917            Operator::EndsWith,
918            Value::String("a".to_string()),
919        );
920        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
921
922        // Test EndsWith with non-string value (should fail gracefully)
923        let condition = Condition::new(
924            "Number.Value".to_string(),
925            Operator::EndsWith,
926            Value::String(".0".to_string()),
927        );
928        assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
929
930        // Test EndsWith on missing field (should fail gracefully)
931        let condition = Condition::new(
932            "Missing.Field".to_string(),
933            Operator::EndsWith,
934            Value::String("test".to_string()),
935        );
936        assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
937
938        // Test case sensitivity
939        let mut facts2 = Facts::new();
940        facts2.set("Text.Value", Value::String("HelloWorld".to_string()));
941
942        let condition = Condition::new(
943            "Text.Value".to_string(),
944            Operator::EndsWith,
945            Value::String("world".to_string()),
946        );
947        assert!(!executor.evaluate_condition(&condition, &facts2).unwrap()); // Should fail due to case mismatch
948
949        let condition = Condition::new(
950            "Text.Value".to_string(),
951            Operator::EndsWith,
952            Value::String("World".to_string()),
953        );
954        assert!(executor.evaluate_condition(&condition, &facts2).unwrap()); // Should pass with correct case
955    }
956
957    #[test]
958    fn test_evaluate_matches_operator() {
959        let kb = KnowledgeBase::new("test");
960        let executor = RuleExecutor::new(kb);
961
962        let mut facts = Facts::new();
963        facts.set("User.Email", Value::String("user@example.com".to_string()));
964        facts.set("Product.Name", Value::String("Premium Laptop Model X".to_string()));
965        facts.set("Log.Message", Value::String("Error: Connection timeout".to_string()));
966
967        // Test Matches with pattern "example"
968        let condition = Condition::new(
969            "User.Email".to_string(),
970            Operator::Matches,
971            Value::String("example".to_string()),
972        );
973        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
974
975        // Test Matches with pattern "Premium"
976        let condition = Condition::new(
977            "Product.Name".to_string(),
978            Operator::Matches,
979            Value::String("Premium".to_string()),
980        );
981        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
982
983        // Test Matches with pattern "Error"
984        let condition = Condition::new(
985            "Log.Message".to_string(),
986            Operator::Matches,
987            Value::String("Error".to_string()),
988        );
989        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
990
991        // Test Matches that should fail
992        let condition = Condition::new(
993            "User.Email".to_string(),
994            Operator::Matches,
995            Value::String("notfound".to_string()),
996        );
997        assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
998
999        // Test Matches with partial pattern
1000        let condition = Condition::new(
1001            "Product.Name".to_string(),
1002            Operator::Matches,
1003            Value::String("Laptop".to_string()),
1004        );
1005        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1006
1007        // Test Matches with full string
1008        let condition = Condition::new(
1009            "Log.Message".to_string(),
1010            Operator::Matches,
1011            Value::String("Error: Connection timeout".to_string()),
1012        );
1013        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1014    }
1015
1016    #[test]
1017    fn test_evaluate_matches_edge_cases() {
1018        let kb = KnowledgeBase::new("test");
1019        let executor = RuleExecutor::new(kb);
1020
1021        let mut facts = Facts::new();
1022        facts.set("Empty.String", Value::String("".to_string()));
1023        facts.set("Single.Char", Value::String("x".to_string()));
1024        facts.set("Number.Value", Value::Number(456.0));
1025        facts.set("Special.Chars", Value::String("test@#$%^&*()".to_string()));
1026
1027        // Test Matches with empty pattern (should match empty string)
1028        let condition = Condition::new(
1029            "Empty.String".to_string(),
1030            Operator::Matches,
1031            Value::String("".to_string()),
1032        );
1033        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1034
1035        // Test Matches on single character
1036        let condition = Condition::new(
1037            "Single.Char".to_string(),
1038            Operator::Matches,
1039            Value::String("x".to_string()),
1040        );
1041        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1042
1043        // Test Matches with non-string value (should fail gracefully)
1044        let condition = Condition::new(
1045            "Number.Value".to_string(),
1046            Operator::Matches,
1047            Value::String("456".to_string()),
1048        );
1049        assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
1050
1051        // Test Matches on missing field (should fail gracefully)
1052        let condition = Condition::new(
1053            "Missing.Field".to_string(),
1054            Operator::Matches,
1055            Value::String("pattern".to_string()),
1056        );
1057        assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
1058
1059        // Test Matches with special characters
1060        let condition = Condition::new(
1061            "Special.Chars".to_string(),
1062            Operator::Matches,
1063            Value::String("@#$".to_string()),
1064        );
1065        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1066
1067        // Test case sensitivity
1068        let mut facts2 = Facts::new();
1069        facts2.set("Text.Value", Value::String("HelloWorld".to_string()));
1070
1071        let condition = Condition::new(
1072            "Text.Value".to_string(),
1073            Operator::Matches,
1074            Value::String("hello".to_string()),
1075        );
1076        assert!(!executor.evaluate_condition(&condition, &facts2).unwrap()); // Should fail due to case mismatch
1077
1078        let condition = Condition::new(
1079            "Text.Value".to_string(),
1080            Operator::Matches,
1081            Value::String("Hello".to_string()),
1082        );
1083        assert!(executor.evaluate_condition(&condition, &facts2).unwrap()); // Should pass with correct case
1084    }
1085
1086    #[test]
1087    fn test_endswith_matches_in_rules() {
1088        // Integration test: EndsWith and Matches in actual rules
1089        let kb = KnowledgeBase::new("test");
1090
1091        // Rule 1: If email ends with .edu, set IsStudent = true
1092        let condition1 = Condition::new(
1093            "User.Email".to_string(),
1094            Operator::EndsWith,
1095            Value::String(".edu".to_string()),
1096        );
1097        let actions1 = vec![ActionType::Set {
1098            field: "User.IsStudent".to_string(),
1099            value: Value::Boolean(true),
1100        }];
1101        let rule1 = Rule::new(
1102            "StudentEmailRule".to_string(),
1103            ConditionGroup::Single(condition1),
1104            actions1,
1105        );
1106
1107        // Rule 2: If product name matches "Premium", set IsPremium = true
1108        let condition2 = Condition::new(
1109            "Product.Name".to_string(),
1110            Operator::Matches,
1111            Value::String("Premium".to_string()),
1112        );
1113        let actions2 = vec![ActionType::Set {
1114            field: "Product.IsPremium".to_string(),
1115            value: Value::Boolean(true),
1116        }];
1117        let rule2 = Rule::new(
1118            "PremiumProductRule".to_string(),
1119            ConditionGroup::Single(condition2),
1120            actions2,
1121        );
1122
1123        let _ = kb.add_rule(rule1.clone());
1124        let _ = kb.add_rule(rule2.clone());
1125
1126        let executor = RuleExecutor::new(kb);
1127
1128        // Test scenario 1: Student email
1129        let mut facts1 = Facts::new();
1130        facts1.set("User.Email", Value::String("student@university.edu".to_string()));
1131
1132        let executed = executor.try_execute_rule(&rule1, &mut facts1).unwrap();
1133        assert!(executed);
1134        assert_eq!(facts1.get("User.IsStudent"), Some(Value::Boolean(true)));
1135
1136        // Test scenario 2: Premium product
1137        let mut facts2 = Facts::new();
1138        facts2.set("Product.Name", Value::String("Premium Laptop X1".to_string()));
1139
1140        let executed = executor.try_execute_rule(&rule2, &mut facts2).unwrap();
1141        assert!(executed);
1142        assert_eq!(facts2.get("Product.IsPremium"), Some(Value::Boolean(true)));
1143
1144        // Test scenario 3: Non-matching cases
1145        let mut facts3 = Facts::new();
1146        facts3.set("User.Email", Value::String("user@company.com".to_string()));
1147
1148        let executed = executor.try_execute_rule(&rule1, &mut facts3).unwrap();
1149        assert!(!executed); // Should not execute because email doesn't end with .edu
1150    }
1151}