use crate::engine::{
agenda::{ActivationGroupManager, AgendaManager},
analytics::RuleAnalytics,
facts::Facts,
knowledge_base::KnowledgeBase,
plugin::{PluginConfig, PluginInfo, PluginManager, PluginStats},
workflow::WorkflowEngine,
};
use crate::errors::{Result, RuleEngineError};
use crate::types::{ActionType, Operator, Value};
use chrono::{DateTime, Utc};
use std::collections::HashMap;
use std::time::{Duration, Instant};
pub type CustomFunction = Box<dyn Fn(&[Value], &Facts) -> Result<Value> + Send + Sync>;
pub type ActionHandler = Box<dyn Fn(&HashMap<String, Value>, &Facts) -> Result<()> + Send + Sync>;
#[derive(Debug, Clone)]
pub struct EngineConfig {
pub max_cycles: usize,
pub timeout: Option<Duration>,
pub enable_stats: bool,
pub debug_mode: bool,
}
impl Default for EngineConfig {
fn default() -> Self {
Self {
max_cycles: 100,
timeout: Some(Duration::from_secs(30)),
enable_stats: true,
debug_mode: false,
}
}
}
#[derive(Debug, Clone)]
pub struct GruleExecutionResult {
pub cycle_count: usize,
pub rules_evaluated: usize,
pub rules_fired: usize,
pub execution_time: Duration,
}
pub struct RustRuleEngine {
knowledge_base: KnowledgeBase,
config: EngineConfig,
custom_functions: HashMap<String, CustomFunction>,
action_handlers: HashMap<String, ActionHandler>,
analytics: Option<RuleAnalytics>,
agenda_manager: AgendaManager,
activation_group_manager: ActivationGroupManager,
fired_rules_global: std::collections::HashSet<String>,
workflow_engine: WorkflowEngine,
plugin_manager: PluginManager,
}
impl RustRuleEngine {
pub fn execute_with_callback<F>(&mut self, facts: &Facts, mut on_rule_fired: F) -> Result<GruleExecutionResult>
where
F: FnMut(&str, &str),
{
use chrono::Utc;
let timestamp = Utc::now();
let start_time = std::time::Instant::now();
let mut cycle_count = 0;
let mut rules_evaluated = 0;
let mut rules_fired = 0;
self.sync_workflow_agenda_activations();
for cycle in 0..self.config.max_cycles {
cycle_count = cycle + 1;
let mut any_rule_fired = false;
let mut fired_rules_in_cycle = std::collections::HashSet::new();
self.activation_group_manager.reset_cycle();
if let Some(timeout) = self.config.timeout {
if start_time.elapsed() > timeout {
return Err(crate::errors::RuleEngineError::EvaluationError {
message: "Execution timeout exceeded".to_string(),
});
}
}
let mut rules = self.knowledge_base.get_rules().clone();
rules.sort_by(|a, b| b.salience.cmp(&a.salience));
let rules: Vec<_> = rules
.iter()
.filter(|rule| self.agenda_manager.should_evaluate_rule(rule))
.collect();
for rule in &rules {
if !rule.enabled {
continue;
}
if !rule.is_active_at(timestamp) {
continue;
}
if !self.agenda_manager.can_fire_rule(rule) {
continue;
}
if !self.activation_group_manager.can_fire(rule) {
continue;
}
if rule.no_loop && self.fired_rules_global.contains(&rule.name) {
continue;
}
rules_evaluated += 1;
let condition_result = self.evaluate_conditions(&rule.conditions, facts)?;
if condition_result {
for action in &rule.actions {
self.execute_action(action, facts)?;
}
rules_fired += 1;
any_rule_fired = true;
fired_rules_in_cycle.insert(rule.name.clone());
if rule.no_loop {
self.fired_rules_global.insert(rule.name.clone());
}
self.agenda_manager.mark_rule_fired(rule);
self.activation_group_manager.mark_fired(rule);
on_rule_fired(&rule.name, "facts"); }
}
if !any_rule_fired {
break;
}
self.sync_workflow_agenda_activations();
}
let execution_time = start_time.elapsed();
Ok(crate::engine::GruleExecutionResult {
cycle_count,
rules_evaluated,
rules_fired,
execution_time,
})
}
pub fn new(knowledge_base: KnowledgeBase) -> Self {
Self {
knowledge_base,
config: EngineConfig::default(),
custom_functions: HashMap::new(),
action_handlers: HashMap::new(),
analytics: None,
agenda_manager: AgendaManager::new(),
activation_group_manager: ActivationGroupManager::new(),
fired_rules_global: std::collections::HashSet::new(),
workflow_engine: WorkflowEngine::new(),
plugin_manager: PluginManager::with_default_config(),
}
}
pub fn with_config(knowledge_base: KnowledgeBase, config: EngineConfig) -> Self {
Self {
knowledge_base,
config,
custom_functions: HashMap::new(),
action_handlers: HashMap::new(),
analytics: None,
agenda_manager: AgendaManager::new(),
activation_group_manager: ActivationGroupManager::new(),
fired_rules_global: std::collections::HashSet::new(),
workflow_engine: WorkflowEngine::new(),
plugin_manager: PluginManager::with_default_config(),
}
}
pub fn register_function<F>(&mut self, name: &str, func: F)
where
F: Fn(&[Value], &Facts) -> Result<Value> + Send + Sync + 'static,
{
self.custom_functions
.insert(name.to_string(), Box::new(func));
}
pub fn register_action_handler<F>(&mut self, action_type: &str, handler: F)
where
F: Fn(&HashMap<String, Value>, &Facts) -> Result<()> + Send + Sync + 'static,
{
self.action_handlers
.insert(action_type.to_string(), Box::new(handler));
}
pub fn enable_analytics(&mut self, analytics: RuleAnalytics) {
self.analytics = Some(analytics);
}
pub fn reset_no_loop_tracking(&mut self) {
self.fired_rules_global.clear();
}
pub fn disable_analytics(&mut self) {
self.analytics = None;
}
pub fn analytics(&self) -> Option<&RuleAnalytics> {
self.analytics.as_ref()
}
pub fn set_debug_mode(&mut self, enabled: bool) {
self.config.debug_mode = enabled;
}
pub fn has_function(&self, name: &str) -> bool {
self.custom_functions.contains_key(name)
}
pub fn has_action_handler(&self, action_type: &str) -> bool {
self.action_handlers.contains_key(action_type)
}
pub fn get_ready_tasks(&mut self) -> Vec<crate::engine::workflow::ScheduledTask> {
self.workflow_engine.get_ready_tasks()
}
pub fn execute_scheduled_tasks(&mut self, facts: &Facts) -> Result<()> {
let ready_tasks = self.get_ready_tasks();
for task in ready_tasks {
if let Some(rule) = self
.knowledge_base
.get_rules()
.iter()
.find(|r| r.name == task.rule_name)
{
if self.config.debug_mode {
println!("⚡ Executing scheduled task: {}", task.rule_name);
}
if self.evaluate_conditions(&rule.conditions, facts)? {
for action in &rule.actions {
self.execute_action(action, facts)?;
}
}
}
}
Ok(())
}
pub fn activate_agenda_group(&mut self, group: String) {
self.workflow_engine.activate_agenda_group(group.clone());
self.agenda_manager.set_focus(&group);
}
pub fn knowledge_base(&self) -> &KnowledgeBase {
&self.knowledge_base
}
pub fn knowledge_base_mut(&mut self) -> &mut KnowledgeBase {
&mut self.knowledge_base
}
fn sync_workflow_agenda_activations(&mut self) {
while let Some(agenda_group) = self.workflow_engine.get_next_pending_agenda_activation() {
if self.config.debug_mode {
println!("🔄 Syncing workflow agenda activation: {}", agenda_group);
}
self.agenda_manager.set_focus(&agenda_group);
}
}
pub fn set_agenda_focus(&mut self, group: &str) {
self.agenda_manager.set_focus(group);
}
pub fn get_active_agenda_group(&self) -> &str {
self.agenda_manager.get_active_group()
}
pub fn pop_agenda_focus(&mut self) -> Option<String> {
self.agenda_manager.pop_focus()
}
pub fn clear_agenda_focus(&mut self) {
self.agenda_manager.clear_focus();
}
pub fn get_agenda_groups(&self) -> Vec<String> {
self.agenda_manager
.get_agenda_groups(&self.knowledge_base.get_rules())
}
pub fn get_activation_groups(&self) -> Vec<String> {
self.activation_group_manager
.get_activation_groups(&self.knowledge_base.get_rules())
}
pub fn start_workflow(&mut self, workflow_name: Option<String>) -> String {
self.workflow_engine.start_workflow(workflow_name)
}
pub fn get_workflow_stats(&self) -> crate::engine::workflow::WorkflowStats {
self.workflow_engine.get_workflow_stats()
}
pub fn get_workflow(
&self,
workflow_id: &str,
) -> Option<&crate::engine::workflow::WorkflowState> {
self.workflow_engine.get_workflow(workflow_id)
}
pub fn cleanup_completed_workflows(&mut self, older_than: Duration) {
self.workflow_engine.cleanup_completed_workflows(older_than);
}
pub fn execute_workflow_step(
&mut self,
agenda_group: &str,
facts: &Facts,
) -> Result<GruleExecutionResult> {
self.set_agenda_focus(agenda_group);
let result = self.execute(facts)?;
self.process_workflow_actions(facts)?;
Ok(result)
}
pub fn execute_workflow(
&mut self,
agenda_groups: Vec<&str>,
facts: &Facts,
) -> Result<crate::engine::workflow::WorkflowResult> {
let start_time = Instant::now();
let mut total_steps = 0;
if self.config.debug_mode {
println!(
"🔄 Starting workflow execution with {} steps",
agenda_groups.len()
);
}
for (i, group) in agenda_groups.iter().enumerate() {
if self.config.debug_mode {
println!("📋 Executing workflow step {}: {}", i + 1, group);
}
let step_result = self.execute_workflow_step(group, facts)?;
total_steps += 1;
if step_result.rules_fired == 0 {
if self.config.debug_mode {
println!("⏸️ No rules fired in step '{}', stopping workflow", group);
}
break;
}
}
let execution_time = start_time.elapsed();
Ok(crate::engine::workflow::WorkflowResult::success(
total_steps,
execution_time,
))
}
fn process_workflow_actions(&mut self, facts: &Facts) -> Result<()> {
while let Some(group) = self.workflow_engine.get_next_agenda_group() {
self.set_agenda_focus(&group);
}
let ready_tasks = self.workflow_engine.get_ready_tasks();
for task in ready_tasks {
if self.config.debug_mode {
println!("⚡ Executing scheduled task: {}", task.rule_name);
}
if let Some(rule) = self
.knowledge_base
.get_rules()
.iter()
.find(|r| r.name == task.rule_name)
{
if self.evaluate_conditions(&rule.conditions, facts)? {
for action in &rule.actions {
self.execute_action(action, facts)?;
}
}
}
}
Ok(())
}
pub fn execute(&mut self, facts: &Facts) -> Result<GruleExecutionResult> {
self.execute_at_time(facts, Utc::now())
}
pub fn execute_at_time(
&mut self,
facts: &Facts,
timestamp: DateTime<Utc>,
) -> Result<GruleExecutionResult> {
let start_time = Instant::now();
let mut cycle_count = 0;
let mut rules_evaluated = 0;
let mut rules_fired = 0;
self.sync_workflow_agenda_activations();
if self.config.debug_mode {
println!(
"🚀 Starting rule execution with {} rules (agenda group: {})",
self.knowledge_base.get_rules().len(),
self.agenda_manager.get_active_group()
);
}
for cycle in 0..self.config.max_cycles {
cycle_count = cycle + 1;
let mut any_rule_fired = false;
let mut fired_rules_in_cycle = std::collections::HashSet::new();
self.activation_group_manager.reset_cycle();
if let Some(timeout) = self.config.timeout {
if start_time.elapsed() > timeout {
return Err(RuleEngineError::EvaluationError {
message: "Execution timeout exceeded".to_string(),
});
}
}
let mut rules = self.knowledge_base.get_rules().clone();
rules.sort_by(|a, b| b.salience.cmp(&a.salience));
let rules: Vec<_> = rules
.iter()
.filter(|rule| self.agenda_manager.should_evaluate_rule(rule))
.collect();
for rule in &rules {
if !rule.enabled {
continue;
}
if !rule.is_active_at(timestamp) {
continue;
}
if !self.agenda_manager.can_fire_rule(rule) {
continue;
}
if !self.activation_group_manager.can_fire(rule) {
continue;
}
if rule.no_loop && self.fired_rules_global.contains(&rule.name) {
if self.config.debug_mode {
println!("⛔ Skipping '{}' due to no_loop (already fired)", rule.name);
}
continue;
}
rules_evaluated += 1;
let rule_start = Instant::now();
if self.config.debug_mode {
println!("📝 Evaluating rule: {} (no_loop={})", rule.name, rule.no_loop);
}
let condition_result = self.evaluate_conditions(&rule.conditions, facts)?;
if self.config.debug_mode {
println!(
" 🔍 Condition result for '{}': {}",
rule.name, condition_result
);
}
if condition_result {
if self.config.debug_mode {
println!(
"🔥 Rule '{}' fired (salience: {})",
rule.name, rule.salience
);
}
for action in &rule.actions {
self.execute_action(action, facts)?;
}
let rule_duration = rule_start.elapsed();
if let Some(analytics) = &mut self.analytics {
analytics.record_execution(&rule.name, rule_duration, true, true, None, 0);
}
rules_fired += 1;
any_rule_fired = true;
fired_rules_in_cycle.insert(rule.name.clone());
if rule.no_loop {
self.fired_rules_global.insert(rule.name.clone());
if self.config.debug_mode {
println!(" 🔒 Marked '{}' as fired (no_loop tracking)", rule.name);
}
}
self.agenda_manager.mark_rule_fired(rule);
self.activation_group_manager.mark_fired(rule);
} else {
let rule_duration = rule_start.elapsed();
if let Some(analytics) = &mut self.analytics {
analytics.record_execution(
&rule.name,
rule_duration,
false,
false,
None,
0,
);
}
}
}
if !any_rule_fired {
break;
}
self.sync_workflow_agenda_activations();
}
let execution_time = start_time.elapsed();
Ok(GruleExecutionResult {
cycle_count,
rules_evaluated,
rules_fired,
execution_time,
})
}
fn evaluate_conditions(
&self,
conditions: &crate::engine::rule::ConditionGroup,
facts: &Facts,
) -> Result<bool> {
use crate::engine::pattern_matcher::PatternMatcher;
use crate::engine::rule::ConditionGroup;
match conditions {
ConditionGroup::Single(condition) => self.evaluate_single_condition(condition, facts),
ConditionGroup::Compound {
left,
operator,
right,
} => {
let left_result = self.evaluate_conditions(left, facts)?;
let right_result = self.evaluate_conditions(right, facts)?;
match operator {
crate::types::LogicalOperator::And => Ok(left_result && right_result),
crate::types::LogicalOperator::Or => Ok(left_result || right_result),
crate::types::LogicalOperator::Not => Err(RuleEngineError::EvaluationError {
message: "NOT operator should not appear in compound conditions"
.to_string(),
}),
}
}
ConditionGroup::Not(condition) => {
let result = self.evaluate_conditions(condition, facts)?;
Ok(!result)
}
ConditionGroup::Exists(condition) => {
Ok(PatternMatcher::evaluate_exists(condition, facts))
}
ConditionGroup::Forall(condition) => {
Ok(PatternMatcher::evaluate_forall(condition, facts))
}
ConditionGroup::Accumulate {
result_var,
source_pattern,
extract_field,
source_conditions,
function,
function_arg,
} => {
self.evaluate_accumulate(
result_var,
source_pattern,
extract_field,
source_conditions,
function,
function_arg,
facts,
)?;
Ok(true)
}
}
}
fn evaluate_accumulate(
&self,
result_var: &str,
source_pattern: &str,
extract_field: &str,
source_conditions: &[String],
function: &str,
function_arg: &str,
facts: &Facts,
) -> Result<()> {
use crate::rete::accumulate::*;
let all_facts = facts.get_all_facts();
let mut matching_values = Vec::new();
let pattern_prefix = format!("{}.", source_pattern);
let mut instances: HashMap<String, HashMap<String, Value>> = HashMap::new();
for (key, value) in &all_facts {
if key.starts_with(&pattern_prefix) {
let parts: Vec<&str> = key.strip_prefix(&pattern_prefix).unwrap().split('.').collect();
if parts.len() >= 2 {
let instance_id = parts[0];
let field_name = parts[1..].join(".");
instances
.entry(instance_id.to_string())
.or_insert_with(HashMap::new)
.insert(field_name, value.clone());
} else if parts.len() == 1 {
instances
.entry("default".to_string())
.or_insert_with(HashMap::new)
.insert(parts[0].to_string(), value.clone());
}
}
}
for (_instance_id, instance_facts) in instances {
let mut matches = true;
for condition_str in source_conditions {
if !self.evaluate_condition_string(condition_str, &instance_facts) {
matches = false;
break;
}
}
if matches {
if let Some(value) = instance_facts.get(extract_field) {
matching_values.push(value.clone());
}
}
}
let result = match function {
"sum" => {
let mut state = SumFunction.init();
for value in &matching_values {
state.accumulate(&self.value_to_fact_value(value));
}
self.fact_value_to_value(&state.get_result())
}
"count" => {
let mut state = CountFunction.init();
for value in &matching_values {
state.accumulate(&self.value_to_fact_value(value));
}
self.fact_value_to_value(&state.get_result())
}
"average" | "avg" => {
let mut state = AverageFunction.init();
for value in &matching_values {
state.accumulate(&self.value_to_fact_value(value));
}
self.fact_value_to_value(&state.get_result())
}
"min" => {
let mut state = MinFunction.init();
for value in &matching_values {
state.accumulate(&self.value_to_fact_value(value));
}
self.fact_value_to_value(&state.get_result())
}
"max" => {
let mut state = MaxFunction.init();
for value in &matching_values {
state.accumulate(&self.value_to_fact_value(value));
}
self.fact_value_to_value(&state.get_result())
}
_ => {
return Err(RuleEngineError::EvaluationError {
message: format!("Unknown accumulate function: {}", function),
});
}
};
let result_key = format!("{}.{}", source_pattern, function);
facts.set(&result_key, result);
if self.config.debug_mode {
println!(" 🧮 Accumulate result: {} = {:?}", result_key, facts.get(&result_key));
}
Ok(())
}
fn value_to_fact_value(&self, value: &Value) -> crate::rete::facts::FactValue {
use crate::rete::facts::FactValue;
match value {
Value::Integer(i) => FactValue::Integer(*i),
Value::Number(n) => FactValue::Float(*n),
Value::String(s) => FactValue::String(s.clone()),
Value::Boolean(b) => FactValue::Boolean(*b),
_ => FactValue::String(value.to_string()),
}
}
fn fact_value_to_value(&self, fact_value: &crate::rete::facts::FactValue) -> Value {
use crate::rete::facts::FactValue;
match fact_value {
FactValue::Integer(i) => Value::Integer(*i),
FactValue::Float(f) => Value::Number(*f),
FactValue::String(s) => Value::String(s.clone()),
FactValue::Boolean(b) => Value::Boolean(*b),
FactValue::Array(_) => Value::String(format!("{:?}", fact_value)),
FactValue::Null => Value::String("null".to_string()),
}
}
fn evaluate_condition_string(&self, condition: &str, facts: &HashMap<String, Value>) -> bool {
let condition = condition.trim();
let operators = ["==", "!=", ">=", "<=", ">", "<"];
for op in &operators {
if let Some(pos) = condition.find(op) {
let field = condition[..pos].trim();
let value_str = condition[pos + op.len()..].trim()
.trim_matches('"')
.trim_matches('\'');
if let Some(field_value) = facts.get(field) {
return self.compare_values(field_value, op, value_str);
} else {
return false;
}
}
}
false
}
fn compare_values(&self, field_value: &Value, operator: &str, value_str: &str) -> bool {
match field_value {
Value::String(s) => {
match operator {
"==" => s == value_str,
"!=" => s != value_str,
_ => false,
}
}
Value::Integer(i) => {
if let Ok(num) = value_str.parse::<i64>() {
match operator {
"==" => *i == num,
"!=" => *i != num,
">" => *i > num,
"<" => *i < num,
">=" => *i >= num,
"<=" => *i <= num,
_ => false,
}
} else {
false
}
}
Value::Number(n) => {
if let Ok(num) = value_str.parse::<f64>() {
match operator {
"==" => (*n - num).abs() < f64::EPSILON,
"!=" => (*n - num).abs() >= f64::EPSILON,
">" => *n > num,
"<" => *n < num,
">=" => *n >= num,
"<=" => *n <= num,
_ => false,
}
} else {
false
}
}
Value::Boolean(b) => {
if let Ok(bool_val) = value_str.parse::<bool>() {
match operator {
"==" => *b == bool_val,
"!=" => *b != bool_val,
_ => false,
}
} else {
false
}
}
_ => false,
}
}
fn evaluate_rule_conditions(
&self,
rule: &crate::engine::rule::Rule,
facts: &Facts,
) -> Result<bool> {
self.evaluate_conditions(&rule.conditions, facts)
}
fn is_retracted(&self, object_name: &str, facts: &Facts) -> bool {
let retract_key = format!("_retracted_{}", object_name);
matches!(facts.get(&retract_key), Some(Value::Boolean(true)))
}
fn evaluate_single_condition(
&self,
condition: &crate::engine::rule::Condition,
facts: &Facts,
) -> Result<bool> {
use crate::engine::rule::ConditionExpression;
let result = match &condition.expression {
ConditionExpression::Field(field_name) => {
if let Some(object_name) = field_name.split('.').next() {
if self.is_retracted(object_name, facts) {
if self.config.debug_mode {
println!(" 🗑️ Skipping retracted fact: {}", object_name);
}
return Ok(false);
}
}
let field_value = facts
.get_nested(field_name)
.or_else(|| facts.get(field_name));
if self.config.debug_mode {
println!(
" 🔎 Evaluating field condition: {} {} {:?}",
field_name,
format!("{:?}", condition.operator).to_lowercase(),
condition.value
);
println!(" Field value: {:?}", field_value);
}
if let Some(value) = field_value {
let rhs = match &condition.value {
crate::types::Value::String(s) => {
facts
.get_nested(s)
.or_else(|| facts.get(s))
.unwrap_or(crate::types::Value::String(s.clone()))
}
crate::types::Value::Expression(expr) => {
match crate::expression::evaluate_expression(expr, facts) {
Ok(evaluated) => evaluated,
Err(_) => {
facts
.get_nested(expr)
.or_else(|| facts.get(expr))
.unwrap_or(crate::types::Value::Expression(expr.clone()))
}
}
}
_ => condition.value.clone(),
};
if self.config.debug_mode {
println!(" Resolved RHS for comparison: {:?}", rhs);
}
condition.operator.evaluate(&value, &rhs)
} else {
false
}
}
ConditionExpression::FunctionCall { name, args } => {
if self.config.debug_mode {
println!(
" 🔎 Evaluating function condition: {}({:?}) {} {:?}",
name,
args,
format!("{:?}", condition.operator).to_lowercase(),
condition.value
);
}
if let Some(function) = self.custom_functions.get(name) {
let arg_values: Vec<Value> = args
.iter()
.map(|arg| {
facts
.get_nested(arg)
.or_else(|| facts.get(arg))
.unwrap_or(Value::String(arg.clone()))
})
.collect();
match function(&arg_values, facts) {
Ok(result_value) => {
if self.config.debug_mode {
println!(" Function result: {:?}", result_value);
}
condition.operator.evaluate(&result_value, &condition.value)
}
Err(e) => {
if self.config.debug_mode {
println!(" Function error: {}", e);
}
false
}
}
} else {
if self.config.debug_mode {
println!(" Function '{}' not found", name);
}
false
}
}
ConditionExpression::Test { name, args } => {
if self.config.debug_mode {
println!(" 🧪 Evaluating test CE: test({}({:?}))", name, args);
}
if let Some(function) = self.custom_functions.get(name) {
let arg_values: Vec<Value> = args
.iter()
.map(|arg| {
let resolved = facts
.get_nested(arg)
.or_else(|| facts.get(arg))
.unwrap_or(Value::String(arg.clone()));
if self.config.debug_mode {
println!(" Resolving arg '{}' -> {:?}", arg, resolved);
}
resolved
})
.collect();
match function(&arg_values, facts) {
Ok(result_value) => {
if self.config.debug_mode {
println!(" Test result: {:?}", result_value);
}
match result_value {
Value::Boolean(b) => b,
Value::Integer(i) => i != 0,
Value::Number(f) => f != 0.0,
Value::String(s) => !s.is_empty(),
_ => false,
}
}
Err(e) => {
if self.config.debug_mode {
println!(" Test function error: {}", e);
}
false
}
}
} else {
if self.config.debug_mode {
println!(" Trying to evaluate '{}' as arithmetic expression", name);
}
match self.evaluate_arithmetic_condition(name, facts) {
Ok(result) => {
if self.config.debug_mode {
println!(" Arithmetic expression result: {}", result);
}
result
}
Err(e) => {
if self.config.debug_mode {
println!(" Failed to evaluate expression: {}", e);
println!(" Test function '{}' not found", name);
}
false
}
}
}
}
ConditionExpression::MultiField { field, operation, variable: _ } => {
if self.config.debug_mode {
println!(" 📦 Evaluating multi-field: {}.{}", field, operation);
}
let field_value = facts.get_nested(field).or_else(|| facts.get(field));
if let Some(value) = field_value {
match operation.as_str() {
"empty" => {
matches!(value, Value::Array(arr) if arr.is_empty())
}
"not_empty" => {
matches!(value, Value::Array(arr) if !arr.is_empty())
}
"count" => {
if let Value::Array(arr) = value {
let count = Value::Integer(arr.len() as i64);
condition.operator.evaluate(&count, &condition.value)
} else {
false
}
}
"contains" => {
condition.operator.evaluate(&value, &condition.value)
}
_ => {
if self.config.debug_mode {
println!(" ⚠️ Operation '{}' not fully implemented yet", operation);
}
true
}
}
} else {
false
}
}
};
if self.config.debug_mode {
println!(" Result: {}", result);
}
Ok(result)
}
fn execute_action(&mut self, action: &ActionType, facts: &Facts) -> Result<()> {
match action {
ActionType::Set { field, value } => {
let evaluated_value = match value {
Value::Expression(expr) => {
crate::expression::evaluate_expression(expr, facts)?
}
_ => value.clone(),
};
if let Err(_) = facts.set_nested(field, evaluated_value.clone()) {
facts.set(field, evaluated_value.clone());
}
if self.config.debug_mode {
println!(" ✅ Set {field} = {evaluated_value:?}");
}
}
ActionType::Log { message } => {
println!("📋 LOG: {}", message);
}
ActionType::MethodCall {
object,
method,
args,
} => {
let result = self.execute_method_call(object, method, args, facts)?;
if self.config.debug_mode {
println!(" 🔧 Called {object}.{method}({args:?}) -> {result}");
}
}
ActionType::Retract { object } => {
if self.config.debug_mode {
println!(" 🗑️ Retracted {object}");
}
facts.set(&format!("_retracted_{}", object), Value::Boolean(true));
}
ActionType::Custom {
action_type,
params,
} => {
if let Some(handler) = self.action_handlers.get(action_type) {
if self.config.debug_mode {
println!(
" 🎯 Executing custom action: {action_type} with params: {params:?}"
);
}
let resolved_params = self.resolve_action_parameters(params, facts)?;
handler(&resolved_params, facts)?;
} else {
if self.config.debug_mode {
println!(" ⚠️ No handler registered for custom action: {action_type}");
println!(
" Available handlers: {:?}",
self.action_handlers.keys().collect::<Vec<_>>()
);
}
return Err(RuleEngineError::EvaluationError {
message: format!(
"No action handler registered for '{action_type}'. Use engine.register_action_handler() to add custom action handlers."
),
});
}
}
ActionType::ActivateAgendaGroup { group } => {
if self.config.debug_mode {
println!(" 🎯 Activating agenda group: {}", group);
}
self.workflow_engine.activate_agenda_group(group.clone());
self.agenda_manager.set_focus(group);
}
ActionType::ScheduleRule {
rule_name,
delay_ms,
} => {
if self.config.debug_mode {
println!(
" ⏰ Scheduling rule '{}' to execute in {}ms",
rule_name, delay_ms
);
}
self.workflow_engine
.schedule_rule(rule_name.clone(), *delay_ms, None);
}
ActionType::CompleteWorkflow { workflow_name } => {
if self.config.debug_mode {
println!(" ✅ Completing workflow: {}", workflow_name);
}
self.workflow_engine
.complete_workflow(workflow_name.clone());
}
ActionType::SetWorkflowData { key, value } => {
if self.config.debug_mode {
println!(" 💾 Setting workflow data: {} = {:?}", key, value);
}
let workflow_id = "default_workflow";
self.workflow_engine
.set_workflow_data(workflow_id, key.clone(), value.clone());
}
}
Ok(())
}
fn evaluate_arithmetic_condition(&self, expr: &str, facts: &Facts) -> Result<bool> {
let operators = [">=", "<=", "==", "!=", ">", "<"];
let mut split_pos = None;
let mut found_op = "";
for op in &operators {
if let Some(pos) = expr.rfind(op) {
split_pos = Some(pos);
found_op = op;
break;
}
}
if split_pos.is_none() {
return Err(RuleEngineError::EvaluationError {
message: format!("No comparison operator found in expression: {}", expr),
});
}
let pos = split_pos.unwrap();
let left_expr = expr[..pos].trim();
let right_value = expr[pos + found_op.len()..].trim();
let left_result = crate::expression::evaluate_expression(left_expr, facts)?;
let right_val = if let Ok(i) = right_value.parse::<i64>() {
Value::Integer(i)
} else if let Ok(f) = right_value.parse::<f64>() {
Value::Number(f)
} else {
match crate::expression::evaluate_expression(right_value, facts) {
Ok(v) => v,
Err(_) => Value::String(right_value.to_string()),
}
};
let operator = Operator::from_str(found_op).ok_or_else(|| {
RuleEngineError::InvalidOperator {
operator: found_op.to_string(),
}
})?;
Ok(operator.evaluate(&left_result, &right_val))
}
fn execute_function_call(
&self,
function: &str,
args: &[Value],
facts: &Facts,
) -> Result<String> {
let function_lower = function.to_lowercase();
match function_lower.as_str() {
"log" | "print" | "println" => self.handle_log_function(args),
"update" | "refresh" => self.handle_update_function(args),
"now" | "timestamp" => self.handle_timestamp_function(),
"random" => self.handle_random_function(args),
"format" | "sprintf" => self.handle_format_function(args),
"length" | "size" | "count" => self.handle_length_function(args),
"sum" | "add" => self.handle_sum_function(args),
"max" | "maximum" => self.handle_max_function(args),
"min" | "minimum" => self.handle_min_function(args),
"avg" | "average" => self.handle_average_function(args),
"round" => self.handle_round_function(args),
"floor" => self.handle_floor_function(args),
"ceil" | "ceiling" => self.handle_ceil_function(args),
"abs" | "absolute" => self.handle_abs_function(args),
"contains" | "includes" => self.handle_contains_function(args),
"startswith" | "begins_with" => self.handle_starts_with_function(args),
"endswith" | "ends_with" => self.handle_ends_with_function(args),
"lowercase" | "tolower" => self.handle_lowercase_function(args),
"uppercase" | "toupper" => self.handle_uppercase_function(args),
"trim" | "strip" => self.handle_trim_function(args),
"split" => self.handle_split_function(args),
"join" => self.handle_join_function(args),
_ => {
self.handle_custom_function(function, args, facts)
}
}
}
fn handle_log_function(&self, args: &[Value]) -> Result<String> {
let message = if args.is_empty() {
"".to_string()
} else if args.len() == 1 {
args[0].to_string()
} else {
args.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(" ")
};
println!("📋 {}", message);
Ok(message)
}
fn handle_update_function(&self, args: &[Value]) -> Result<String> {
if let Some(arg) = args.first() {
Ok(format!("Updated: {}", arg.to_string()))
} else {
Ok("Updated".to_string())
}
}
fn handle_timestamp_function(&self) -> Result<String> {
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| RuleEngineError::EvaluationError {
message: format!("Failed to get timestamp: {}", e),
})?
.as_secs();
Ok(timestamp.to_string())
}
fn handle_random_function(&self, args: &[Value]) -> Result<String> {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
std::time::SystemTime::now().hash(&mut hasher);
let random_value = hasher.finish();
if args.is_empty() {
Ok((random_value % 100).to_string()) } else if let Some(Value::Number(max)) = args.first() {
let max_val = *max as u64;
Ok((random_value % max_val).to_string())
} else {
Ok(random_value.to_string())
}
}
fn handle_format_function(&self, args: &[Value]) -> Result<String> {
if args.is_empty() {
return Ok("".to_string());
}
let template = args[0].to_string();
let values: Vec<String> = args[1..].iter().map(|v| v.to_string()).collect();
let mut result = template;
for (i, value) in values.iter().enumerate() {
result = result.replace(&format!("{{{}}}", i), value);
}
Ok(result)
}
fn handle_length_function(&self, args: &[Value]) -> Result<String> {
if let Some(arg) = args.first() {
match arg {
Value::String(s) => Ok(s.len().to_string()),
Value::Array(arr) => Ok(arr.len().to_string()),
Value::Object(obj) => Ok(obj.len().to_string()),
_ => Ok("1".to_string()), }
} else {
Ok("0".to_string())
}
}
fn handle_sum_function(&self, args: &[Value]) -> Result<String> {
let sum = args.iter().fold(0.0, |acc, val| match val {
Value::Number(n) => acc + n,
Value::Integer(i) => acc + (*i as f64),
_ => acc,
});
Ok(sum.to_string())
}
fn handle_max_function(&self, args: &[Value]) -> Result<String> {
let max = args.iter().fold(f64::NEG_INFINITY, |acc, val| match val {
Value::Number(n) => acc.max(*n),
Value::Integer(i) => acc.max(*i as f64),
_ => acc,
});
Ok(max.to_string())
}
fn handle_min_function(&self, args: &[Value]) -> Result<String> {
let min = args.iter().fold(f64::INFINITY, |acc, val| match val {
Value::Number(n) => acc.min(*n),
Value::Integer(i) => acc.min(*i as f64),
_ => acc,
});
Ok(min.to_string())
}
fn handle_average_function(&self, args: &[Value]) -> Result<String> {
if args.is_empty() {
return Ok("0".to_string());
}
let (sum, count) = args.iter().fold((0.0, 0), |(sum, count), val| match val {
Value::Number(n) => (sum + n, count + 1),
Value::Integer(i) => (sum + (*i as f64), count + 1),
_ => (sum, count),
});
if count > 0 {
Ok((sum / count as f64).to_string())
} else {
Ok("0".to_string())
}
}
fn handle_round_function(&self, args: &[Value]) -> Result<String> {
if let Some(Value::Number(n)) = args.first() {
Ok(n.round().to_string())
} else if let Some(Value::Integer(i)) = args.first() {
Ok(i.to_string())
} else {
Err(RuleEngineError::EvaluationError {
message: "round() requires a numeric argument".to_string(),
})
}
}
fn handle_floor_function(&self, args: &[Value]) -> Result<String> {
if let Some(Value::Number(n)) = args.first() {
Ok(n.floor().to_string())
} else if let Some(Value::Integer(i)) = args.first() {
Ok(i.to_string())
} else {
Err(RuleEngineError::EvaluationError {
message: "floor() requires a numeric argument".to_string(),
})
}
}
fn handle_ceil_function(&self, args: &[Value]) -> Result<String> {
if let Some(Value::Number(n)) = args.first() {
Ok(n.ceil().to_string())
} else if let Some(Value::Integer(i)) = args.first() {
Ok(i.to_string())
} else {
Err(RuleEngineError::EvaluationError {
message: "ceil() requires a numeric argument".to_string(),
})
}
}
fn handle_abs_function(&self, args: &[Value]) -> Result<String> {
if let Some(Value::Number(n)) = args.first() {
Ok(n.abs().to_string())
} else if let Some(Value::Integer(i)) = args.first() {
Ok(i.abs().to_string())
} else {
Err(RuleEngineError::EvaluationError {
message: "abs() requires a numeric argument".to_string(),
})
}
}
fn handle_contains_function(&self, args: &[Value]) -> Result<String> {
if args.len() >= 2 {
let haystack = args[0].to_string();
let needle = args[1].to_string();
Ok(haystack.contains(&needle).to_string())
} else {
Err(RuleEngineError::EvaluationError {
message: "contains() requires 2 arguments".to_string(),
})
}
}
fn handle_starts_with_function(&self, args: &[Value]) -> Result<String> {
if args.len() >= 2 {
let text = args[0].to_string();
let prefix = args[1].to_string();
Ok(text.starts_with(&prefix).to_string())
} else {
Err(RuleEngineError::EvaluationError {
message: "startswith() requires 2 arguments".to_string(),
})
}
}
fn handle_ends_with_function(&self, args: &[Value]) -> Result<String> {
if args.len() >= 2 {
let text = args[0].to_string();
let suffix = args[1].to_string();
Ok(text.ends_with(&suffix).to_string())
} else {
Err(RuleEngineError::EvaluationError {
message: "endswith() requires 2 arguments".to_string(),
})
}
}
fn handle_lowercase_function(&self, args: &[Value]) -> Result<String> {
if let Some(arg) = args.first() {
Ok(arg.to_string().to_lowercase())
} else {
Err(RuleEngineError::EvaluationError {
message: "lowercase() requires 1 argument".to_string(),
})
}
}
fn handle_uppercase_function(&self, args: &[Value]) -> Result<String> {
if let Some(arg) = args.first() {
Ok(arg.to_string().to_uppercase())
} else {
Err(RuleEngineError::EvaluationError {
message: "uppercase() requires 1 argument".to_string(),
})
}
}
fn handle_trim_function(&self, args: &[Value]) -> Result<String> {
if let Some(arg) = args.first() {
Ok(arg.to_string().trim().to_string())
} else {
Err(RuleEngineError::EvaluationError {
message: "trim() requires 1 argument".to_string(),
})
}
}
fn handle_split_function(&self, args: &[Value]) -> Result<String> {
if args.len() >= 2 {
let text = args[0].to_string();
let delimiter = args[1].to_string();
let parts: Vec<String> = text.split(&delimiter).map(|s| s.to_string()).collect();
Ok(format!("{:?}", parts)) } else {
Err(RuleEngineError::EvaluationError {
message: "split() requires 2 arguments".to_string(),
})
}
}
fn handle_join_function(&self, args: &[Value]) -> Result<String> {
if args.len() >= 2 {
let delimiter = args[0].to_string();
let parts: Vec<String> = args[1..].iter().map(|v| v.to_string()).collect();
Ok(parts.join(&delimiter))
} else {
Err(RuleEngineError::EvaluationError {
message: "join() requires at least 2 arguments".to_string(),
})
}
}
fn handle_custom_function(
&self,
function: &str,
args: &[Value],
facts: &Facts,
) -> Result<String> {
if let Some(custom_func) = self.custom_functions.get(function) {
if self.config.debug_mode {
println!("🎯 Calling registered function: {}({:?})", function, args);
}
match custom_func(args, facts) {
Ok(result) => Ok(result.to_string()),
Err(e) => Err(e),
}
} else {
if self.config.debug_mode {
println!("⚠️ Custom function '{}' not registered", function);
}
Err(RuleEngineError::EvaluationError {
message: format!("Function '{}' is not registered. Use engine.register_function() to add custom functions.", function),
})
}
}
fn execute_method_call(
&self,
object_name: &str,
method: &str,
args: &[Value],
facts: &Facts,
) -> Result<String> {
let Some(object_value) = facts.get(object_name) else {
return Err(RuleEngineError::EvaluationError {
message: format!("Object '{}' not found in facts", object_name),
});
};
let method_lower = method.to_lowercase();
if method_lower.starts_with("set") && args.len() == 1 {
return self.handle_setter_method(object_name, method, &args[0], object_value, facts);
}
if method_lower.starts_with("get") && args.is_empty() {
return self.handle_getter_method(object_name, method, &object_value);
}
match method_lower.as_str() {
"tostring" => Ok(object_value.to_string()),
"update" => {
facts.add_value(object_name, object_value)?;
Ok(format!("Updated {}", object_name))
}
"reset" => self.handle_reset_method(object_name, object_value, facts),
_ => self.handle_property_access_or_fallback(
object_name,
method,
args.len(),
&object_value,
),
}
}
fn handle_setter_method(
&self,
object_name: &str,
method: &str,
new_value: &Value,
mut object_value: Value,
facts: &Facts,
) -> Result<String> {
let property_name = Self::extract_property_name_from_setter(method);
match object_value {
Value::Object(ref mut obj) => {
obj.insert(property_name.clone(), new_value.clone());
facts.add_value(object_name, object_value)?;
Ok(format!(
"Set {} to {}",
property_name,
new_value.to_string()
))
}
_ => Err(RuleEngineError::EvaluationError {
message: format!("Cannot call setter on non-object type: {}", object_name),
}),
}
}
fn handle_getter_method(
&self,
object_name: &str,
method: &str,
object_value: &Value,
) -> Result<String> {
let property_name = Self::extract_property_name_from_getter(method);
match object_value {
Value::Object(obj) => {
if let Some(value) = obj.get(&property_name) {
Ok(value.to_string())
} else {
Err(RuleEngineError::EvaluationError {
message: format!(
"Property '{}' not found on object '{}'",
property_name, object_name
),
})
}
}
_ => Err(RuleEngineError::EvaluationError {
message: format!("Cannot call getter on non-object type: {}", object_name),
}),
}
}
fn handle_reset_method(
&self,
object_name: &str,
mut object_value: Value,
facts: &Facts,
) -> Result<String> {
match object_value {
Value::Object(ref mut obj) => {
obj.clear();
facts.add_value(object_name, object_value)?;
Ok(format!("Reset {}", object_name))
}
_ => Err(RuleEngineError::EvaluationError {
message: format!("Cannot reset non-object type: {}", object_name),
}),
}
}
fn handle_property_access_or_fallback(
&self,
object_name: &str,
method: &str,
arg_count: usize,
object_value: &Value,
) -> Result<String> {
if let Value::Object(obj) = object_value {
if let Some(value) = obj.get(method) {
return Ok(value.to_string());
}
let capitalized_method = Self::capitalize_first_letter(method);
if let Some(value) = obj.get(&capitalized_method) {
return Ok(value.to_string());
}
}
Ok(format!(
"Called {}.{} with {} args",
object_name, method, arg_count
))
}
fn extract_property_name_from_setter(method: &str) -> String {
let property_name = &method[3..]; Self::capitalize_first_letter(property_name)
}
fn extract_property_name_from_getter(method: &str) -> String {
let property_name = &method[3..]; Self::capitalize_first_letter(property_name)
}
fn capitalize_first_letter(s: &str) -> String {
if s.is_empty() {
return String::new();
}
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
}
fn resolve_action_parameters(
&self,
params: &HashMap<String, Value>,
facts: &Facts,
) -> Result<HashMap<String, Value>> {
let mut resolved = HashMap::new();
for (key, value) in params {
let resolved_value = match value {
Value::String(s) => {
if s.contains('.') {
if let Some(fact_value) = facts.get_nested(s) {
fact_value
} else {
value.clone()
}
} else {
value.clone()
}
}
_ => value.clone(),
};
resolved.insert(key.clone(), resolved_value);
}
Ok(resolved)
}
pub fn load_plugin(
&mut self,
plugin: std::sync::Arc<dyn crate::engine::plugin::RulePlugin>,
) -> Result<()> {
plugin.register_actions(self)?;
plugin.register_functions(self)?;
self.plugin_manager.load_plugin(plugin)
}
pub fn unload_plugin(&mut self, name: &str) -> Result<()> {
self.plugin_manager.unload_plugin(name)
}
pub fn hot_reload_plugin(
&mut self,
name: &str,
new_plugin: std::sync::Arc<dyn crate::engine::plugin::RulePlugin>,
) -> Result<()> {
self.plugin_manager.unload_plugin(name)?;
new_plugin.register_actions(self)?;
new_plugin.register_functions(self)?;
self.plugin_manager.load_plugin(new_plugin)
}
pub fn get_plugin_info(&self, name: &str) -> Option<&crate::engine::plugin::PluginMetadata> {
self.plugin_manager.get_plugin_info(name)
}
pub fn list_plugins(&self) -> Vec<PluginInfo> {
self.plugin_manager.list_plugins()
}
pub fn get_plugin_stats(&self) -> PluginStats {
self.plugin_manager.get_stats()
}
pub fn plugin_health_check(&mut self) -> HashMap<String, crate::engine::plugin::PluginHealth> {
self.plugin_manager.plugin_health_check()
}
pub fn configure_plugins(&mut self, config: PluginConfig) {
self.plugin_manager = PluginManager::new(config);
}
}