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