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