rust_rule_engine/plugins/
math_utils.rs1use crate::engine::plugin::{PluginHealth, PluginMetadata, PluginState, RulePlugin};
2use crate::engine::RustRuleEngine;
3use crate::errors::{Result, RuleEngineError};
4use crate::types::Value;
5
6pub 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 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 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 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 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 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 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 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 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 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 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 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
227fn 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 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}