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 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 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 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 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 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 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 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 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 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 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 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 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
233fn 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 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}