rust_rule_engine/plugins/
collection_utils.rs

1use crate::engine::plugin::{PluginHealth, PluginMetadata, PluginState, RulePlugin};
2use crate::engine::RustRuleEngine;
3use crate::errors::{Result, RuleEngineError};
4use crate::types::Value;
5use std::collections::HashMap;
6
7/// Built-in plugin for collection operations
8pub struct CollectionUtilsPlugin {
9    metadata: PluginMetadata,
10}
11
12impl CollectionUtilsPlugin {
13    pub fn new() -> Self {
14        Self {
15            metadata: PluginMetadata {
16                name: "collection_utils".to_string(),
17                version: "1.0.0".to_string(),
18                description: "Collection manipulation utilities".to_string(),
19                author: "Rust Rule Engine Team".to_string(),
20                state: PluginState::Loaded,
21                health: PluginHealth::Healthy,
22                actions: vec![
23                    "ArrayLength".to_string(),
24                    "ArrayPush".to_string(),
25                    "ArrayPop".to_string(),
26                    "ArraySort".to_string(),
27                    "ArrayFilter".to_string(),
28                    "ArrayMap".to_string(),
29                    "ArrayFind".to_string(),
30                    "ObjectKeys".to_string(),
31                    "ObjectValues".to_string(),
32                    "ObjectMerge".to_string(),
33                ],
34                functions: vec![
35                    "length".to_string(),
36                    "contains".to_string(),
37                    "first".to_string(),
38                    "last".to_string(),
39                    "reverse".to_string(),
40                    "join".to_string(),
41                    "slice".to_string(),
42                    "keys".to_string(),
43                    "values".to_string(),
44                ],
45                dependencies: vec![],
46            },
47        }
48    }
49}
50
51impl RulePlugin for CollectionUtilsPlugin {
52    fn get_metadata(&self) -> &PluginMetadata {
53        &self.metadata
54    }
55
56    fn register_actions(&self, engine: &mut RustRuleEngine) -> Result<()> {
57        // ArrayLength - Get array length
58        engine.register_action_handler("ArrayLength", |params, facts| {
59            let input = get_string_param(params, "input", "0")?;
60            let output = get_string_param(params, "output", "1")?;
61
62            if let Some(value) = facts.get(&input) {
63                let length = match value {
64                    Value::Array(arr) => arr.len(),
65                    Value::String(s) => s.len(),
66                    Value::Object(obj) => obj.len(),
67                    _ => 0,
68                };
69                facts.set_nested(&output, Value::Integer(length as i64))?;
70            }
71            Ok(())
72        });
73
74        // ArrayPush - Add element to array
75        engine.register_action_handler("ArrayPush", |params, facts| {
76            let array_path = get_string_param(params, "array", "0")?;
77            let element = get_value_param(params, facts, "element", "1")?;
78
79            if let Some(value) = facts.get(&array_path) {
80                if let Value::Array(mut arr) = value.clone() {
81                    arr.push(element);
82                    facts.set_nested(&array_path, Value::Array(arr))?;
83                } else {
84                    return Err(RuleEngineError::ActionError {
85                        message: "Target must be an array".to_string(),
86                    });
87                }
88            } else {
89                // Create new array with the element
90                facts.set_nested(&array_path, Value::Array(vec![element]))?;
91            }
92            Ok(())
93        });
94
95        // ArrayPop - Remove and return last element
96        engine.register_action_handler("ArrayPop", |params, facts| {
97            let array_path = get_string_param(params, "array", "0")?;
98            let output = get_string_param(params, "output", "1")?;
99
100            if let Some(value) = facts.get(&array_path) {
101                if let Value::Array(mut arr) = value.clone() {
102                    if let Some(popped) = arr.pop() {
103                        facts.set_nested(&array_path, Value::Array(arr))?;
104                        facts.set_nested(&output, popped)?;
105                    } else {
106                        facts.set_nested(&output, Value::Null)?;
107                    }
108                }
109            }
110            Ok(())
111        });
112
113        // ArraySort - Sort array
114        engine.register_action_handler("ArraySort", |params, facts| {
115            let array_path = get_string_param(params, "array", "0")?;
116            let ascending = get_optional_bool_param(params, "ascending").unwrap_or(true);
117
118            if let Some(value) = facts.get(&array_path) {
119                if let Value::Array(mut arr) = value.clone() {
120                    arr.sort_by(|a, b| {
121                        let order = compare_values(a, b);
122                        if ascending {
123                            order
124                        } else {
125                            order.reverse()
126                        }
127                    });
128                    facts.set_nested(&array_path, Value::Array(arr))?;
129                }
130            }
131            Ok(())
132        });
133
134        // ArrayFilter - Filter array elements
135        engine.register_action_handler("ArrayFilter", |params, facts| {
136            let input = get_string_param(params, "input", "0")?;
137            let predicate_field = get_string_param(params, "field", "1")?;
138            let predicate_value = get_value_param(params, facts, "value", "2")?;
139            let output = get_string_param(params, "output", "3")?;
140
141            if let Some(value) = facts.get(&input) {
142                if let Value::Array(arr) = value {
143                    let filtered: Vec<Value> = arr
144                        .iter()
145                        .filter(|item| filter_predicate(item, &predicate_field, &predicate_value))
146                        .cloned()
147                        .collect();
148                    facts.set_nested(&output, Value::Array(filtered))?;
149                }
150            }
151            Ok(())
152        });
153
154        // ArrayFind - Find first matching element
155        engine.register_action_handler("ArrayFind", |params, facts| {
156            let input = get_string_param(params, "input", "0")?;
157            let predicate_field = get_string_param(params, "field", "1")?;
158            let predicate_value = get_value_param(params, facts, "value", "2")?;
159            let output = get_string_param(params, "output", "3")?;
160
161            if let Some(value) = facts.get(&input) {
162                if let Value::Array(arr) = value {
163                    let found = arr
164                        .iter()
165                        .find(|item| filter_predicate(item, &predicate_field, &predicate_value))
166                        .cloned()
167                        .unwrap_or(Value::Null);
168                    facts.set_nested(&output, found)?;
169                }
170            }
171            Ok(())
172        });
173
174        // ObjectKeys - Get object keys
175        engine.register_action_handler("ObjectKeys", |params, facts| {
176            let input = get_string_param(params, "input", "0")?;
177            let output = get_string_param(params, "output", "1")?;
178
179            if let Some(value) = facts.get(&input) {
180                if let Value::Object(obj) = value {
181                    let keys: Vec<Value> = obj.keys().map(|k| Value::String(k.clone())).collect();
182                    facts.set_nested(&output, Value::Array(keys))?;
183                }
184            }
185            Ok(())
186        });
187
188        // ObjectValues - Get object values
189        engine.register_action_handler("ObjectValues", |params, facts| {
190            let input = get_string_param(params, "input", "0")?;
191            let output = get_string_param(params, "output", "1")?;
192
193            if let Some(value) = facts.get(&input) {
194                if let Value::Object(obj) = value {
195                    let values: Vec<Value> = obj.values().cloned().collect();
196                    facts.set_nested(&output, Value::Array(values))?;
197                }
198            }
199            Ok(())
200        });
201
202        // ObjectMerge - Merge two objects
203        engine.register_action_handler("ObjectMerge", |params, facts| {
204            let source1 = get_string_param(params, "source1", "0")?;
205            let source2 = get_string_param(params, "source2", "1")?;
206            let output = get_string_param(params, "output", "2")?;
207
208            let obj1 = facts
209                .get(&source1)
210                .and_then(|v| {
211                    if let Value::Object(obj) = v {
212                        Some(obj.clone())
213                    } else {
214                        None
215                    }
216                })
217                .unwrap_or_default();
218
219            let obj2 = facts
220                .get(&source2)
221                .and_then(|v| {
222                    if let Value::Object(obj) = v {
223                        Some(obj.clone())
224                    } else {
225                        None
226                    }
227                })
228                .unwrap_or_default();
229
230            let mut merged = obj1;
231            for (key, value) in obj2 {
232                merged.insert(key, value);
233            }
234
235            facts.set_nested(&output, Value::Object(merged))?;
236            Ok(())
237        });
238
239        Ok(())
240    }
241
242    fn register_functions(&self, engine: &mut RustRuleEngine) -> Result<()> {
243        // length - Get collection length
244        engine.register_function("length", |args, _facts| {
245            if args.len() != 1 {
246                return Err(RuleEngineError::EvaluationError {
247                    message: "length requires exactly 1 argument".to_string(),
248                });
249            }
250
251            let length = match &args[0] {
252                Value::Array(arr) => arr.len(),
253                Value::String(s) => s.len(),
254                Value::Object(obj) => obj.len(),
255                _ => 0,
256            };
257            Ok(Value::Integer(length as i64))
258        });
259
260        // contains - Check if collection contains value
261        engine.register_function("contains", |args, _facts| {
262            if args.len() != 2 {
263                return Err(RuleEngineError::EvaluationError {
264                    message: "contains requires exactly 2 arguments: collection, value".to_string(),
265                });
266            }
267
268            let contains = match (&args[0], &args[1]) {
269                (Value::Array(arr), value) => arr.contains(value),
270                (Value::String(s), Value::String(search)) => s.contains(search),
271                (Value::Object(obj), Value::String(key)) => obj.contains_key(key),
272                _ => false,
273            };
274            Ok(Value::Boolean(contains))
275        });
276
277        // first - Get first element
278        engine.register_function("first", |args, _facts| {
279            if args.len() != 1 {
280                return Err(RuleEngineError::EvaluationError {
281                    message: "first requires exactly 1 argument".to_string(),
282                });
283            }
284
285            let first = match &args[0] {
286                Value::Array(arr) => arr.first().cloned().unwrap_or(Value::Null),
287                Value::String(s) => {
288                    if s.is_empty() {
289                        Value::Null
290                    } else {
291                        Value::String(s.chars().next().unwrap().to_string())
292                    }
293                }
294                _ => Value::Null,
295            };
296            Ok(first)
297        });
298
299        // last - Get last element
300        engine.register_function("last", |args, _facts| {
301            if args.len() != 1 {
302                return Err(RuleEngineError::EvaluationError {
303                    message: "last requires exactly 1 argument".to_string(),
304                });
305            }
306
307            let last = match &args[0] {
308                Value::Array(arr) => arr.last().cloned().unwrap_or(Value::Null),
309                Value::String(s) => {
310                    if s.is_empty() {
311                        Value::Null
312                    } else {
313                        Value::String(s.chars().last().unwrap().to_string())
314                    }
315                }
316                _ => Value::Null,
317            };
318            Ok(last)
319        });
320
321        // reverse - Reverse array or string
322        engine.register_function("reverse", |args, _facts| {
323            if args.len() != 1 {
324                return Err(RuleEngineError::EvaluationError {
325                    message: "reverse requires exactly 1 argument".to_string(),
326                });
327            }
328
329            let reversed = match &args[0] {
330                Value::Array(arr) => {
331                    let mut rev = arr.clone();
332                    rev.reverse();
333                    Value::Array(rev)
334                }
335                Value::String(s) => Value::String(s.chars().rev().collect()),
336                _ => args[0].clone(),
337            };
338            Ok(reversed)
339        });
340
341        // join - Join array elements
342        engine.register_function("join", |args, _facts| {
343            if args.len() != 2 {
344                return Err(RuleEngineError::EvaluationError {
345                    message: "join requires exactly 2 arguments: array, separator".to_string(),
346                });
347            }
348
349            match (&args[0], &args[1]) {
350                (Value::Array(arr), Value::String(sep)) => {
351                    let strings: Vec<String> = arr
352                        .iter()
353                        .map(|v| value_to_string(v).unwrap_or_default())
354                        .collect();
355                    Ok(Value::String(strings.join(sep)))
356                }
357                _ => Err(RuleEngineError::EvaluationError {
358                    message: "join requires array and string separator".to_string(),
359                }),
360            }
361        });
362
363        // slice - Get slice of array
364        engine.register_function("slice", |args, _facts| {
365            if args.len() < 2 || args.len() > 3 {
366                return Err(RuleEngineError::EvaluationError {
367                    message: "slice requires 2-3 arguments: array, start, [end]".to_string(),
368                });
369            }
370
371            match &args[0] {
372                Value::Array(arr) => {
373                    let start = value_to_number(&args[1])? as usize;
374                    let end = if args.len() == 3 {
375                        value_to_number(&args[2])? as usize
376                    } else {
377                        arr.len()
378                    };
379
380                    let start = start.min(arr.len());
381                    let end = end.min(arr.len());
382
383                    if start <= end {
384                        Ok(Value::Array(arr[start..end].to_vec()))
385                    } else {
386                        Ok(Value::Array(vec![]))
387                    }
388                }
389                _ => Err(RuleEngineError::EvaluationError {
390                    message: "slice requires array as first argument".to_string(),
391                }),
392            }
393        });
394
395        // keys - Get object keys
396        engine.register_function("keys", |args, _facts| {
397            if args.len() != 1 {
398                return Err(RuleEngineError::EvaluationError {
399                    message: "keys requires exactly 1 argument".to_string(),
400                });
401            }
402
403            match &args[0] {
404                Value::Object(obj) => {
405                    let keys: Vec<Value> = obj.keys().map(|k| Value::String(k.clone())).collect();
406                    Ok(Value::Array(keys))
407                }
408                _ => Ok(Value::Array(vec![])),
409            }
410        });
411
412        // values - Get object values
413        engine.register_function("values", |args, _facts| {
414            if args.len() != 1 {
415                return Err(RuleEngineError::EvaluationError {
416                    message: "values requires exactly 1 argument".to_string(),
417                });
418            }
419
420            match &args[0] {
421                Value::Object(obj) => Ok(Value::Array(obj.values().cloned().collect())),
422                _ => Ok(Value::Array(vec![])),
423            }
424        });
425
426        Ok(())
427    }
428
429    fn unload(&mut self) -> Result<()> {
430        self.metadata.state = PluginState::Unloaded;
431        Ok(())
432    }
433
434    fn health_check(&mut self) -> PluginHealth {
435        match self.metadata.state {
436            PluginState::Loaded => PluginHealth::Healthy,
437            PluginState::Loading => PluginHealth::Warning("Plugin is loading".to_string()),
438            PluginState::Error => PluginHealth::Error("Plugin is in error state".to_string()),
439            PluginState::Unloaded => PluginHealth::Warning("Plugin is unloaded".to_string()),
440        }
441    }
442}
443
444// Helper functions
445fn get_string_param(params: &HashMap<String, Value>, name: &str, pos: &str) -> Result<String> {
446    let value = params
447        .get(name)
448        .or_else(|| params.get(pos))
449        .ok_or_else(|| RuleEngineError::ActionError {
450            message: format!("Missing parameter: {}", name),
451        })?;
452
453    match value {
454        Value::String(s) => Ok(s.clone()),
455        _ => Err(RuleEngineError::ActionError {
456            message: format!("Parameter {} must be string", name),
457        }),
458    }
459}
460
461fn get_value_param(
462    params: &HashMap<String, Value>,
463    facts: &crate::Facts,
464    name: &str,
465    pos: &str,
466) -> Result<Value> {
467    let value = params
468        .get(name)
469        .or_else(|| params.get(pos))
470        .ok_or_else(|| RuleEngineError::ActionError {
471            message: format!("Missing parameter: {}", name),
472        })?;
473
474    if let Value::String(s) = value {
475        if s.contains('.') {
476            if let Some(fact_value) = facts.get(s) {
477                return Ok(fact_value.clone());
478            }
479        }
480    }
481
482    Ok(value.clone())
483}
484
485fn get_optional_bool_param(params: &HashMap<String, Value>, name: &str) -> Option<bool> {
486    params.get(name).and_then(|v| match v {
487        Value::Boolean(b) => Some(*b),
488        Value::String(s) => s.parse().ok(),
489        _ => None,
490    })
491}
492
493fn value_to_string(value: &Value) -> Result<String> {
494    match value {
495        Value::String(s) => Ok(s.clone()),
496        Value::Integer(i) => Ok(i.to_string()),
497        Value::Number(f) => Ok(f.to_string()),
498        Value::Boolean(b) => Ok(b.to_string()),
499        _ => Err(RuleEngineError::ActionError {
500            message: "Value cannot be converted to string".to_string(),
501        }),
502    }
503}
504
505fn value_to_number(value: &Value) -> Result<f64> {
506    match value {
507        Value::Number(f) => Ok(*f),
508        Value::Integer(i) => Ok(*i as f64),
509        Value::String(s) => s.parse::<f64>().map_err(|_| RuleEngineError::ActionError {
510            message: format!("Cannot convert '{}' to number", s),
511        }),
512        _ => Err(RuleEngineError::ActionError {
513            message: "Value cannot be converted to number".to_string(),
514        }),
515    }
516}
517
518fn compare_values(a: &Value, b: &Value) -> std::cmp::Ordering {
519    use std::cmp::Ordering;
520
521    match (a, b) {
522        (Value::Integer(a), Value::Integer(b)) => a.cmp(b),
523        (Value::Number(a), Value::Number(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
524        (Value::String(a), Value::String(b)) => a.cmp(b),
525        (Value::Boolean(a), Value::Boolean(b)) => a.cmp(b),
526        _ => Ordering::Equal,
527    }
528}
529
530fn filter_predicate(item: &Value, field: &str, expected: &Value) -> bool {
531    if field == "_value" {
532        return item == expected;
533    }
534
535    if let Value::Object(obj) = item {
536        if let Some(field_value) = obj.get(field) {
537            return field_value == expected;
538        }
539    }
540
541    false
542}