Skip to main content

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            ActionType::Append { field, value } => {
333                // Evaluate value expression if needed
334                let evaluated_value = self.evaluate_value_expression(value, facts)?;
335
336                // Get current array or create new one
337                let current_value = facts.get(field);
338                let mut array = match current_value {
339                    Some(Value::Array(arr)) => arr.clone(),
340                    Some(_) => {
341                        // Field exists but is not an array, create new array
342                        Vec::new()
343                    }
344                    None => Vec::new(),
345                };
346
347                // Append value
348                array.push(evaluated_value);
349
350                // Set the updated array
351                facts.set(field, Value::Array(array));
352
353                Ok(())
354            }
355        }
356    }
357
358    /// Collect a best-effort list of premise keys from the rule's conditions.
359    /// Each entry has the format: "Type.field=value" when possible. This is
360    /// intentionally conservative: only field-based conditions with a dotted
361    /// "Type.field" expression are collected.
362    fn collect_premise_keys_from_rule(&self, rule: &Rule, facts: &Facts) -> Vec<String> {
363        use crate::engine::rule::{ConditionExpression, ConditionGroup};
364
365        let mut keys = Vec::new();
366
367        fn collect_from_group(group: &ConditionGroup, keys: &mut Vec<String>, facts: &Facts) {
368            match group {
369                ConditionGroup::Single(cond) => {
370                    if let ConditionExpression::Field(f) = &cond.expression {
371                        if let Some(dot_pos) = f.find('.') {
372                            let fact_type = &f[..dot_pos];
373                            let field_name = &f[dot_pos + 1..];
374
375                            // Try to get value from facts
376                            if let Some(val) = facts.get(f).or_else(|| facts.get_nested(f)) {
377                                let value_str = match val {
378                                    crate::types::Value::String(s) => s.clone(),
379                                    crate::types::Value::Integer(i) => i.to_string(),
380                                    crate::types::Value::Number(n) => n.to_string(),
381                                    crate::types::Value::Boolean(b) => b.to_string(),
382                                    _ => format!("{:?}", val),
383                                };
384
385                                keys.push(format!("{}.{}={}", fact_type, field_name, value_str));
386                            } else {
387                                // If we don't have a value at this time, still record the key without value
388                                keys.push(format!("{}.{}=", fact_type, field_name));
389                            }
390                        }
391                    }
392                }
393                ConditionGroup::Compound { left, right, .. } => {
394                    collect_from_group(left, keys, facts);
395                    collect_from_group(right, keys, facts);
396                }
397                // For other complex groups, skip
398                _ => {}
399            }
400        }
401
402        collect_from_group(&rule.conditions, &mut keys, facts);
403        keys
404    }
405
406    /// Evaluate value expression
407    fn evaluate_value_expression(&self, value: &Value, facts: &Facts) -> Result<Value> {
408        match value {
409            Value::Expression(expr) => {
410                // Try simple field lookup first
411                if let Some(val) = facts.get(expr).or_else(|| facts.get_nested(expr)) {
412                    return Ok(val);
413                }
414
415                // Try arithmetic expression evaluation
416                if let Some(result) = self.try_evaluate_arithmetic(expr, facts) {
417                    return Ok(result);
418                }
419
420                // Try to parse as literal
421                if expr == "true" {
422                    Ok(Value::Boolean(true))
423                } else if expr == "false" {
424                    Ok(Value::Boolean(false))
425                } else if expr == "null" {
426                    Ok(Value::Null)
427                } else if let Ok(n) = expr.parse::<f64>() {
428                    Ok(Value::Number(n))
429                } else if let Ok(i) = expr.parse::<i64>() {
430                    Ok(Value::Integer(i))
431                } else {
432                    // Try to parse as literal using simple parsing
433                    if expr == "true" {
434                        Ok(Value::Boolean(true))
435                    } else if expr == "false" {
436                        Ok(Value::Boolean(false))
437                    } else if expr == "null" {
438                        Ok(Value::Null)
439                    } else if let Ok(n) = expr.parse::<f64>() {
440                        Ok(Value::Number(n))
441                    } else if let Ok(i) = expr.parse::<i64>() {
442                        Ok(Value::Integer(i))
443                    } else {
444                        Ok(value.clone())
445                    }
446                }
447            }
448            _ => Ok(value.clone()),
449        }
450    }
451
452    /// Try to evaluate simple arithmetic expressions
453    /// Supports: +, -, *, /
454    fn try_evaluate_arithmetic(&self, expr: &str, facts: &Facts) -> Option<Value> {
455        // Check for division
456        if let Some(div_pos) = expr.find(" / ") {
457            let left = expr[..div_pos].trim();
458            let right = expr[div_pos + 3..].trim();
459
460            let left_val = self.get_numeric_value(left, facts)?;
461            let right_val = self.get_numeric_value(right, facts)?;
462
463            if right_val != 0.0 {
464                return Some(Value::Number(left_val / right_val));
465            }
466            return None;
467        }
468
469        // Check for multiplication
470        if let Some(mul_pos) = expr.find(" * ") {
471            let left = expr[..mul_pos].trim();
472            let right = expr[mul_pos + 3..].trim();
473
474            let left_val = self.get_numeric_value(left, facts)?;
475            let right_val = self.get_numeric_value(right, facts)?;
476
477            return Some(Value::Number(left_val * right_val));
478        }
479
480        // Check for addition
481        if let Some(add_pos) = expr.find(" + ") {
482            let left = expr[..add_pos].trim();
483            let right = expr[add_pos + 3..].trim();
484
485            let left_val = self.get_numeric_value(left, facts)?;
486            let right_val = self.get_numeric_value(right, facts)?;
487
488            return Some(Value::Number(left_val + right_val));
489        }
490
491        // Check for subtraction
492        if let Some(sub_pos) = expr.find(" - ") {
493            let left = expr[..sub_pos].trim();
494            let right = expr[sub_pos + 3..].trim();
495
496            let left_val = self.get_numeric_value(left, facts)?;
497            let right_val = self.get_numeric_value(right, facts)?;
498
499            return Some(Value::Number(left_val - right_val));
500        }
501
502        None
503    }
504
505    /// Get numeric value from field name or literal
506    fn get_numeric_value(&self, s: &str, facts: &Facts) -> Option<f64> {
507        // Try parsing as number first
508        if let Ok(n) = s.parse::<f64>() {
509            return Some(n);
510        }
511
512        // Try getting from facts
513        if let Some(val) = facts.get(s).or_else(|| facts.get_nested(s)) {
514            match val {
515                Value::Number(n) => Some(n),
516                Value::Integer(i) => Some(i as f64),
517                _ => None,
518            }
519        } else {
520            None
521        }
522    }
523}
524
525#[cfg(test)]
526mod tests {
527    use super::*;
528    use crate::types::Operator;
529
530    #[test]
531    fn test_evaluate_simple_condition() {
532        let kb = KnowledgeBase::new("test");
533        let executor = RuleExecutor::new(kb);
534
535        let facts = Facts::new();
536        facts.set("User.Age", Value::Number(25.0));
537
538        let condition = Condition::new(
539            "User.Age".to_string(),
540            Operator::GreaterThan,
541            Value::Number(18.0),
542        );
543
544        let result = executor.evaluate_condition(&condition, &facts).unwrap();
545        assert!(result);
546    }
547
548    #[test]
549    fn test_evaluate_function_call_len() {
550        let kb = KnowledgeBase::new("test");
551        let executor = RuleExecutor::new(kb);
552
553        let facts = Facts::new();
554        facts.set("User.Name", Value::String("John".to_string()));
555
556        let condition = Condition::with_function(
557            "len".to_string(),
558            vec!["User.Name".to_string()],
559            Operator::GreaterThan,
560            Value::Number(3.0),
561        );
562
563        let result = executor.evaluate_condition(&condition, &facts).unwrap();
564        assert!(result); // "John".len() = 4 > 3
565    }
566
567    #[test]
568    fn test_execute_set_action() {
569        let kb = KnowledgeBase::new("test");
570        let executor = RuleExecutor::new(kb);
571
572        let mut facts = Facts::new();
573
574        let action = ActionType::Set {
575            field: "User.IsVIP".to_string(),
576            value: Value::Boolean(true),
577        };
578
579        executor.execute_action(None, &action, &mut facts).unwrap();
580
581        assert_eq!(facts.get("User.IsVIP"), Some(Value::Boolean(true)));
582    }
583
584    #[test]
585    fn test_evaluate_compound_and_condition() {
586        let kb = KnowledgeBase::new("test");
587        let executor = RuleExecutor::new(kb);
588
589        let facts = Facts::new();
590        facts.set("User.Age", Value::Number(25.0));
591        facts.set("User.Country", Value::String("US".to_string()));
592
593        let conditions = ConditionGroup::Compound {
594            left: Box::new(ConditionGroup::Single(Condition::new(
595                "User.Age".to_string(),
596                Operator::GreaterThan,
597                Value::Number(18.0),
598            ))),
599            operator: crate::types::LogicalOperator::And,
600            right: Box::new(ConditionGroup::Single(Condition::new(
601                "User.Country".to_string(),
602                Operator::Equal,
603                Value::String("US".to_string()),
604            ))),
605        };
606
607        let result = executor.evaluate_conditions(&conditions, &facts).unwrap();
608        assert!(result);
609    }
610
611    #[test]
612    fn test_evaluate_compound_or_condition() {
613        let kb = KnowledgeBase::new("test");
614        let executor = RuleExecutor::new(kb);
615
616        let facts = Facts::new();
617        facts.set("User.Age", Value::Number(15.0));
618        facts.set("User.HasParentalConsent", Value::Boolean(true));
619
620        let conditions = ConditionGroup::Compound {
621            left: Box::new(ConditionGroup::Single(Condition::new(
622                "User.Age".to_string(),
623                Operator::GreaterThan,
624                Value::Number(18.0),
625            ))),
626            operator: crate::types::LogicalOperator::Or,
627            right: Box::new(ConditionGroup::Single(Condition::new(
628                "User.HasParentalConsent".to_string(),
629                Operator::Equal,
630                Value::Boolean(true),
631            ))),
632        };
633
634        let result = executor.evaluate_conditions(&conditions, &facts).unwrap();
635        assert!(result); // True because HasParentalConsent is true
636    }
637
638    #[test]
639    fn test_evaluate_not_condition() {
640        let kb = KnowledgeBase::new("test");
641        let executor = RuleExecutor::new(kb);
642
643        let facts = Facts::new();
644        facts.set("User.IsBanned", Value::Boolean(false));
645
646        let conditions = ConditionGroup::Not(Box::new(ConditionGroup::Single(Condition::new(
647            "User.IsBanned".to_string(),
648            Operator::Equal,
649            Value::Boolean(true),
650        ))));
651
652        let result = executor.evaluate_conditions(&conditions, &facts).unwrap();
653        assert!(result); // True because NOT banned
654    }
655
656    #[test]
657    fn test_evaluate_function_isempty() {
658        let kb = KnowledgeBase::new("test");
659        let executor = RuleExecutor::new(kb);
660
661        let facts = Facts::new();
662        facts.set("User.Description", Value::String("".to_string()));
663
664        let condition = Condition::with_function(
665            "isEmpty".to_string(),
666            vec!["User.Description".to_string()],
667            Operator::Equal,
668            Value::Boolean(true),
669        );
670
671        let result = executor.evaluate_condition(&condition, &facts).unwrap();
672        assert!(result); // Empty string
673    }
674
675    #[test]
676    fn test_evaluate_test_expression_exists() {
677        let kb = KnowledgeBase::new("test");
678        let executor = RuleExecutor::new(kb);
679
680        let facts = Facts::new();
681        facts.set("User.Email", Value::String("user@example.com".to_string()));
682
683        let condition = Condition {
684            field: "User.Email".to_string(),
685            expression: crate::engine::rule::ConditionExpression::Test {
686                name: "exists".to_string(),
687                args: vec!["User.Email".to_string()],
688            },
689            operator: Operator::Equal,
690            value: Value::Boolean(true),
691        };
692
693        let result = executor.evaluate_condition(&condition, &facts).unwrap();
694        assert!(result);
695    }
696
697    #[test]
698    fn test_execute_log_action() {
699        let kb = KnowledgeBase::new("test");
700        let executor = RuleExecutor::new(kb);
701
702        let mut facts = Facts::new();
703
704        let action = ActionType::Log {
705            message: "Test log message".to_string(),
706        };
707
708        // Should not panic
709        executor.execute_action(None, &action, &mut facts).unwrap();
710    }
711
712    #[test]
713    fn test_try_execute_rule_success() {
714        let kb = KnowledgeBase::new("test");
715        let executor = RuleExecutor::new(kb);
716
717        let mut facts = Facts::new();
718        facts.set("User.Age", Value::Number(25.0));
719
720        let conditions = ConditionGroup::Single(Condition::new(
721            "User.Age".to_string(),
722            Operator::GreaterThan,
723            Value::Number(18.0),
724        ));
725
726        let actions = vec![ActionType::Set {
727            field: "User.IsAdult".to_string(),
728            value: Value::Boolean(true),
729        }];
730
731        let rule = Rule::new("CheckAdult".to_string(), conditions, actions);
732
733        let executed = executor.try_execute_rule(&rule, &mut facts).unwrap();
734        assert!(executed);
735        assert_eq!(facts.get("User.IsAdult"), Some(Value::Boolean(true)));
736    }
737
738    #[test]
739    fn test_try_execute_rule_failure() {
740        let kb = KnowledgeBase::new("test");
741        let executor = RuleExecutor::new(kb);
742
743        let mut facts = Facts::new();
744        facts.set("User.Age", Value::Number(15.0));
745
746        let conditions = ConditionGroup::Single(Condition::new(
747            "User.Age".to_string(),
748            Operator::GreaterThan,
749            Value::Number(18.0),
750        ));
751
752        let actions = vec![ActionType::Set {
753            field: "User.IsAdult".to_string(),
754            value: Value::Boolean(true),
755        }];
756
757        let rule = Rule::new("CheckAdult".to_string(), conditions, actions);
758
759        let executed = executor.try_execute_rule(&rule, &mut facts).unwrap();
760        assert!(!executed); // Conditions not met
761        assert_eq!(facts.get("User.IsAdult"), None); // Action not executed
762    }
763
764    #[test]
765    fn test_evaluate_string_operators() {
766        let kb = KnowledgeBase::new("test");
767        let executor = RuleExecutor::new(kb);
768
769        let facts = Facts::new();
770        facts.set("User.Email", Value::String("user@example.com".to_string()));
771
772        // Test Contains
773        let condition = Condition::new(
774            "User.Email".to_string(),
775            Operator::Contains,
776            Value::String("@example".to_string()),
777        );
778        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
779
780        // Test StartsWith
781        let condition = Condition::new(
782            "User.Email".to_string(),
783            Operator::StartsWith,
784            Value::String("user".to_string()),
785        );
786        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
787
788        // Test EndsWith
789        let condition = Condition::new(
790            "User.Email".to_string(),
791            Operator::EndsWith,
792            Value::String(".com".to_string()),
793        );
794        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
795    }
796
797    #[test]
798    fn test_evaluate_numeric_operators() {
799        let kb = KnowledgeBase::new("test");
800        let executor = RuleExecutor::new(kb);
801
802        let facts = Facts::new();
803        facts.set("Order.Amount", Value::Number(1500.0));
804
805        // Test GreaterThanOrEqual
806        let condition = Condition::new(
807            "Order.Amount".to_string(),
808            Operator::GreaterThanOrEqual,
809            Value::Number(1500.0),
810        );
811        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
812
813        // Test LessThan
814        let condition = Condition::new(
815            "Order.Amount".to_string(),
816            Operator::LessThan,
817            Value::Number(2000.0),
818        );
819        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
820
821        // Test NotEqual
822        let condition = Condition::new(
823            "Order.Amount".to_string(),
824            Operator::NotEqual,
825            Value::Number(1000.0),
826        );
827        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
828    }
829
830    #[test]
831    fn test_evaluate_missing_field() {
832        let kb = KnowledgeBase::new("test");
833        let executor = RuleExecutor::new(kb);
834
835        let facts = Facts::new(); // Empty facts
836
837        let condition = Condition::new(
838            "User.Age".to_string(),
839            Operator::GreaterThan,
840            Value::Number(18.0),
841        );
842
843        let result = executor.evaluate_condition(&condition, &facts).unwrap();
844        assert!(!result); // Missing field evaluates to false
845    }
846
847    #[test]
848    fn test_execute_multiple_actions() {
849        let kb = KnowledgeBase::new("test");
850        let executor = RuleExecutor::new(kb);
851
852        let mut facts = Facts::new();
853        facts.set("User.Points", Value::Number(150.0));
854
855        let conditions = ConditionGroup::Single(Condition::new(
856            "User.Points".to_string(),
857            Operator::GreaterThan,
858            Value::Number(100.0),
859        ));
860
861        let actions = vec![
862            ActionType::Set {
863                field: "User.IsVIP".to_string(),
864                value: Value::Boolean(true),
865            },
866            ActionType::Log {
867                message: "User promoted to VIP".to_string(),
868            },
869            ActionType::Set {
870                field: "User.Discount".to_string(),
871                value: Value::Number(0.2),
872            },
873        ];
874
875        let rule = Rule::new("PromoteToVIP".to_string(), conditions, actions);
876
877        let executed = executor.try_execute_rule(&rule, &mut facts).unwrap();
878        assert!(executed);
879        assert_eq!(facts.get("User.IsVIP"), Some(Value::Boolean(true)));
880        assert_eq!(facts.get("User.Discount"), Some(Value::Number(0.2)));
881    }
882
883    #[test]
884    fn test_evaluate_endswith_operator() {
885        let kb = KnowledgeBase::new("test");
886        let executor = RuleExecutor::new(kb);
887
888        let facts = Facts::new();
889        facts.set("User.Email", Value::String("user@example.com".to_string()));
890        facts.set("File.Name", Value::String("document.pdf".to_string()));
891        facts.set(
892            "Domain.URL",
893            Value::String("https://api.example.org".to_string()),
894        );
895
896        // Test EndsWith with .com suffix
897        let condition = Condition::new(
898            "User.Email".to_string(),
899            Operator::EndsWith,
900            Value::String(".com".to_string()),
901        );
902        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
903
904        // Test EndsWith with .pdf suffix
905        let condition = Condition::new(
906            "File.Name".to_string(),
907            Operator::EndsWith,
908            Value::String(".pdf".to_string()),
909        );
910        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
911
912        // Test EndsWith with .org suffix
913        let condition = Condition::new(
914            "Domain.URL".to_string(),
915            Operator::EndsWith,
916            Value::String(".org".to_string()),
917        );
918        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
919
920        // Test EndsWith that should fail
921        let condition = Condition::new(
922            "User.Email".to_string(),
923            Operator::EndsWith,
924            Value::String(".net".to_string()),
925        );
926        assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
927
928        // Test EndsWith with full string match
929        let condition = Condition::new(
930            "File.Name".to_string(),
931            Operator::EndsWith,
932            Value::String("document.pdf".to_string()),
933        );
934        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
935    }
936
937    #[test]
938    fn test_evaluate_endswith_edge_cases() {
939        let kb = KnowledgeBase::new("test");
940        let executor = RuleExecutor::new(kb);
941
942        let facts = Facts::new();
943        facts.set("Empty.String", Value::String("".to_string()));
944        facts.set("Single.Char", Value::String("a".to_string()));
945        facts.set("Number.Value", Value::Number(123.0));
946
947        // Test EndsWith with empty string (should match everything)
948        let condition = Condition::new(
949            "Empty.String".to_string(),
950            Operator::EndsWith,
951            Value::String("".to_string()),
952        );
953        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
954
955        // Test EndsWith on single character
956        let condition = Condition::new(
957            "Single.Char".to_string(),
958            Operator::EndsWith,
959            Value::String("a".to_string()),
960        );
961        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
962
963        // Test EndsWith with non-string value (should fail gracefully)
964        let condition = Condition::new(
965            "Number.Value".to_string(),
966            Operator::EndsWith,
967            Value::String(".0".to_string()),
968        );
969        assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
970
971        // Test EndsWith on missing field (should fail gracefully)
972        let condition = Condition::new(
973            "Missing.Field".to_string(),
974            Operator::EndsWith,
975            Value::String("test".to_string()),
976        );
977        assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
978
979        // Test case sensitivity
980        let facts2 = Facts::new();
981        facts2.set("Text.Value", Value::String("HelloWorld".to_string()));
982
983        let condition = Condition::new(
984            "Text.Value".to_string(),
985            Operator::EndsWith,
986            Value::String("world".to_string()),
987        );
988        assert!(!executor.evaluate_condition(&condition, &facts2).unwrap()); // Should fail due to case mismatch
989
990        let condition = Condition::new(
991            "Text.Value".to_string(),
992            Operator::EndsWith,
993            Value::String("World".to_string()),
994        );
995        assert!(executor.evaluate_condition(&condition, &facts2).unwrap()); // Should pass with correct case
996    }
997
998    #[test]
999    fn test_evaluate_matches_operator() {
1000        let kb = KnowledgeBase::new("test");
1001        let executor = RuleExecutor::new(kb);
1002
1003        let facts = Facts::new();
1004        facts.set("User.Email", Value::String("user@example.com".to_string()));
1005        facts.set(
1006            "Product.Name",
1007            Value::String("Premium Laptop Model X".to_string()),
1008        );
1009        facts.set(
1010            "Log.Message",
1011            Value::String("Error: Connection timeout".to_string()),
1012        );
1013
1014        // Test Matches with pattern "example"
1015        let condition = Condition::new(
1016            "User.Email".to_string(),
1017            Operator::Matches,
1018            Value::String("example".to_string()),
1019        );
1020        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1021
1022        // Test Matches with pattern "Premium"
1023        let condition = Condition::new(
1024            "Product.Name".to_string(),
1025            Operator::Matches,
1026            Value::String("Premium".to_string()),
1027        );
1028        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1029
1030        // Test Matches with pattern "Error"
1031        let condition = Condition::new(
1032            "Log.Message".to_string(),
1033            Operator::Matches,
1034            Value::String("Error".to_string()),
1035        );
1036        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1037
1038        // Test Matches that should fail
1039        let condition = Condition::new(
1040            "User.Email".to_string(),
1041            Operator::Matches,
1042            Value::String("notfound".to_string()),
1043        );
1044        assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
1045
1046        // Test Matches with partial pattern
1047        let condition = Condition::new(
1048            "Product.Name".to_string(),
1049            Operator::Matches,
1050            Value::String("Laptop".to_string()),
1051        );
1052        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1053
1054        // Test Matches with full string
1055        let condition = Condition::new(
1056            "Log.Message".to_string(),
1057            Operator::Matches,
1058            Value::String("Error: Connection timeout".to_string()),
1059        );
1060        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1061    }
1062
1063    #[test]
1064    fn test_evaluate_matches_edge_cases() {
1065        let kb = KnowledgeBase::new("test");
1066        let executor = RuleExecutor::new(kb);
1067
1068        let facts = Facts::new();
1069        facts.set("Empty.String", Value::String("".to_string()));
1070        facts.set("Single.Char", Value::String("x".to_string()));
1071        facts.set("Number.Value", Value::Number(456.0));
1072        facts.set("Special.Chars", Value::String("test@#$%^&*()".to_string()));
1073
1074        // Test Matches with empty pattern (should match empty string)
1075        let condition = Condition::new(
1076            "Empty.String".to_string(),
1077            Operator::Matches,
1078            Value::String("".to_string()),
1079        );
1080        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1081
1082        // Test Matches on single character
1083        let condition = Condition::new(
1084            "Single.Char".to_string(),
1085            Operator::Matches,
1086            Value::String("x".to_string()),
1087        );
1088        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1089
1090        // Test Matches with non-string value (should fail gracefully)
1091        let condition = Condition::new(
1092            "Number.Value".to_string(),
1093            Operator::Matches,
1094            Value::String("456".to_string()),
1095        );
1096        assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
1097
1098        // Test Matches on missing field (should fail gracefully)
1099        let condition = Condition::new(
1100            "Missing.Field".to_string(),
1101            Operator::Matches,
1102            Value::String("pattern".to_string()),
1103        );
1104        assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
1105
1106        // Test Matches with special characters
1107        let condition = Condition::new(
1108            "Special.Chars".to_string(),
1109            Operator::Matches,
1110            Value::String("@#$".to_string()),
1111        );
1112        assert!(executor.evaluate_condition(&condition, &facts).unwrap());
1113
1114        // Test case sensitivity
1115        let facts2 = Facts::new();
1116        facts2.set("Text.Value", Value::String("HelloWorld".to_string()));
1117
1118        let condition = Condition::new(
1119            "Text.Value".to_string(),
1120            Operator::Matches,
1121            Value::String("hello".to_string()),
1122        );
1123        assert!(!executor.evaluate_condition(&condition, &facts2).unwrap()); // Should fail due to case mismatch
1124
1125        let condition = Condition::new(
1126            "Text.Value".to_string(),
1127            Operator::Matches,
1128            Value::String("Hello".to_string()),
1129        );
1130        assert!(executor.evaluate_condition(&condition, &facts2).unwrap()); // Should pass with correct case
1131    }
1132
1133    #[test]
1134    fn test_endswith_matches_in_rules() {
1135        // Integration test: EndsWith and Matches in actual rules
1136        let kb = KnowledgeBase::new("test");
1137
1138        // Rule 1: If email ends with .edu, set IsStudent = true
1139        let condition1 = Condition::new(
1140            "User.Email".to_string(),
1141            Operator::EndsWith,
1142            Value::String(".edu".to_string()),
1143        );
1144        let actions1 = vec![ActionType::Set {
1145            field: "User.IsStudent".to_string(),
1146            value: Value::Boolean(true),
1147        }];
1148        let rule1 = Rule::new(
1149            "StudentEmailRule".to_string(),
1150            ConditionGroup::Single(condition1),
1151            actions1,
1152        );
1153
1154        // Rule 2: If product name matches "Premium", set IsPremium = true
1155        let condition2 = Condition::new(
1156            "Product.Name".to_string(),
1157            Operator::Matches,
1158            Value::String("Premium".to_string()),
1159        );
1160        let actions2 = vec![ActionType::Set {
1161            field: "Product.IsPremium".to_string(),
1162            value: Value::Boolean(true),
1163        }];
1164        let rule2 = Rule::new(
1165            "PremiumProductRule".to_string(),
1166            ConditionGroup::Single(condition2),
1167            actions2,
1168        );
1169
1170        let _ = kb.add_rule(rule1.clone());
1171        let _ = kb.add_rule(rule2.clone());
1172
1173        let executor = RuleExecutor::new(kb);
1174
1175        // Test scenario 1: Student email
1176        let mut facts1 = Facts::new();
1177        facts1.set(
1178            "User.Email",
1179            Value::String("student@university.edu".to_string()),
1180        );
1181
1182        let executed = executor.try_execute_rule(&rule1, &mut facts1).unwrap();
1183        assert!(executed);
1184        assert_eq!(facts1.get("User.IsStudent"), Some(Value::Boolean(true)));
1185
1186        // Test scenario 2: Premium product
1187        let mut facts2 = Facts::new();
1188        facts2.set(
1189            "Product.Name",
1190            Value::String("Premium Laptop X1".to_string()),
1191        );
1192
1193        let executed = executor.try_execute_rule(&rule2, &mut facts2).unwrap();
1194        assert!(executed);
1195        assert_eq!(facts2.get("Product.IsPremium"), Some(Value::Boolean(true)));
1196
1197        // Test scenario 3: Non-matching cases
1198        let mut facts3 = Facts::new();
1199        facts3.set("User.Email", Value::String("user@company.com".to_string()));
1200
1201        let executed = executor.try_execute_rule(&rule1, &mut facts3).unwrap();
1202        assert!(!executed); // Should not execute because email doesn't end with .edu
1203    }
1204}