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
7pub 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 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 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 facts.set_nested(&array_path, Value::Array(vec![element]))?;
91 }
92 Ok(())
93 });
94
95 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
444fn 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}