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