use crate::engine::rule::{Condition, ConditionGroup, Rule};
use crate::engine::condition_evaluator::ConditionEvaluator;
use crate::types::{ActionType, Value};
use crate::{Facts, KnowledgeBase};
use crate::errors::{Result, RuleEngineError};
pub struct RuleExecutor {
knowledge_base: KnowledgeBase,
evaluator: ConditionEvaluator,
}
impl RuleExecutor {
pub fn new(knowledge_base: KnowledgeBase) -> Self {
Self {
knowledge_base,
evaluator: ConditionEvaluator::with_builtin_functions(),
}
}
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(action, facts)?;
}
Ok(())
}
fn execute_action(&self, action: &ActionType, facts: &mut Facts) -> Result<()> {
match action {
ActionType::Set { field, value } => {
let evaluated_value = self.evaluate_value_expression(value, facts)?;
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(|e| RuleEngineError::ExecutionError(e))?;
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(())
}
}
}
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 mut 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 mut 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(&action, &mut facts).unwrap();
assert_eq!(facts.get("User.IsVIP"), Some(Value::Boolean(true)));
}
}