rust_rule_engine/plugins/
math_utils.rs

1use crate::engine::plugin::{PluginHealth, PluginMetadata, PluginState, RulePlugin};
2use crate::engine::RustRuleEngine;
3use crate::errors::{Result, RuleEngineError};
4use crate::types::Value;
5
6/// Built-in plugin for mathematical operations
7pub struct MathUtilsPlugin {
8    metadata: PluginMetadata,
9}
10
11impl Default for MathUtilsPlugin {
12    fn default() -> Self {
13        Self::new()
14    }
15}
16
17impl MathUtilsPlugin {
18    pub fn new() -> Self {
19        Self {
20            metadata: PluginMetadata {
21                name: "math-utils".to_string(),
22                version: "1.0.0".to_string(),
23                description: "Mathematical operations and utilities".to_string(),
24                author: "Rust Rule Engine Team".to_string(),
25                state: PluginState::Loaded,
26                health: PluginHealth::Healthy,
27                actions: vec![
28                    "Add".to_string(),
29                    "Subtract".to_string(),
30                    "Multiply".to_string(),
31                    "Divide".to_string(),
32                    "Modulo".to_string(),
33                    "Power".to_string(),
34                    "Abs".to_string(),
35                    "Round".to_string(),
36                    "Ceil".to_string(),
37                    "Floor".to_string(),
38                ],
39                functions: vec![
40                    "min".to_string(),
41                    "max".to_string(),
42                    "sqrt".to_string(),
43                    "random".to_string(),
44                    "sum".to_string(),
45                    "avg".to_string(),
46                ],
47                dependencies: vec![],
48            },
49        }
50    }
51}
52
53impl RulePlugin for MathUtilsPlugin {
54    fn get_metadata(&self) -> &PluginMetadata {
55        &self.metadata
56    }
57
58    fn register_actions(&self, engine: &mut RustRuleEngine) -> Result<()> {
59        // Add - Addition
60        engine.register_action_handler("Add", |params, facts| {
61            let a = get_number_param(params, facts, "a", "0")?;
62            let b = get_number_param(params, facts, "b", "1")?;
63            let output = get_string_param(params, "output", "2")?;
64
65            let result = a + b;
66            facts.set_nested(&output, Value::Number(result))?;
67            Ok(())
68        });
69
70        // Subtract - Subtraction
71        engine.register_action_handler("Subtract", |params, facts| {
72            let a = get_number_param(params, facts, "a", "0")?;
73            let b = get_number_param(params, facts, "b", "1")?;
74            let output = get_string_param(params, "output", "2")?;
75
76            let result = a - b;
77            facts.set_nested(&output, Value::Number(result))?;
78            Ok(())
79        });
80
81        // Multiply - Multiplication
82        engine.register_action_handler("Multiply", |params, facts| {
83            let a = get_number_param(params, facts, "a", "0")?;
84            let b = get_number_param(params, facts, "b", "1")?;
85            let output = get_string_param(params, "output", "2")?;
86
87            let result = a * b;
88            facts.set_nested(&output, Value::Number(result))?;
89            Ok(())
90        });
91
92        // Divide - Division
93        engine.register_action_handler("Divide", |params, facts| {
94            let a = get_number_param(params, facts, "a", "0")?;
95            let b = get_number_param(params, facts, "b", "1")?;
96            let output = get_string_param(params, "output", "2")?;
97
98            if b == 0.0 {
99                return Err(RuleEngineError::ActionError {
100                    message: "Division by zero".to_string(),
101                });
102            }
103
104            let result = a / b;
105            facts.set_nested(&output, Value::Number(result))?;
106            Ok(())
107        });
108
109        // Abs - Absolute value
110        engine.register_action_handler("Abs", |params, facts| {
111            let a = get_number_param(params, facts, "input", "0")?;
112            let output = get_string_param(params, "output", "1")?;
113
114            let result = a.abs();
115            facts.set_nested(&output, Value::Number(result))?;
116            Ok(())
117        });
118
119        // Round - Round to nearest integer
120        engine.register_action_handler("Round", |params, facts| {
121            let a = get_number_param(params, facts, "input", "0")?;
122            let output = get_string_param(params, "output", "1")?;
123
124            let result = a.round();
125            facts.set_nested(&output, Value::Number(result))?;
126            Ok(())
127        });
128
129        Ok(())
130    }
131
132    fn register_functions(&self, engine: &mut RustRuleEngine) -> Result<()> {
133        // min - Find minimum value
134        engine.register_function("min", |args, _facts| {
135            if args.is_empty() {
136                return Err(RuleEngineError::EvaluationError {
137                    message: "min requires at least 1 argument".to_string(),
138                });
139            }
140
141            let mut min_val = value_to_number(&args[0])?;
142            for arg in &args[1..] {
143                let val = value_to_number(arg)?;
144                if val < min_val {
145                    min_val = val;
146                }
147            }
148            Ok(Value::Number(min_val))
149        });
150
151        // max - Find maximum value
152        engine.register_function("max", |args, _facts| {
153            if args.is_empty() {
154                return Err(RuleEngineError::EvaluationError {
155                    message: "max requires at least 1 argument".to_string(),
156                });
157            }
158
159            let mut max_val = value_to_number(&args[0])?;
160            for arg in &args[1..] {
161                let val = value_to_number(arg)?;
162                if val > max_val {
163                    max_val = val;
164                }
165            }
166            Ok(Value::Number(max_val))
167        });
168
169        // sqrt - Square root
170        engine.register_function("sqrt", |args, _facts| {
171            if args.len() != 1 {
172                return Err(RuleEngineError::EvaluationError {
173                    message: "sqrt requires exactly 1 argument".to_string(),
174                });
175            }
176
177            let val = value_to_number(&args[0])?;
178            if val < 0.0 {
179                return Err(RuleEngineError::EvaluationError {
180                    message: "Cannot calculate square root of negative number".to_string(),
181                });
182            }
183
184            Ok(Value::Number(val.sqrt()))
185        });
186
187        // sum - Sum all values
188        engine.register_function("sum", |args, _facts| {
189            if args.is_empty() {
190                return Ok(Value::Number(0.0));
191            }
192
193            let mut total = 0.0;
194            for arg in args {
195                total += value_to_number(arg)?;
196            }
197            Ok(Value::Number(total))
198        });
199
200        // avg - Average of all values
201        engine.register_function("avg", |args, _facts| {
202            if args.is_empty() {
203                return Err(RuleEngineError::EvaluationError {
204                    message: "avg requires at least 1 argument".to_string(),
205                });
206            }
207
208            let mut total = 0.0;
209            for arg in args {
210                total += value_to_number(arg)?;
211            }
212            Ok(Value::Number(total / args.len() as f64))
213        });
214
215        Ok(())
216    }
217
218    fn unload(&mut self) -> Result<()> {
219        self.metadata.state = PluginState::Unloaded;
220        Ok(())
221    }
222
223    fn health_check(&mut self) -> PluginHealth {
224        match self.metadata.state {
225            PluginState::Loaded => PluginHealth::Healthy,
226            PluginState::Loading => PluginHealth::Warning("Plugin is loading".to_string()),
227            PluginState::Error => PluginHealth::Error("Plugin is in error state".to_string()),
228            PluginState::Unloaded => PluginHealth::Warning("Plugin is unloaded".to_string()),
229        }
230    }
231}
232
233// Helper functions
234fn get_string_param(
235    params: &std::collections::HashMap<String, Value>,
236    name: &str,
237    pos: &str,
238) -> Result<String> {
239    let value = params
240        .get(name)
241        .or_else(|| params.get(pos))
242        .ok_or_else(|| RuleEngineError::ActionError {
243            message: format!("Missing parameter: {}", name),
244        })?;
245
246    match value {
247        Value::String(s) => Ok(s.clone()),
248        _ => Err(RuleEngineError::ActionError {
249            message: format!("Parameter {} must be string", name),
250        }),
251    }
252}
253
254fn get_number_param(
255    params: &std::collections::HashMap<String, Value>,
256    facts: &crate::Facts,
257    name: &str,
258    pos: &str,
259) -> Result<f64> {
260    let value = params
261        .get(name)
262        .or_else(|| params.get(pos))
263        .ok_or_else(|| RuleEngineError::ActionError {
264            message: format!("Missing parameter: {}", name),
265        })?;
266
267    // If it's a fact reference, resolve it
268    if let Value::String(s) = value {
269        if s.contains('.') {
270            if let Some(fact_value) = facts.get(s) {
271                return value_to_number(&fact_value);
272            }
273        }
274    }
275
276    value_to_number(value)
277}
278
279fn value_to_number(value: &Value) -> Result<f64> {
280    match value {
281        Value::Number(f) => Ok(*f),
282        Value::Integer(i) => Ok(*i as f64),
283        Value::String(s) => s.parse::<f64>().map_err(|_| RuleEngineError::ActionError {
284            message: format!("Cannot convert '{}' to number", s),
285        }),
286        _ => Err(RuleEngineError::ActionError {
287            message: "Value cannot be converted to number".to_string(),
288        }),
289    }
290}