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 #[allow(clippy::inherent_to_string)]
180 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 trimmed.strip_prefix("$?").map(|s| s.to_string())
266}
267
268pub fn is_multifield_variable(input: &str) -> bool {
270 input.trim().starts_with("$?")
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 fn create_test_facts_with_array() -> TypedFacts {
278 let mut facts = TypedFacts::new();
279 facts.set(
280 "items",
281 FactValue::Array(vec![
282 FactValue::String("item1".to_string()),
283 FactValue::String("item2".to_string()),
284 FactValue::String("item3".to_string()),
285 ]),
286 );
287 facts.set(
288 "tags",
289 FactValue::Array(vec![
290 FactValue::String("electronics".to_string()),
291 FactValue::String("gadgets".to_string()),
292 ]),
293 );
294 facts
295 }
296
297 #[test]
298 fn test_collect_operation() {
299 let facts = create_test_facts_with_array();
300 let op = MultifieldOp::Collect;
301
302 let result = op.evaluate(&facts, "items", None);
303 assert!(result.is_some());
304
305 let values = result.unwrap();
306 assert_eq!(values.len(), 3);
307 assert_eq!(values[0], FactValue::String("item1".to_string()));
308 }
309
310 #[test]
311 fn test_contains_operation() {
312 let facts = create_test_facts_with_array();
313 let op = MultifieldOp::Contains;
314 let search = FactValue::String("electronics".to_string());
315
316 let result = op.evaluate(&facts, "tags", Some(&search));
317 assert!(result.is_some());
318
319 let values = result.unwrap();
320 assert_eq!(values.len(), 1);
321 assert_eq!(values[0], FactValue::Boolean(true));
322 }
323
324 #[test]
325 fn test_count_operation() {
326 let facts = create_test_facts_with_array();
327 let op = MultifieldOp::Count;
328
329 let result = op.evaluate(&facts, "items", None);
330 assert!(result.is_some());
331
332 let values = result.unwrap();
333 assert_eq!(values[0], FactValue::Integer(3));
334 }
335
336 #[test]
337 fn test_first_last_operations() {
338 let facts = create_test_facts_with_array();
339
340 let first = MultifieldOp::First.evaluate(&facts, "items", None).unwrap();
341 assert_eq!(first[0], FactValue::String("item1".to_string()));
342
343 let last = MultifieldOp::Last.evaluate(&facts, "items", None).unwrap();
344 assert_eq!(last[0], FactValue::String("item3".to_string()));
345 }
346
347 #[test]
348 fn test_index_operation() {
349 let facts = create_test_facts_with_array();
350 let op = MultifieldOp::Index(1);
351
352 let result = op.evaluate(&facts, "items", None).unwrap();
353 assert_eq!(result[0], FactValue::String("item2".to_string()));
354 }
355
356 #[test]
357 fn test_slice_operation() {
358 let facts = create_test_facts_with_array();
359 let op = MultifieldOp::Slice(0, 2);
360
361 let result = op.evaluate(&facts, "items", None).unwrap();
362 assert_eq!(result.len(), 2);
363 assert_eq!(result[0], FactValue::String("item1".to_string()));
364 assert_eq!(result[1], FactValue::String("item2".to_string()));
365 }
366
367 #[test]
368 fn test_is_empty_operation() {
369 let mut facts = TypedFacts::new();
370 facts.set("empty_array", FactValue::Array(Vec::new()));
371
372 let op = MultifieldOp::IsEmpty;
373 let result = op.evaluate(&facts, "empty_array", None).unwrap();
374 assert_eq!(result[0], FactValue::Boolean(true));
375 }
376
377 #[test]
378 fn test_parse_multifield_variable() {
379 assert_eq!(
380 parse_multifield_variable("$?items"),
381 Some("items".to_string())
382 );
383 assert_eq!(parse_multifield_variable("$?all"), Some("all".to_string()));
384 assert_eq!(parse_multifield_variable("$single"), None);
385 assert_eq!(parse_multifield_variable("items"), None);
386 }
387
388 #[test]
389 fn test_is_multifield_variable() {
390 assert!(is_multifield_variable("$?items"));
391 assert!(is_multifield_variable("$?all"));
392 assert!(!is_multifield_variable("$single"));
393 assert!(!is_multifield_variable("items"));
394 }
395
396 #[test]
397 fn test_evaluate_multifield_pattern_with_binding() {
398 let facts = create_test_facts_with_array();
399 let bindings = HashMap::new();
400
401 let result = evaluate_multifield_pattern(
402 &facts,
403 "items",
404 &MultifieldOp::Collect,
405 Some("$?all_items"),
406 None,
407 &bindings,
408 );
409
410 assert!(result.is_some());
411 let new_bindings = result.unwrap();
412
413 assert!(new_bindings.contains_key("$?all_items"));
414 if let FactValue::Array(arr) = &new_bindings["$?all_items"] {
415 assert_eq!(arr.len(), 3);
416 } else {
417 panic!("Expected array binding");
418 }
419 }
420
421 #[test]
422 fn test_evaluate_multifield_pattern_contains() {
423 let facts = create_test_facts_with_array();
424 let bindings = HashMap::new();
425 let search = FactValue::String("electronics".to_string());
426
427 let result = evaluate_multifield_pattern(
428 &facts,
429 "tags",
430 &MultifieldOp::Contains,
431 None,
432 Some(&search),
433 &bindings,
434 );
435
436 assert!(result.is_some()); let search2 = FactValue::String("nonexistent".to_string());
440 let result2 = evaluate_multifield_pattern(
441 &facts,
442 "tags",
443 &MultifieldOp::Contains,
444 None,
445 Some(&search2),
446 &bindings,
447 );
448
449 assert!(result2.is_none()); }
451}