1use crate::rete::facts::{FactValue, TypedFacts};
48use crate::errors::{Result, RuleEngineError};
49use std::collections::HashMap;
50
51#[derive(Debug, Clone, PartialEq)]
55pub enum MultifieldOp {
56 Collect,
60
61 Contains,
65
66 Count,
70
71 First,
75
76 Last,
80
81 Index(usize),
85
86 Slice(usize, usize),
90
91 IsEmpty,
95
96 NotEmpty,
100}
101
102impl MultifieldOp {
103 pub fn evaluate(
111 &self,
112 facts: &TypedFacts,
113 field: &str,
114 value: Option<&FactValue>,
115 ) -> Option<Vec<FactValue>> {
116 let field_value = facts.get(field)?;
118
119 let array = match field_value {
121 FactValue::Array(arr) => arr,
122 _ => return None, };
124
125 match self {
126 MultifieldOp::Collect => {
127 Some(array.clone())
129 }
130
131 MultifieldOp::Contains => {
132 let search_value = value?;
134 let contains = array.contains(search_value);
135 Some(vec![FactValue::Boolean(contains)])
136 }
137
138 MultifieldOp::Count => {
139 Some(vec![FactValue::Integer(array.len() as i64)])
141 }
142
143 MultifieldOp::First => {
144 array.first().cloned().map(|v| vec![v])
146 }
147
148 MultifieldOp::Last => {
149 array.last().cloned().map(|v| vec![v])
151 }
152
153 MultifieldOp::Index(idx) => {
154 array.get(*idx).cloned().map(|v| vec![v])
156 }
157
158 MultifieldOp::Slice(start, end) => {
159 let end = (*end).min(array.len());
161 if *start >= end {
162 return Some(Vec::new());
163 }
164 Some(array[*start..end].to_vec())
165 }
166
167 MultifieldOp::IsEmpty => {
168 Some(vec![FactValue::Boolean(array.is_empty())])
170 }
171
172 MultifieldOp::NotEmpty => {
173 Some(vec![FactValue::Boolean(!array.is_empty())])
175 }
176 }
177 }
178
179 pub fn to_string(&self) -> String {
181 match self {
182 MultifieldOp::Collect => "collect".to_string(),
183 MultifieldOp::Contains => "contains".to_string(),
184 MultifieldOp::Count => "count".to_string(),
185 MultifieldOp::First => "first".to_string(),
186 MultifieldOp::Last => "last".to_string(),
187 MultifieldOp::Index(idx) => format!("[{}]", idx),
188 MultifieldOp::Slice(start, end) => format!("[{}:{}]", start, end),
189 MultifieldOp::IsEmpty => "empty".to_string(),
190 MultifieldOp::NotEmpty => "not_empty".to_string(),
191 }
192 }
193}
194
195pub fn evaluate_multifield_pattern(
214 facts: &TypedFacts,
215 field: &str,
216 operator: &MultifieldOp,
217 variable: Option<&str>,
218 value: Option<&FactValue>,
219 bindings: &HashMap<String, FactValue>,
220) -> Option<HashMap<String, FactValue>> {
221 let result = operator.evaluate(facts, field, value)?;
223
224 let mut new_bindings = bindings.clone();
226
227 if let Some(var_name) = variable {
229 if matches!(operator, MultifieldOp::Collect) {
231 new_bindings.insert(var_name.to_string(), FactValue::Array(result));
232 } else {
233 if result.len() == 1 {
235 new_bindings.insert(var_name.to_string(), result[0].clone());
236 } else {
237 new_bindings.insert(var_name.to_string(), FactValue::Array(result));
239 }
240 }
241 } else {
242 if result.len() == 1 {
245 if let FactValue::Boolean(b) = result[0] {
246 if !b {
247 return None; }
249 }
250 }
251 }
252
253 Some(new_bindings)
254}
255
256pub fn parse_multifield_variable(input: &str) -> Option<String> {
264 let trimmed = input.trim();
265 if trimmed.starts_with("$?") {
266 Some(trimmed[2..].to_string())
267 } else {
268 None
269 }
270}
271
272pub fn is_multifield_variable(input: &str) -> bool {
274 input.trim().starts_with("$?")
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 fn create_test_facts_with_array() -> TypedFacts {
282 let mut facts = TypedFacts::new();
283 facts.set("items", FactValue::Array(vec![
284 FactValue::String("item1".to_string()),
285 FactValue::String("item2".to_string()),
286 FactValue::String("item3".to_string()),
287 ]));
288 facts.set("tags", FactValue::Array(vec![
289 FactValue::String("electronics".to_string()),
290 FactValue::String("gadgets".to_string()),
291 ]));
292 facts
293 }
294
295 #[test]
296 fn test_collect_operation() {
297 let facts = create_test_facts_with_array();
298 let op = MultifieldOp::Collect;
299
300 let result = op.evaluate(&facts, "items", None);
301 assert!(result.is_some());
302
303 let values = result.unwrap();
304 assert_eq!(values.len(), 3);
305 assert_eq!(values[0], FactValue::String("item1".to_string()));
306 }
307
308 #[test]
309 fn test_contains_operation() {
310 let facts = create_test_facts_with_array();
311 let op = MultifieldOp::Contains;
312 let search = FactValue::String("electronics".to_string());
313
314 let result = op.evaluate(&facts, "tags", Some(&search));
315 assert!(result.is_some());
316
317 let values = result.unwrap();
318 assert_eq!(values.len(), 1);
319 assert_eq!(values[0], FactValue::Boolean(true));
320 }
321
322 #[test]
323 fn test_count_operation() {
324 let facts = create_test_facts_with_array();
325 let op = MultifieldOp::Count;
326
327 let result = op.evaluate(&facts, "items", None);
328 assert!(result.is_some());
329
330 let values = result.unwrap();
331 assert_eq!(values[0], FactValue::Integer(3));
332 }
333
334 #[test]
335 fn test_first_last_operations() {
336 let facts = create_test_facts_with_array();
337
338 let first = MultifieldOp::First.evaluate(&facts, "items", None).unwrap();
339 assert_eq!(first[0], FactValue::String("item1".to_string()));
340
341 let last = MultifieldOp::Last.evaluate(&facts, "items", None).unwrap();
342 assert_eq!(last[0], FactValue::String("item3".to_string()));
343 }
344
345 #[test]
346 fn test_index_operation() {
347 let facts = create_test_facts_with_array();
348 let op = MultifieldOp::Index(1);
349
350 let result = op.evaluate(&facts, "items", None).unwrap();
351 assert_eq!(result[0], FactValue::String("item2".to_string()));
352 }
353
354 #[test]
355 fn test_slice_operation() {
356 let facts = create_test_facts_with_array();
357 let op = MultifieldOp::Slice(0, 2);
358
359 let result = op.evaluate(&facts, "items", None).unwrap();
360 assert_eq!(result.len(), 2);
361 assert_eq!(result[0], FactValue::String("item1".to_string()));
362 assert_eq!(result[1], FactValue::String("item2".to_string()));
363 }
364
365 #[test]
366 fn test_is_empty_operation() {
367 let mut facts = TypedFacts::new();
368 facts.set("empty_array", FactValue::Array(Vec::new()));
369
370 let op = MultifieldOp::IsEmpty;
371 let result = op.evaluate(&facts, "empty_array", None).unwrap();
372 assert_eq!(result[0], FactValue::Boolean(true));
373 }
374
375 #[test]
376 fn test_parse_multifield_variable() {
377 assert_eq!(parse_multifield_variable("$?items"), Some("items".to_string()));
378 assert_eq!(parse_multifield_variable("$?all"), Some("all".to_string()));
379 assert_eq!(parse_multifield_variable("$single"), None);
380 assert_eq!(parse_multifield_variable("items"), None);
381 }
382
383 #[test]
384 fn test_is_multifield_variable() {
385 assert!(is_multifield_variable("$?items"));
386 assert!(is_multifield_variable("$?all"));
387 assert!(!is_multifield_variable("$single"));
388 assert!(!is_multifield_variable("items"));
389 }
390
391 #[test]
392 fn test_evaluate_multifield_pattern_with_binding() {
393 let facts = create_test_facts_with_array();
394 let bindings = HashMap::new();
395
396 let result = evaluate_multifield_pattern(
397 &facts,
398 "items",
399 &MultifieldOp::Collect,
400 Some("$?all_items"),
401 None,
402 &bindings,
403 );
404
405 assert!(result.is_some());
406 let new_bindings = result.unwrap();
407
408 assert!(new_bindings.contains_key("$?all_items"));
409 if let FactValue::Array(arr) = &new_bindings["$?all_items"] {
410 assert_eq!(arr.len(), 3);
411 } else {
412 panic!("Expected array binding");
413 }
414 }
415
416 #[test]
417 fn test_evaluate_multifield_pattern_contains() {
418 let facts = create_test_facts_with_array();
419 let bindings = HashMap::new();
420 let search = FactValue::String("electronics".to_string());
421
422 let result = evaluate_multifield_pattern(
423 &facts,
424 "tags",
425 &MultifieldOp::Contains,
426 None,
427 Some(&search),
428 &bindings,
429 );
430
431 assert!(result.is_some()); let search2 = FactValue::String("nonexistent".to_string());
435 let result2 = evaluate_multifield_pattern(
436 &facts,
437 "tags",
438 &MultifieldOp::Contains,
439 None,
440 Some(&search2),
441 &bindings,
442 );
443
444 assert!(result2.is_none()); }
446}