#![allow(deprecated)]
#![allow(clippy::type_complexity)]
use crate::engine::condition_evaluator::ConditionEvaluator;
use crate::engine::rule::{Condition, ConditionGroup, Rule};
use crate::errors::{Result, RuleEngineError};
use crate::types::{ActionType, Value};
use crate::{Facts, KnowledgeBase};
pub struct RuleExecutor {
evaluator: ConditionEvaluator,
tms_inserter: Option<
std::sync::Arc<dyn Fn(String, crate::rete::TypedFacts, String, Vec<String>) + Send + Sync>,
>,
}
impl RuleExecutor {
pub fn new(_knowledge_base: KnowledgeBase) -> Self {
Self::new_with_inserter(_knowledge_base, None)
}
pub fn new_with_inserter(
_knowledge_base: KnowledgeBase,
inserter: Option<
std::sync::Arc<
dyn Fn(String, crate::rete::TypedFacts, String, Vec<String>) + Send + Sync,
>,
>,
) -> Self {
Self {
evaluator: ConditionEvaluator::with_builtin_functions(),
tms_inserter: inserter,
}
}
pub fn try_execute_rule(&self, rule: &Rule, facts: &mut Facts) -> Result<bool> {
if !self.evaluate_conditions(&rule.conditions, facts)? {
return Ok(false);
}
self.execute_actions(rule, facts)?;
Ok(true)
}
pub fn evaluate_conditions(&self, group: &ConditionGroup, facts: &Facts) -> Result<bool> {
self.evaluator.evaluate_conditions(group, facts)
}
pub fn evaluate_condition(&self, condition: &Condition, facts: &Facts) -> Result<bool> {
self.evaluator.evaluate_condition(condition, facts)
}
fn execute_actions(&self, rule: &Rule, facts: &mut Facts) -> Result<()> {
for action in &rule.actions {
self.execute_action(Some(rule), action, facts)?;
}
Ok(())
}
fn execute_action(
&self,
rule: Option<&Rule>,
action: &ActionType,
facts: &mut Facts,
) -> Result<()> {
match action {
ActionType::Set { field, value } => {
let evaluated_value = self.evaluate_value_expression(value, facts)?;
if let Some(inserter) = &self.tms_inserter {
if let Some(dot_pos) = field.find('.') {
let fact_type = field[..dot_pos].to_string();
let field_name = field[dot_pos + 1..].to_string();
let mut typed = crate::rete::TypedFacts::new();
let fv = match &evaluated_value {
crate::types::Value::String(s) => {
crate::rete::FactValue::String(s.clone())
}
crate::types::Value::Integer(i) => crate::rete::FactValue::Integer(*i),
crate::types::Value::Number(n) => crate::rete::FactValue::Float(*n),
crate::types::Value::Boolean(b) => crate::rete::FactValue::Boolean(*b),
_ => crate::rete::FactValue::String(format!("{:?}", evaluated_value)),
};
typed.set(field_name, fv);
let premises = match rule {
Some(r) => self.collect_premise_keys_from_rule(r, facts),
None => Vec::new(),
};
let source_name = rule
.map(|r| r.name.clone())
.unwrap_or_else(|| "<unknown>".to_string());
(inserter)(fact_type, typed, source_name, premises);
facts.set(field, evaluated_value);
return Ok(());
}
}
facts.set(field, evaluated_value);
Ok(())
}
ActionType::MethodCall {
object,
method,
args,
} => {
if let Some(obj_value) = facts.get(object) {
let mut obj_value = obj_value.clone();
let mut arg_values = Vec::new();
for arg in args {
let val = self.evaluate_value_expression(arg, facts)?;
arg_values.push(val);
}
let result = obj_value
.call_method(method, arg_values)
.map_err(RuleEngineError::ExecutionError)?;
facts.set(object, obj_value);
if result != Value::Null {
facts.set(&format!("{}._return", object), result);
}
Ok(())
} else {
Err(RuleEngineError::ExecutionError(format!(
"Object not found: {}",
object
)))
}
}
ActionType::Retract { object } => {
facts.remove(object);
Ok(())
}
ActionType::Log { message } => {
println!("[BC Action] {}", message);
Ok(())
}
ActionType::Custom { .. } => {
Ok(())
}
ActionType::ActivateAgendaGroup { .. } => {
Ok(())
}
ActionType::ScheduleRule { .. } => {
Ok(())
}
ActionType::CompleteWorkflow { .. } => {
Ok(())
}
ActionType::SetWorkflowData { .. } => {
Ok(())
}
ActionType::Append { field, value } => {
let evaluated_value = self.evaluate_value_expression(value, facts)?;
let current_value = facts.get(field);
let mut array = match current_value {
Some(Value::Array(arr)) => arr.clone(),
Some(_) => {
Vec::new()
}
None => Vec::new(),
};
array.push(evaluated_value);
facts.set(field, Value::Array(array));
Ok(())
}
}
}
fn collect_premise_keys_from_rule(&self, rule: &Rule, facts: &Facts) -> Vec<String> {
use crate::engine::rule::{ConditionExpression, ConditionGroup};
let mut keys = Vec::new();
fn collect_from_group(group: &ConditionGroup, keys: &mut Vec<String>, facts: &Facts) {
match group {
ConditionGroup::Single(cond) => {
if let ConditionExpression::Field(f) = &cond.expression {
if let Some(dot_pos) = f.find('.') {
let fact_type = &f[..dot_pos];
let field_name = &f[dot_pos + 1..];
if let Some(val) = facts.get(f).or_else(|| facts.get_nested(f)) {
let value_str = match val {
crate::types::Value::String(s) => s.clone(),
crate::types::Value::Integer(i) => i.to_string(),
crate::types::Value::Number(n) => n.to_string(),
crate::types::Value::Boolean(b) => b.to_string(),
_ => format!("{:?}", val),
};
keys.push(format!("{}.{}={}", fact_type, field_name, value_str));
} else {
keys.push(format!("{}.{}=", fact_type, field_name));
}
}
}
}
ConditionGroup::Compound { left, right, .. } => {
collect_from_group(left, keys, facts);
collect_from_group(right, keys, facts);
}
_ => {}
}
}
collect_from_group(&rule.conditions, &mut keys, facts);
keys
}
fn evaluate_value_expression(&self, value: &Value, facts: &Facts) -> Result<Value> {
match value {
Value::Expression(expr) => {
if let Some(val) = facts.get(expr).or_else(|| facts.get_nested(expr)) {
return Ok(val);
}
if let Some(result) = self.try_evaluate_arithmetic(expr, facts) {
return Ok(result);
}
if expr == "true" {
Ok(Value::Boolean(true))
} else if expr == "false" {
Ok(Value::Boolean(false))
} else if expr == "null" {
Ok(Value::Null)
} else if let Ok(n) = expr.parse::<f64>() {
Ok(Value::Number(n))
} else if let Ok(i) = expr.parse::<i64>() {
Ok(Value::Integer(i))
} else {
if expr == "true" {
Ok(Value::Boolean(true))
} else if expr == "false" {
Ok(Value::Boolean(false))
} else if expr == "null" {
Ok(Value::Null)
} else if let Ok(n) = expr.parse::<f64>() {
Ok(Value::Number(n))
} else if let Ok(i) = expr.parse::<i64>() {
Ok(Value::Integer(i))
} else {
Ok(value.clone())
}
}
}
_ => Ok(value.clone()),
}
}
fn try_evaluate_arithmetic(&self, expr: &str, facts: &Facts) -> Option<Value> {
if let Some(div_pos) = expr.find(" / ") {
let left = expr[..div_pos].trim();
let right = expr[div_pos + 3..].trim();
let left_val = self.get_numeric_value(left, facts)?;
let right_val = self.get_numeric_value(right, facts)?;
if right_val != 0.0 {
return Some(Value::Number(left_val / right_val));
}
return None;
}
if let Some(mul_pos) = expr.find(" * ") {
let left = expr[..mul_pos].trim();
let right = expr[mul_pos + 3..].trim();
let left_val = self.get_numeric_value(left, facts)?;
let right_val = self.get_numeric_value(right, facts)?;
return Some(Value::Number(left_val * right_val));
}
if let Some(add_pos) = expr.find(" + ") {
let left = expr[..add_pos].trim();
let right = expr[add_pos + 3..].trim();
let left_val = self.get_numeric_value(left, facts)?;
let right_val = self.get_numeric_value(right, facts)?;
return Some(Value::Number(left_val + right_val));
}
if let Some(sub_pos) = expr.find(" - ") {
let left = expr[..sub_pos].trim();
let right = expr[sub_pos + 3..].trim();
let left_val = self.get_numeric_value(left, facts)?;
let right_val = self.get_numeric_value(right, facts)?;
return Some(Value::Number(left_val - right_val));
}
None
}
fn get_numeric_value(&self, s: &str, facts: &Facts) -> Option<f64> {
if let Ok(n) = s.parse::<f64>() {
return Some(n);
}
if let Some(val) = facts.get(s).or_else(|| facts.get_nested(s)) {
match val {
Value::Number(n) => Some(n),
Value::Integer(i) => Some(i as f64),
_ => None,
}
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Operator;
#[test]
fn test_evaluate_simple_condition() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("User.Age", Value::Number(25.0));
let condition = Condition::new(
"User.Age".to_string(),
Operator::GreaterThan,
Value::Number(18.0),
);
let result = executor.evaluate_condition(&condition, &facts).unwrap();
assert!(result);
}
#[test]
fn test_evaluate_function_call_len() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("User.Name", Value::String("John".to_string()));
let condition = Condition::with_function(
"len".to_string(),
vec!["User.Name".to_string()],
Operator::GreaterThan,
Value::Number(3.0),
);
let result = executor.evaluate_condition(&condition, &facts).unwrap();
assert!(result); }
#[test]
fn test_execute_set_action() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let mut facts = Facts::new();
let action = ActionType::Set {
field: "User.IsVIP".to_string(),
value: Value::Boolean(true),
};
executor.execute_action(None, &action, &mut facts).unwrap();
assert_eq!(facts.get("User.IsVIP"), Some(Value::Boolean(true)));
}
#[test]
fn test_evaluate_compound_and_condition() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("User.Age", Value::Number(25.0));
facts.set("User.Country", Value::String("US".to_string()));
let conditions = ConditionGroup::Compound {
left: Box::new(ConditionGroup::Single(Condition::new(
"User.Age".to_string(),
Operator::GreaterThan,
Value::Number(18.0),
))),
operator: crate::types::LogicalOperator::And,
right: Box::new(ConditionGroup::Single(Condition::new(
"User.Country".to_string(),
Operator::Equal,
Value::String("US".to_string()),
))),
};
let result = executor.evaluate_conditions(&conditions, &facts).unwrap();
assert!(result);
}
#[test]
fn test_evaluate_compound_or_condition() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("User.Age", Value::Number(15.0));
facts.set("User.HasParentalConsent", Value::Boolean(true));
let conditions = ConditionGroup::Compound {
left: Box::new(ConditionGroup::Single(Condition::new(
"User.Age".to_string(),
Operator::GreaterThan,
Value::Number(18.0),
))),
operator: crate::types::LogicalOperator::Or,
right: Box::new(ConditionGroup::Single(Condition::new(
"User.HasParentalConsent".to_string(),
Operator::Equal,
Value::Boolean(true),
))),
};
let result = executor.evaluate_conditions(&conditions, &facts).unwrap();
assert!(result); }
#[test]
fn test_evaluate_not_condition() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("User.IsBanned", Value::Boolean(false));
let conditions = ConditionGroup::Not(Box::new(ConditionGroup::Single(Condition::new(
"User.IsBanned".to_string(),
Operator::Equal,
Value::Boolean(true),
))));
let result = executor.evaluate_conditions(&conditions, &facts).unwrap();
assert!(result); }
#[test]
fn test_evaluate_function_isempty() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("User.Description", Value::String("".to_string()));
let condition = Condition::with_function(
"isEmpty".to_string(),
vec!["User.Description".to_string()],
Operator::Equal,
Value::Boolean(true),
);
let result = executor.evaluate_condition(&condition, &facts).unwrap();
assert!(result); }
#[test]
fn test_evaluate_test_expression_exists() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("User.Email", Value::String("user@example.com".to_string()));
let condition = Condition {
field: "User.Email".to_string(),
expression: crate::engine::rule::ConditionExpression::Test {
name: "exists".to_string(),
args: vec!["User.Email".to_string()],
},
operator: Operator::Equal,
value: Value::Boolean(true),
};
let result = executor.evaluate_condition(&condition, &facts).unwrap();
assert!(result);
}
#[test]
fn test_execute_log_action() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let mut facts = Facts::new();
let action = ActionType::Log {
message: "Test log message".to_string(),
};
executor.execute_action(None, &action, &mut facts).unwrap();
}
#[test]
fn test_try_execute_rule_success() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let mut facts = Facts::new();
facts.set("User.Age", Value::Number(25.0));
let conditions = ConditionGroup::Single(Condition::new(
"User.Age".to_string(),
Operator::GreaterThan,
Value::Number(18.0),
));
let actions = vec![ActionType::Set {
field: "User.IsAdult".to_string(),
value: Value::Boolean(true),
}];
let rule = Rule::new("CheckAdult".to_string(), conditions, actions);
let executed = executor.try_execute_rule(&rule, &mut facts).unwrap();
assert!(executed);
assert_eq!(facts.get("User.IsAdult"), Some(Value::Boolean(true)));
}
#[test]
fn test_try_execute_rule_failure() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let mut facts = Facts::new();
facts.set("User.Age", Value::Number(15.0));
let conditions = ConditionGroup::Single(Condition::new(
"User.Age".to_string(),
Operator::GreaterThan,
Value::Number(18.0),
));
let actions = vec![ActionType::Set {
field: "User.IsAdult".to_string(),
value: Value::Boolean(true),
}];
let rule = Rule::new("CheckAdult".to_string(), conditions, actions);
let executed = executor.try_execute_rule(&rule, &mut facts).unwrap();
assert!(!executed); assert_eq!(facts.get("User.IsAdult"), None); }
#[test]
fn test_evaluate_string_operators() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("User.Email", Value::String("user@example.com".to_string()));
let condition = Condition::new(
"User.Email".to_string(),
Operator::Contains,
Value::String("@example".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"User.Email".to_string(),
Operator::StartsWith,
Value::String("user".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"User.Email".to_string(),
Operator::EndsWith,
Value::String(".com".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
}
#[test]
fn test_evaluate_numeric_operators() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("Order.Amount", Value::Number(1500.0));
let condition = Condition::new(
"Order.Amount".to_string(),
Operator::GreaterThanOrEqual,
Value::Number(1500.0),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Order.Amount".to_string(),
Operator::LessThan,
Value::Number(2000.0),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Order.Amount".to_string(),
Operator::NotEqual,
Value::Number(1000.0),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
}
#[test]
fn test_evaluate_missing_field() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
let condition = Condition::new(
"User.Age".to_string(),
Operator::GreaterThan,
Value::Number(18.0),
);
let result = executor.evaluate_condition(&condition, &facts).unwrap();
assert!(!result); }
#[test]
fn test_execute_multiple_actions() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let mut facts = Facts::new();
facts.set("User.Points", Value::Number(150.0));
let conditions = ConditionGroup::Single(Condition::new(
"User.Points".to_string(),
Operator::GreaterThan,
Value::Number(100.0),
));
let actions = vec![
ActionType::Set {
field: "User.IsVIP".to_string(),
value: Value::Boolean(true),
},
ActionType::Log {
message: "User promoted to VIP".to_string(),
},
ActionType::Set {
field: "User.Discount".to_string(),
value: Value::Number(0.2),
},
];
let rule = Rule::new("PromoteToVIP".to_string(), conditions, actions);
let executed = executor.try_execute_rule(&rule, &mut facts).unwrap();
assert!(executed);
assert_eq!(facts.get("User.IsVIP"), Some(Value::Boolean(true)));
assert_eq!(facts.get("User.Discount"), Some(Value::Number(0.2)));
}
#[test]
fn test_evaluate_endswith_operator() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("User.Email", Value::String("user@example.com".to_string()));
facts.set("File.Name", Value::String("document.pdf".to_string()));
facts.set(
"Domain.URL",
Value::String("https://api.example.org".to_string()),
);
let condition = Condition::new(
"User.Email".to_string(),
Operator::EndsWith,
Value::String(".com".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"File.Name".to_string(),
Operator::EndsWith,
Value::String(".pdf".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Domain.URL".to_string(),
Operator::EndsWith,
Value::String(".org".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"User.Email".to_string(),
Operator::EndsWith,
Value::String(".net".to_string()),
);
assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"File.Name".to_string(),
Operator::EndsWith,
Value::String("document.pdf".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
}
#[test]
fn test_evaluate_endswith_edge_cases() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("Empty.String", Value::String("".to_string()));
facts.set("Single.Char", Value::String("a".to_string()));
facts.set("Number.Value", Value::Number(123.0));
let condition = Condition::new(
"Empty.String".to_string(),
Operator::EndsWith,
Value::String("".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Single.Char".to_string(),
Operator::EndsWith,
Value::String("a".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Number.Value".to_string(),
Operator::EndsWith,
Value::String(".0".to_string()),
);
assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Missing.Field".to_string(),
Operator::EndsWith,
Value::String("test".to_string()),
);
assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
let facts2 = Facts::new();
facts2.set("Text.Value", Value::String("HelloWorld".to_string()));
let condition = Condition::new(
"Text.Value".to_string(),
Operator::EndsWith,
Value::String("world".to_string()),
);
assert!(!executor.evaluate_condition(&condition, &facts2).unwrap());
let condition = Condition::new(
"Text.Value".to_string(),
Operator::EndsWith,
Value::String("World".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts2).unwrap()); }
#[test]
fn test_evaluate_matches_operator() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("User.Email", Value::String("user@example.com".to_string()));
facts.set(
"Product.Name",
Value::String("Premium Laptop Model X".to_string()),
);
facts.set(
"Log.Message",
Value::String("Error: Connection timeout".to_string()),
);
let condition = Condition::new(
"User.Email".to_string(),
Operator::Matches,
Value::String("example".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Product.Name".to_string(),
Operator::Matches,
Value::String("Premium".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Log.Message".to_string(),
Operator::Matches,
Value::String("Error".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"User.Email".to_string(),
Operator::Matches,
Value::String("notfound".to_string()),
);
assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Product.Name".to_string(),
Operator::Matches,
Value::String("Laptop".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Log.Message".to_string(),
Operator::Matches,
Value::String("Error: Connection timeout".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
}
#[test]
fn test_evaluate_matches_edge_cases() {
let kb = KnowledgeBase::new("test");
let executor = RuleExecutor::new(kb);
let facts = Facts::new();
facts.set("Empty.String", Value::String("".to_string()));
facts.set("Single.Char", Value::String("x".to_string()));
facts.set("Number.Value", Value::Number(456.0));
facts.set("Special.Chars", Value::String("test@#$%^&*()".to_string()));
let condition = Condition::new(
"Empty.String".to_string(),
Operator::Matches,
Value::String("".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Single.Char".to_string(),
Operator::Matches,
Value::String("x".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Number.Value".to_string(),
Operator::Matches,
Value::String("456".to_string()),
);
assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Missing.Field".to_string(),
Operator::Matches,
Value::String("pattern".to_string()),
);
assert!(!executor.evaluate_condition(&condition, &facts).unwrap());
let condition = Condition::new(
"Special.Chars".to_string(),
Operator::Matches,
Value::String("@#$".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts).unwrap());
let facts2 = Facts::new();
facts2.set("Text.Value", Value::String("HelloWorld".to_string()));
let condition = Condition::new(
"Text.Value".to_string(),
Operator::Matches,
Value::String("hello".to_string()),
);
assert!(!executor.evaluate_condition(&condition, &facts2).unwrap());
let condition = Condition::new(
"Text.Value".to_string(),
Operator::Matches,
Value::String("Hello".to_string()),
);
assert!(executor.evaluate_condition(&condition, &facts2).unwrap()); }
#[test]
fn test_endswith_matches_in_rules() {
let kb = KnowledgeBase::new("test");
let condition1 = Condition::new(
"User.Email".to_string(),
Operator::EndsWith,
Value::String(".edu".to_string()),
);
let actions1 = vec![ActionType::Set {
field: "User.IsStudent".to_string(),
value: Value::Boolean(true),
}];
let rule1 = Rule::new(
"StudentEmailRule".to_string(),
ConditionGroup::Single(condition1),
actions1,
);
let condition2 = Condition::new(
"Product.Name".to_string(),
Operator::Matches,
Value::String("Premium".to_string()),
);
let actions2 = vec![ActionType::Set {
field: "Product.IsPremium".to_string(),
value: Value::Boolean(true),
}];
let rule2 = Rule::new(
"PremiumProductRule".to_string(),
ConditionGroup::Single(condition2),
actions2,
);
let _ = kb.add_rule(rule1.clone());
let _ = kb.add_rule(rule2.clone());
let executor = RuleExecutor::new(kb);
let mut facts1 = Facts::new();
facts1.set(
"User.Email",
Value::String("student@university.edu".to_string()),
);
let executed = executor.try_execute_rule(&rule1, &mut facts1).unwrap();
assert!(executed);
assert_eq!(facts1.get("User.IsStudent"), Some(Value::Boolean(true)));
let mut facts2 = Facts::new();
facts2.set(
"Product.Name",
Value::String("Premium Laptop X1".to_string()),
);
let executed = executor.try_execute_rule(&rule2, &mut facts2).unwrap();
assert!(executed);
assert_eq!(facts2.get("Product.IsPremium"), Some(Value::Boolean(true)));
let mut facts3 = Facts::new();
facts3.set("User.Email", Value::String("user@company.com".to_string()));
let executed = executor.try_execute_rule(&rule1, &mut facts3).unwrap();
assert!(!executed); }
}