1use crate::engine::module::{ExportItem, ExportList, ImportType, ItemType, ModuleManager};
2use crate::engine::rule::{Condition, ConditionGroup, Rule};
3use crate::errors::{Result, RuleEngineError};
4use crate::types::{ActionType, Operator, Value};
5use chrono::{DateTime, Utc};
6use rexile::Pattern;
7use std::collections::HashMap;
8use std::sync::OnceLock;
9
10#[cfg(feature = "streaming")]
12pub mod stream_syntax;
13
14static RULE_REGEX: OnceLock<Pattern> = OnceLock::new();
16static RULE_SPLIT_REGEX: OnceLock<Pattern> = OnceLock::new();
17static DEFMODULE_REGEX: OnceLock<Pattern> = OnceLock::new();
18static DEFMODULE_SPLIT_REGEX: OnceLock<Pattern> = OnceLock::new();
19static WHEN_THEN_REGEX: OnceLock<Pattern> = OnceLock::new();
20static SALIENCE_REGEX: OnceLock<Pattern> = OnceLock::new();
21static TEST_CONDITION_REGEX: OnceLock<Pattern> = OnceLock::new();
22static TYPED_TEST_CONDITION_REGEX: OnceLock<Pattern> = OnceLock::new();
23static FUNCTION_CALL_REGEX: OnceLock<Pattern> = OnceLock::new();
24static CONDITION_REGEX: OnceLock<Pattern> = OnceLock::new();
25static METHOD_CALL_REGEX: OnceLock<Pattern> = OnceLock::new();
26static FUNCTION_BINDING_REGEX: OnceLock<Pattern> = OnceLock::new();
27static MULTIFIELD_COLLECT_REGEX: OnceLock<Pattern> = OnceLock::new();
28static MULTIFIELD_COUNT_REGEX: OnceLock<Pattern> = OnceLock::new();
29static MULTIFIELD_FIRST_REGEX: OnceLock<Pattern> = OnceLock::new();
30static MULTIFIELD_LAST_REGEX: OnceLock<Pattern> = OnceLock::new();
31static MULTIFIELD_EMPTY_REGEX: OnceLock<Pattern> = OnceLock::new();
32static MULTIFIELD_NOT_EMPTY_REGEX: OnceLock<Pattern> = OnceLock::new();
33static SIMPLE_CONDITION_REGEX: OnceLock<Pattern> = OnceLock::new();
34
35fn rule_regex() -> &'static Pattern {
37 RULE_REGEX.get_or_init(|| {
38 Pattern::new(r#"rule\s+(?:"([^"]+)"|([a-zA-Z_]\w*))\s*([^{]*)\{(.+)\}"#)
39 .expect("Invalid rule regex pattern")
40 })
41}
42
43fn rule_split_regex() -> &'static Pattern {
44 RULE_SPLIT_REGEX.get_or_init(|| {
45 Pattern::new(r#"(?s)rule\s+(?:"[^"]+"|[a-zA-Z_]\w*).*?\}"#)
46 .expect("Invalid rule split regex pattern")
47 })
48}
49
50fn defmodule_regex() -> &'static Pattern {
51 DEFMODULE_REGEX.get_or_init(|| {
52 Pattern::new(r#"defmodule\s+([A-Z_]\w*)\s*\{([^}]*)\}"#)
53 .expect("Invalid defmodule regex pattern")
54 })
55}
56
57fn defmodule_split_regex() -> &'static Pattern {
58 DEFMODULE_SPLIT_REGEX.get_or_init(|| {
59 Pattern::new(r#"(?s)defmodule\s+[A-Z_]\w*\s*\{[^}]*\}"#)
60 .expect("Invalid defmodule split regex pattern")
61 })
62}
63
64fn when_then_regex() -> &'static Pattern {
65 WHEN_THEN_REGEX.get_or_init(|| {
66 Pattern::new(r"when\s+(.+?)\s+then\s+(.+)").expect("Invalid when-then regex pattern")
67 })
68}
69
70fn salience_regex() -> &'static Pattern {
71 SALIENCE_REGEX
72 .get_or_init(|| Pattern::new(r"salience\s+(\d+)").expect("Invalid salience regex pattern"))
73}
74
75fn test_condition_regex() -> &'static Pattern {
76 TEST_CONDITION_REGEX.get_or_init(|| {
77 Pattern::new(r#"^test\s*\(\s*([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*\)$"#)
78 .expect("Invalid test condition regex")
79 })
80}
81
82fn typed_test_condition_regex() -> &'static Pattern {
83 TYPED_TEST_CONDITION_REGEX.get_or_init(|| {
84 Pattern::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#)
85 .expect("Invalid typed test condition regex")
86 })
87}
88
89fn function_call_regex() -> &'static Pattern {
90 FUNCTION_CALL_REGEX.get_or_init(|| {
91 Pattern::new(r#"([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#)
92 .expect("Invalid function call regex")
93 })
94}
95
96fn condition_regex() -> &'static Pattern {
97 CONDITION_REGEX.get_or_init(|| {
98 Pattern::new(r#"([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*(?:\s*[+\-*/%]\s*[a-zA-Z0-9_\.]+)*)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#)
99 .expect("Invalid condition regex")
100 })
101}
102
103fn method_call_regex() -> &'static Pattern {
104 METHOD_CALL_REGEX.get_or_init(|| {
105 Pattern::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#).expect("Invalid method call regex")
106 })
107}
108
109fn function_binding_regex() -> &'static Pattern {
110 FUNCTION_BINDING_REGEX.get_or_init(|| {
111 Pattern::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#).expect("Invalid function binding regex")
112 })
113}
114
115fn multifield_collect_regex() -> &'static Pattern {
116 MULTIFIELD_COLLECT_REGEX.get_or_init(|| {
117 Pattern::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+(\$\?[a-zA-Z_]\w*)$"#)
118 .expect("Invalid multifield collect regex")
119 })
120}
121
122fn multifield_count_regex() -> &'static Pattern {
123 MULTIFIELD_COUNT_REGEX.get_or_init(|| {
124 Pattern::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+count\s*(>=|<=|==|!=|>|<)\s*(.+)$"#)
125 .expect("Invalid multifield count regex")
126 })
127}
128
129fn multifield_first_regex() -> &'static Pattern {
130 MULTIFIELD_FIRST_REGEX.get_or_init(|| {
131 Pattern::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+first(?:\s+(\$[a-zA-Z_]\w*))?$"#)
132 .expect("Invalid multifield first regex")
133 })
134}
135
136fn multifield_last_regex() -> &'static Pattern {
137 MULTIFIELD_LAST_REGEX.get_or_init(|| {
138 Pattern::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+last(?:\s+(\$[a-zA-Z_]\w*))?$"#)
139 .expect("Invalid multifield last regex")
140 })
141}
142
143fn multifield_empty_regex() -> &'static Pattern {
144 MULTIFIELD_EMPTY_REGEX.get_or_init(|| {
145 Pattern::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+empty$"#)
146 .expect("Invalid multifield empty regex")
147 })
148}
149
150fn multifield_not_empty_regex() -> &'static Pattern {
151 MULTIFIELD_NOT_EMPTY_REGEX.get_or_init(|| {
152 Pattern::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+not_empty$"#)
153 .expect("Invalid multifield not_empty regex")
154 })
155}
156
157fn simple_condition_regex() -> &'static Pattern {
158 SIMPLE_CONDITION_REGEX.get_or_init(|| {
159 Pattern::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#).expect("Invalid simple condition regex")
160 })
161}
162
163pub struct GRLParser;
166
167#[derive(Debug, Default)]
169struct RuleAttributes {
170 pub no_loop: bool,
171 pub lock_on_active: bool,
172 pub agenda_group: Option<String>,
173 pub activation_group: Option<String>,
174 pub date_effective: Option<DateTime<Utc>>,
175 pub date_expires: Option<DateTime<Utc>>,
176}
177
178#[derive(Debug, Clone)]
180pub struct ParsedGRL {
181 pub rules: Vec<Rule>,
183 pub module_manager: ModuleManager,
185 pub rule_modules: HashMap<String, String>,
187}
188
189impl Default for ParsedGRL {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195impl ParsedGRL {
196 pub fn new() -> Self {
197 Self {
198 rules: Vec::new(),
199 module_manager: ModuleManager::new(),
200 rule_modules: HashMap::new(),
201 }
202 }
203}
204
205impl GRLParser {
206 pub fn parse_rule(grl_text: &str) -> Result<Rule> {
219 let mut parser = GRLParser;
220 parser.parse_single_rule(grl_text)
221 }
222
223 pub fn parse_rules(grl_text: &str) -> Result<Vec<Rule>> {
225 let mut parser = GRLParser;
226 parser.parse_multiple_rules(grl_text)
227 }
228
229 pub fn parse_with_modules(grl_text: &str) -> Result<ParsedGRL> {
247 let mut parser = GRLParser;
248 parser.parse_grl_with_modules(grl_text)
249 }
250
251 fn parse_grl_with_modules(&mut self, grl_text: &str) -> Result<ParsedGRL> {
252 let mut result = ParsedGRL::new();
253
254 for module_match in defmodule_split_regex().find_iter(grl_text) {
256 let module_def = module_match.as_str();
257 self.parse_and_register_module(module_def, &mut result.module_manager)?;
258 }
259
260 let rules_text = defmodule_split_regex().replace_all(grl_text, "");
262
263 let rules = self.parse_multiple_rules(&rules_text)?;
265
266 for rule in rules {
268 let module_name = self.extract_module_from_context(grl_text, &rule.name);
269 result
270 .rule_modules
271 .insert(rule.name.clone(), module_name.clone());
272
273 if let Ok(module) = result.module_manager.get_module_mut(&module_name) {
275 module.add_rule(&rule.name);
276 }
277
278 result.rules.push(rule);
279 }
280
281 Ok(result)
282 }
283
284 fn parse_and_register_module(
285 &self,
286 module_def: &str,
287 manager: &mut ModuleManager,
288 ) -> Result<()> {
289 if let Some(captures) = defmodule_regex().captures(module_def) {
291 let module_name = captures.get(1).unwrap().to_string();
292 let module_body = captures.get(2).unwrap();
293
294 let _ = manager.create_module(&module_name);
296 let module = manager.get_module_mut(&module_name)?;
297
298 if let Some(export_type) = self.extract_directive(module_body, "export:") {
300 let exports = if export_type.trim() == "all" {
301 ExportList::All
302 } else if export_type.trim() == "none" {
303 ExportList::None
304 } else {
305 ExportList::Specific(vec![ExportItem {
307 item_type: ItemType::All,
308 pattern: export_type.trim().to_string(),
309 }])
310 };
311 module.set_exports(exports);
312 }
313
314 let import_lines: Vec<&str> = module_body
316 .lines()
317 .filter(|line| line.trim().starts_with("import:"))
318 .collect();
319
320 for import_line in import_lines {
321 if let Some(import_spec) = self.extract_directive(import_line, "import:") {
322 self.parse_import_spec(&module_name, &import_spec, manager)?;
324 }
325 }
326 }
327
328 Ok(())
329 }
330
331 fn extract_directive(&self, text: &str, directive: &str) -> Option<String> {
332 if let Some(pos) = text.find(directive) {
333 let after_directive = &text[pos + directive.len()..];
334
335 let end = after_directive
337 .find("import:")
338 .or_else(|| after_directive.find("export:"))
339 .unwrap_or(after_directive.len());
340
341 Some(after_directive[..end].trim().to_string())
342 } else {
343 None
344 }
345 }
346
347 fn parse_import_spec(
348 &self,
349 importing_module: &str,
350 spec: &str,
351 manager: &mut ModuleManager,
352 ) -> Result<()> {
353 let parts: Vec<&str> = spec.splitn(2, '(').collect();
355 if parts.is_empty() {
356 return Ok(());
357 }
358
359 let source_module = parts[0].trim().to_string();
360 let rest = if parts.len() > 1 { parts[1] } else { "" };
361
362 if rest.contains("rules") {
364 manager.import_from(importing_module, &source_module, ImportType::AllRules, "*")?;
365 }
366
367 if rest.contains("templates") {
368 manager.import_from(
369 importing_module,
370 &source_module,
371 ImportType::AllTemplates,
372 "*",
373 )?;
374 }
375
376 Ok(())
377 }
378
379 fn extract_module_from_context(&self, grl_text: &str, rule_name: &str) -> String {
380 if let Some(rule_pos) = grl_text
382 .find(&format!("rule \"{}\"", rule_name))
383 .or_else(|| grl_text.find(&format!("rule {}", rule_name)))
384 {
385 let before = &grl_text[..rule_pos];
387 if let Some(module_pos) = before.rfind(";; MODULE:") {
388 let after_module_marker = &before[module_pos + 10..];
389 if let Some(end_of_line) = after_module_marker.find('\n') {
390 let module_line = &after_module_marker[..end_of_line].trim();
391 if let Some(first_word) = module_line.split_whitespace().next() {
393 return first_word.to_string();
394 }
395 }
396 }
397 }
398
399 "MAIN".to_string()
401 }
402
403 fn parse_single_rule(&mut self, grl_text: &str) -> Result<Rule> {
404 let cleaned = self.clean_text(grl_text);
405
406 let captures =
408 rule_regex()
409 .captures(&cleaned)
410 .ok_or_else(|| RuleEngineError::ParseError {
411 message: format!("Invalid GRL rule format. Input: {}", cleaned),
412 })?;
413
414 let rule_name = if let Some(quoted_name) = captures.get(1) {
416 quoted_name.to_string()
417 } else if let Some(unquoted_name) = captures.get(2) {
418 unquoted_name.to_string()
419 } else {
420 return Err(RuleEngineError::ParseError {
421 message: "Could not extract rule name".to_string(),
422 });
423 };
424
425 let attributes_section = captures.get(3).unwrap_or("");
427
428 let rule_body = captures.get(4).unwrap();
430
431 let salience = self.extract_salience(attributes_section)?;
433
434 let when_then_captures =
436 when_then_regex()
437 .captures(rule_body)
438 .ok_or_else(|| RuleEngineError::ParseError {
439 message: "Missing when or then clause".to_string(),
440 })?;
441
442 let when_clause = when_then_captures.get(1).unwrap().trim();
443 let then_clause = when_then_captures.get(2).unwrap().trim();
444
445 let conditions = self.parse_when_clause(when_clause)?;
447 let actions = self.parse_then_clause(then_clause)?;
448
449 let attributes = self.parse_rule_attributes(attributes_section)?;
451
452 let mut rule = Rule::new(rule_name, conditions, actions);
454 rule = rule.with_priority(salience);
455
456 if attributes.no_loop {
458 rule = rule.with_no_loop(true);
459 }
460 if attributes.lock_on_active {
461 rule = rule.with_lock_on_active(true);
462 }
463 if let Some(agenda_group) = attributes.agenda_group {
464 rule = rule.with_agenda_group(agenda_group);
465 }
466 if let Some(activation_group) = attributes.activation_group {
467 rule = rule.with_activation_group(activation_group);
468 }
469 if let Some(date_effective) = attributes.date_effective {
470 rule = rule.with_date_effective(date_effective);
471 }
472 if let Some(date_expires) = attributes.date_expires {
473 rule = rule.with_date_expires(date_expires);
474 }
475
476 Ok(rule)
477 }
478
479 fn parse_multiple_rules(&mut self, grl_text: &str) -> Result<Vec<Rule>> {
480 let mut rules = Vec::new();
483
484 for rule_match in rule_split_regex().find_iter(grl_text) {
485 let rule_text = rule_match.as_str();
486 let rule = self.parse_single_rule(rule_text)?;
487 rules.push(rule);
488 }
489
490 Ok(rules)
491 }
492
493 fn parse_rule_attributes(&self, rule_header: &str) -> Result<RuleAttributes> {
495 let mut attributes = RuleAttributes::default();
496
497 let mut attrs_section = rule_header.to_string();
501
502 let quoted_regex = Pattern::new(r#""[^"]*""#).map_err(|e| RuleEngineError::ParseError {
504 message: format!("Invalid quoted string regex: {}", e),
505 })?;
506 attrs_section = quoted_regex.replace_all(&attrs_section, "").to_string();
507
508 if let Some(rule_pos) = attrs_section.find("rule") {
510 let after_rule = &attrs_section[rule_pos + 4..];
512 if let Some(first_keyword) = after_rule
513 .find("salience")
514 .or_else(|| after_rule.find("no-loop"))
515 .or_else(|| after_rule.find("lock-on-active"))
516 .or_else(|| after_rule.find("agenda-group"))
517 .or_else(|| after_rule.find("activation-group"))
518 .or_else(|| after_rule.find("date-effective"))
519 .or_else(|| after_rule.find("date-expires"))
520 {
521 attrs_section = after_rule[first_keyword..].to_string();
522 }
523 }
524
525 let no_loop_regex =
527 Pattern::new(r"\bno-loop\b").map_err(|e| RuleEngineError::ParseError {
528 message: format!("Invalid no-loop regex: {}", e),
529 })?;
530 let lock_on_active_regex =
531 Pattern::new(r"\block-on-active\b").map_err(|e| RuleEngineError::ParseError {
532 message: format!("Invalid lock-on-active regex: {}", e),
533 })?;
534
535 if no_loop_regex.is_match(&attrs_section) {
536 attributes.no_loop = true;
537 }
538 if lock_on_active_regex.is_match(&attrs_section) {
539 attributes.lock_on_active = true;
540 }
541
542 if let Some(agenda_group) = self.extract_quoted_attribute(rule_header, "agenda-group")? {
544 attributes.agenda_group = Some(agenda_group);
545 }
546
547 if let Some(activation_group) =
549 self.extract_quoted_attribute(rule_header, "activation-group")?
550 {
551 attributes.activation_group = Some(activation_group);
552 }
553
554 if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-effective")? {
556 attributes.date_effective = Some(self.parse_date_string(&date_str)?);
557 }
558
559 if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-expires")? {
561 attributes.date_expires = Some(self.parse_date_string(&date_str)?);
562 }
563
564 Ok(attributes)
565 }
566
567 fn extract_quoted_attribute(&self, header: &str, attribute: &str) -> Result<Option<String>> {
569 let pattern = format!(r#"{}\s+"([^"]+)""#, attribute);
570 let regex = Pattern::new(&pattern).map_err(|e| RuleEngineError::ParseError {
571 message: format!("Invalid attribute regex for {}: {}", attribute, e),
572 })?;
573
574 if let Some(captures) = regex.captures(header) {
575 if let Some(value) = captures.get(1) {
576 return Ok(Some(value.to_string()));
577 }
578 }
579
580 Ok(None)
581 }
582
583 fn parse_date_string(&self, date_str: &str) -> Result<DateTime<Utc>> {
585 if let Ok(date) = DateTime::parse_from_rfc3339(date_str) {
587 return Ok(date.with_timezone(&Utc));
588 }
589
590 let formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%d-%b-%Y", "%d-%m-%Y"];
592
593 for format in &formats {
594 if let Ok(naive_date) = chrono::NaiveDateTime::parse_from_str(date_str, format) {
595 return Ok(naive_date.and_utc());
596 }
597 if let Ok(naive_date) = chrono::NaiveDate::parse_from_str(date_str, format) {
598 return Ok(naive_date.and_hms_opt(0, 0, 0).unwrap().and_utc());
599 }
600 }
601
602 Err(RuleEngineError::ParseError {
603 message: format!("Unable to parse date: {}", date_str),
604 })
605 }
606
607 fn extract_salience(&self, attributes_section: &str) -> Result<i32> {
609 if let Some(captures) = salience_regex().captures(attributes_section) {
610 if let Some(salience_match) = captures.get(1) {
611 return salience_match
612 .parse::<i32>()
613 .map_err(|e| RuleEngineError::ParseError {
614 message: format!("Invalid salience value: {}", e),
615 });
616 }
617 }
618
619 Ok(0) }
621
622 fn clean_text(&self, text: &str) -> String {
623 text.lines()
624 .map(|line| line.trim())
625 .filter(|line| !line.is_empty() && !line.starts_with("//"))
626 .collect::<Vec<_>>()
627 .join(" ")
628 }
629
630 fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
631 let trimmed = when_clause.trim();
633
634 let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
636 let inner = &trimmed[1..trimmed.len() - 1];
638 if self.is_balanced_parentheses(inner) {
639 inner
640 } else {
641 trimmed
642 }
643 } else {
644 trimmed
645 };
646
647 if let Some(parts) = self.split_logical_operator(clause, "||") {
649 return self.parse_or_parts(parts);
650 }
651
652 if let Some(parts) = self.split_logical_operator(clause, "&&") {
654 return self.parse_and_parts(parts);
655 }
656
657 if clause.trim_start().starts_with("!") {
659 return self.parse_not_condition(clause);
660 }
661
662 if clause.trim_start().starts_with("exists(") {
664 return self.parse_exists_condition(clause);
665 }
666
667 if clause.trim_start().starts_with("forall(") {
669 return self.parse_forall_condition(clause);
670 }
671
672 if clause.trim_start().starts_with("accumulate(") {
674 return self.parse_accumulate_condition(clause);
675 }
676
677 self.parse_single_condition(clause)
679 }
680
681 fn is_balanced_parentheses(&self, text: &str) -> bool {
682 let mut count = 0;
683 for ch in text.chars() {
684 match ch {
685 '(' => count += 1,
686 ')' => {
687 count -= 1;
688 if count < 0 {
689 return false;
690 }
691 }
692 _ => {}
693 }
694 }
695 count == 0
696 }
697
698 fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
699 let mut parts = Vec::new();
700 let mut current_part = String::new();
701 let mut paren_count = 0;
702 let mut chars = clause.chars().peekable();
703
704 while let Some(ch) = chars.next() {
705 match ch {
706 '(' => {
707 paren_count += 1;
708 current_part.push(ch);
709 }
710 ')' => {
711 paren_count -= 1;
712 current_part.push(ch);
713 }
714 '&' if operator == "&&" && paren_count == 0 => {
715 if chars.peek() == Some(&'&') {
716 chars.next(); parts.push(current_part.trim().to_string());
718 current_part.clear();
719 } else {
720 current_part.push(ch);
721 }
722 }
723 '|' if operator == "||" && paren_count == 0 => {
724 if chars.peek() == Some(&'|') {
725 chars.next(); parts.push(current_part.trim().to_string());
727 current_part.clear();
728 } else {
729 current_part.push(ch);
730 }
731 }
732 _ => {
733 current_part.push(ch);
734 }
735 }
736 }
737
738 if !current_part.trim().is_empty() {
739 parts.push(current_part.trim().to_string());
740 }
741
742 if parts.len() > 1 {
743 Some(parts)
744 } else {
745 None
746 }
747 }
748
749 fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
750 let mut conditions = Vec::new();
751 for part in parts {
752 let condition = self.parse_when_clause(&part)?;
753 conditions.push(condition);
754 }
755
756 if conditions.is_empty() {
757 return Err(RuleEngineError::ParseError {
758 message: "No conditions found in OR".to_string(),
759 });
760 }
761
762 let mut iter = conditions.into_iter();
763 let mut result = iter.next().unwrap();
764 for condition in iter {
765 result = ConditionGroup::or(result, condition);
766 }
767
768 Ok(result)
769 }
770
771 fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
772 let mut conditions = Vec::new();
773 for part in parts {
774 let condition = self.parse_when_clause(&part)?;
775 conditions.push(condition);
776 }
777
778 if conditions.is_empty() {
779 return Err(RuleEngineError::ParseError {
780 message: "No conditions found in AND".to_string(),
781 });
782 }
783
784 let mut iter = conditions.into_iter();
785 let mut result = iter.next().unwrap();
786 for condition in iter {
787 result = ConditionGroup::and(result, condition);
788 }
789
790 Ok(result)
791 }
792
793 fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
794 let inner_clause = clause.strip_prefix("!").unwrap().trim();
795 let inner_condition = self.parse_when_clause(inner_clause)?;
796 Ok(ConditionGroup::not(inner_condition))
797 }
798
799 fn parse_exists_condition(&self, clause: &str) -> Result<ConditionGroup> {
800 let clause = clause.trim_start();
801 if !clause.starts_with("exists(") || !clause.ends_with(")") {
802 return Err(RuleEngineError::ParseError {
803 message: "Invalid exists syntax. Expected: exists(condition)".to_string(),
804 });
805 }
806
807 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
810 Ok(ConditionGroup::exists(inner_condition))
811 }
812
813 fn parse_forall_condition(&self, clause: &str) -> Result<ConditionGroup> {
814 let clause = clause.trim_start();
815 if !clause.starts_with("forall(") || !clause.ends_with(")") {
816 return Err(RuleEngineError::ParseError {
817 message: "Invalid forall syntax. Expected: forall(condition)".to_string(),
818 });
819 }
820
821 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
824 Ok(ConditionGroup::forall(inner_condition))
825 }
826
827 fn parse_accumulate_condition(&self, clause: &str) -> Result<ConditionGroup> {
828 let clause = clause.trim_start();
829 if !clause.starts_with("accumulate(") || !clause.ends_with(")") {
830 return Err(RuleEngineError::ParseError {
831 message: "Invalid accumulate syntax. Expected: accumulate(pattern, function)"
832 .to_string(),
833 });
834 }
835
836 let inner = &clause[11..clause.len() - 1]; let parts = self.split_accumulate_parts(inner)?;
841
842 if parts.len() != 2 {
843 return Err(RuleEngineError::ParseError {
844 message: format!(
845 "Invalid accumulate syntax. Expected 2 parts (pattern, function), got {}",
846 parts.len()
847 ),
848 });
849 }
850
851 let pattern_part = parts[0].trim();
852 let function_part = parts[1].trim();
853
854 let (source_pattern, extract_field, source_conditions) =
856 self.parse_accumulate_pattern(pattern_part)?;
857
858 let (function, function_arg) = self.parse_accumulate_function(function_part)?;
860
861 let result_var = "$result".to_string();
865
866 Ok(ConditionGroup::accumulate(
867 result_var,
868 source_pattern,
869 extract_field,
870 source_conditions,
871 function,
872 function_arg,
873 ))
874 }
875
876 fn split_accumulate_parts(&self, content: &str) -> Result<Vec<String>> {
877 let mut parts = Vec::new();
878 let mut current = String::new();
879 let mut paren_depth = 0;
880
881 for ch in content.chars() {
882 match ch {
883 '(' => {
884 paren_depth += 1;
885 current.push(ch);
886 }
887 ')' => {
888 paren_depth -= 1;
889 current.push(ch);
890 }
891 ',' if paren_depth == 0 => {
892 parts.push(current.trim().to_string());
893 current.clear();
894 }
895 _ => {
896 current.push(ch);
897 }
898 }
899 }
900
901 if !current.trim().is_empty() {
902 parts.push(current.trim().to_string());
903 }
904
905 Ok(parts)
906 }
907
908 fn parse_accumulate_pattern(&self, pattern: &str) -> Result<(String, String, Vec<String>)> {
909 let pattern = pattern.trim();
916
917 let paren_pos = pattern
919 .find('(')
920 .ok_or_else(|| RuleEngineError::ParseError {
921 message: format!("Invalid accumulate pattern: missing '(' in '{}'", pattern),
922 })?;
923
924 let source_pattern = pattern[..paren_pos].trim().to_string();
925
926 if !pattern.ends_with(')') {
928 return Err(RuleEngineError::ParseError {
929 message: format!("Invalid accumulate pattern: missing ')' in '{}'", pattern),
930 });
931 }
932
933 let inner = &pattern[paren_pos + 1..pattern.len() - 1];
934
935 let parts = self.split_pattern_parts(inner)?;
937
938 let mut extract_field = String::new();
939 let mut source_conditions = Vec::new();
940
941 for part in parts {
942 let part = part.trim();
943
944 if part.contains(':') && part.starts_with('$') {
946 let colon_pos = part.find(':').unwrap();
947 let _var_name = part[..colon_pos].trim();
948 let field_name = part[colon_pos + 1..].trim();
949 extract_field = field_name.to_string();
950 } else if part.contains("==")
951 || part.contains("!=")
952 || part.contains(">=")
953 || part.contains("<=")
954 || part.contains('>')
955 || part.contains('<')
956 {
957 source_conditions.push(part.to_string());
959 }
960 }
961
962 Ok((source_pattern, extract_field, source_conditions))
963 }
964
965 fn split_pattern_parts(&self, content: &str) -> Result<Vec<String>> {
966 let mut parts = Vec::new();
967 let mut current = String::new();
968 let mut paren_depth = 0;
969 let mut in_quotes = false;
970 let mut quote_char = ' ';
971
972 for ch in content.chars() {
973 match ch {
974 '"' | '\'' if !in_quotes => {
975 in_quotes = true;
976 quote_char = ch;
977 current.push(ch);
978 }
979 '"' | '\'' if in_quotes && ch == quote_char => {
980 in_quotes = false;
981 current.push(ch);
982 }
983 '(' if !in_quotes => {
984 paren_depth += 1;
985 current.push(ch);
986 }
987 ')' if !in_quotes => {
988 paren_depth -= 1;
989 current.push(ch);
990 }
991 ',' if !in_quotes && paren_depth == 0 => {
992 parts.push(current.trim().to_string());
993 current.clear();
994 }
995 _ => {
996 current.push(ch);
997 }
998 }
999 }
1000
1001 if !current.trim().is_empty() {
1002 parts.push(current.trim().to_string());
1003 }
1004
1005 Ok(parts)
1006 }
1007
1008 fn parse_accumulate_function(&self, function_str: &str) -> Result<(String, String)> {
1009 let function_str = function_str.trim();
1012
1013 let paren_pos = function_str
1014 .find('(')
1015 .ok_or_else(|| RuleEngineError::ParseError {
1016 message: format!(
1017 "Invalid accumulate function: missing '(' in '{}'",
1018 function_str
1019 ),
1020 })?;
1021
1022 let function_name = function_str[..paren_pos].trim().to_string();
1023
1024 if !function_str.ends_with(')') {
1025 return Err(RuleEngineError::ParseError {
1026 message: format!(
1027 "Invalid accumulate function: missing ')' in '{}'",
1028 function_str
1029 ),
1030 });
1031 }
1032
1033 let args = &function_str[paren_pos + 1..function_str.len() - 1];
1034 let function_arg = args.trim().to_string();
1035
1036 Ok((function_name, function_arg))
1037 }
1038
1039 fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
1040 let trimmed_clause = clause.trim();
1042 let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
1043 trimmed_clause[1..trimmed_clause.len() - 1].trim()
1044 } else {
1045 trimmed_clause
1046 };
1047
1048 #[cfg(feature = "streaming")]
1051 if clause_to_parse.contains("from stream(") {
1052 return self.parse_stream_pattern_condition(clause_to_parse);
1053 }
1054
1055 if let Some(captures) = multifield_collect_regex().captures(clause_to_parse) {
1062 let field = captures.get(1).unwrap().to_string();
1063 let variable = captures.get(2).unwrap().to_string();
1064
1065 let condition = Condition::with_multifield_collect(field, variable);
1068 return Ok(ConditionGroup::single(condition));
1069 }
1070
1071 if let Some(captures) = multifield_count_regex().captures(clause_to_parse) {
1078 let field = captures.get(1).unwrap().to_string();
1079 let operator_str = captures.get(2).unwrap();
1080 let value_str = captures.get(3).unwrap().trim();
1081
1082 let operator = Operator::from_str(operator_str).ok_or_else(|| {
1083 RuleEngineError::InvalidOperator {
1084 operator: operator_str.to_string(),
1085 }
1086 })?;
1087
1088 let value = self.parse_value(value_str)?;
1089
1090 let condition = Condition::with_multifield_count(field, operator, value);
1091 return Ok(ConditionGroup::single(condition));
1092 }
1093
1094 if let Some(captures) = multifield_first_regex().captures(clause_to_parse) {
1097 let field = captures.get(1).unwrap().to_string();
1098 let variable = captures.get(2).map(|m| m.to_string());
1099
1100 let condition = Condition::with_multifield_first(field, variable);
1101 return Ok(ConditionGroup::single(condition));
1102 }
1103
1104 if let Some(captures) = multifield_last_regex().captures(clause_to_parse) {
1107 let field = captures.get(1).unwrap().to_string();
1108 let variable = captures.get(2).map(|m| m.to_string());
1109
1110 let condition = Condition::with_multifield_last(field, variable);
1111 return Ok(ConditionGroup::single(condition));
1112 }
1113
1114 if let Some(captures) = multifield_empty_regex().captures(clause_to_parse) {
1117 let field = captures.get(1).unwrap().to_string();
1118
1119 let condition = Condition::with_multifield_empty(field);
1120 return Ok(ConditionGroup::single(condition));
1121 }
1122
1123 if let Some(captures) = multifield_not_empty_regex().captures(clause_to_parse) {
1126 let field = captures.get(1).unwrap().to_string();
1127
1128 let condition = Condition::with_multifield_not_empty(field);
1129 return Ok(ConditionGroup::single(condition));
1130 }
1131
1132 if let Some(captures) = test_condition_regex().captures(clause_to_parse) {
1137 let function_name = captures.get(1).unwrap().to_string();
1138 let args_str = captures.get(2).unwrap();
1139
1140 let args: Vec<String> = if args_str.trim().is_empty() {
1142 Vec::new()
1143 } else {
1144 args_str
1145 .split(',')
1146 .map(|arg| arg.trim().to_string())
1147 .collect()
1148 };
1149
1150 let condition = Condition::with_test(function_name, args);
1151 return Ok(ConditionGroup::single(condition));
1152 }
1153
1154 if let Some(captures) = typed_test_condition_regex().captures(clause_to_parse) {
1156 let _object_name = captures.get(1).unwrap();
1157 let _object_type = captures.get(2).unwrap();
1158 let conditions_str = captures.get(3).unwrap();
1159
1160 return self.parse_conditions_within_object(conditions_str);
1162 }
1163
1164 if let Some(captures) = function_call_regex().captures(clause_to_parse) {
1166 let function_name = captures.get(1).unwrap().to_string();
1167 let args_str = captures.get(2).unwrap();
1168 let operator_str = captures.get(3).unwrap();
1169 let value_str = captures.get(4).unwrap().trim();
1170
1171 let args: Vec<String> = if args_str.trim().is_empty() {
1173 Vec::new()
1174 } else {
1175 args_str
1176 .split(',')
1177 .map(|arg| arg.trim().to_string())
1178 .collect()
1179 };
1180
1181 let operator = Operator::from_str(operator_str).ok_or_else(|| {
1182 RuleEngineError::InvalidOperator {
1183 operator: operator_str.to_string(),
1184 }
1185 })?;
1186
1187 let value = self.parse_value(value_str)?;
1188
1189 let condition = Condition::with_function(function_name, args, operator, value);
1190 return Ok(ConditionGroup::single(condition));
1191 }
1192
1193 let captures = condition_regex().captures(clause_to_parse).ok_or_else(|| {
1197 RuleEngineError::ParseError {
1198 message: format!("Invalid condition format: {}", clause_to_parse),
1199 }
1200 })?;
1201
1202 let left_side = captures.get(1).unwrap().trim().to_string();
1203 let operator_str = captures.get(2).unwrap();
1204 let value_str = captures.get(3).unwrap().trim();
1205
1206 let operator =
1207 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1208 operator: operator_str.to_string(),
1209 })?;
1210
1211 let value = self.parse_value(value_str)?;
1212
1213 if left_side.contains('+')
1215 || left_side.contains('-')
1216 || left_side.contains('*')
1217 || left_side.contains('/')
1218 || left_side.contains('%')
1219 {
1220 let test_expr = format!("{} {} {}", left_side, operator_str, value_str);
1223 let condition = Condition::with_test(test_expr, vec![]);
1224 Ok(ConditionGroup::single(condition))
1225 } else {
1226 let condition = Condition::new(left_side, operator, value);
1228 Ok(ConditionGroup::single(condition))
1229 }
1230 }
1231
1232 fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
1233 let parts: Vec<&str> = conditions_str.split("&&").collect();
1235
1236 let mut conditions = Vec::new();
1237 for part in parts {
1238 let trimmed = part.trim();
1239 let condition = self.parse_simple_condition(trimmed)?;
1240 conditions.push(condition);
1241 }
1242
1243 if conditions.is_empty() {
1245 return Err(RuleEngineError::ParseError {
1246 message: "No conditions found".to_string(),
1247 });
1248 }
1249
1250 let mut iter = conditions.into_iter();
1251 let mut result = iter.next().unwrap();
1252 for condition in iter {
1253 result = ConditionGroup::and(result, condition);
1254 }
1255
1256 Ok(result)
1257 }
1258
1259 fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
1260 let captures = simple_condition_regex().captures(clause).ok_or_else(|| {
1262 RuleEngineError::ParseError {
1263 message: format!("Invalid simple condition format: {}", clause),
1264 }
1265 })?;
1266
1267 let field = captures.get(1).unwrap().to_string();
1268 let operator_str = captures.get(2).unwrap();
1269 let value_str = captures.get(3).unwrap().trim();
1270
1271 let operator =
1272 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1273 operator: operator_str.to_string(),
1274 })?;
1275
1276 let value = self.parse_value(value_str)?;
1277
1278 let condition = Condition::new(field, operator, value);
1279 Ok(ConditionGroup::single(condition))
1280 }
1281
1282 fn parse_value(&self, value_str: &str) -> Result<Value> {
1283 let trimmed = value_str.trim();
1284
1285 if (trimmed.starts_with('"') && trimmed.ends_with('"'))
1287 || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
1288 {
1289 let unquoted = &trimmed[1..trimmed.len() - 1];
1290 return Ok(Value::String(unquoted.to_string()));
1291 }
1292
1293 if trimmed.eq_ignore_ascii_case("true") {
1295 return Ok(Value::Boolean(true));
1296 }
1297 if trimmed.eq_ignore_ascii_case("false") {
1298 return Ok(Value::Boolean(false));
1299 }
1300
1301 if trimmed.eq_ignore_ascii_case("null") {
1303 return Ok(Value::Null);
1304 }
1305
1306 if let Ok(int_val) = trimmed.parse::<i64>() {
1308 return Ok(Value::Integer(int_val));
1309 }
1310
1311 if let Ok(float_val) = trimmed.parse::<f64>() {
1312 return Ok(Value::Number(float_val));
1313 }
1314
1315 if self.is_expression(trimmed) {
1318 return Ok(Value::Expression(trimmed.to_string()));
1319 }
1320
1321 if trimmed.contains('.') {
1323 return Ok(Value::String(trimmed.to_string()));
1324 }
1325
1326 if self.is_identifier(trimmed) {
1330 return Ok(Value::Expression(trimmed.to_string()));
1331 }
1332
1333 Ok(Value::String(trimmed.to_string()))
1335 }
1336
1337 fn is_identifier(&self, s: &str) -> bool {
1340 if s.is_empty() {
1341 return false;
1342 }
1343
1344 let first_char = s.chars().next().unwrap();
1346 if !first_char.is_alphabetic() && first_char != '_' {
1347 return false;
1348 }
1349
1350 s.chars().all(|c| c.is_alphanumeric() || c == '_')
1352 }
1353
1354 fn is_expression(&self, s: &str) -> bool {
1356 let has_operator = s.contains('+')
1358 || s.contains('-')
1359 || s.contains('*')
1360 || s.contains('/')
1361 || s.contains('%');
1362
1363 let has_field_ref = s.contains('.');
1365
1366 let has_spaces = s.contains(' ');
1368
1369 has_operator && (has_field_ref || has_spaces)
1371 }
1372
1373 fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
1374 let statements: Vec<&str> = then_clause
1375 .split(';')
1376 .map(|s| s.trim())
1377 .filter(|s| !s.is_empty())
1378 .collect();
1379
1380 let mut actions = Vec::new();
1381
1382 for statement in statements {
1383 let action = self.parse_action_statement(statement)?;
1384 actions.push(action);
1385 }
1386
1387 Ok(actions)
1388 }
1389
1390 fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
1391 let trimmed = statement.trim();
1392
1393 if let Some(captures) = method_call_regex().captures(trimmed) {
1395 let object = captures.get(1).unwrap().to_string();
1396 let method = captures.get(2).unwrap().to_string();
1397 let args_str = captures.get(3).unwrap();
1398
1399 let args = if args_str.trim().is_empty() {
1400 Vec::new()
1401 } else {
1402 self.parse_method_args(args_str)?
1403 };
1404
1405 return Ok(ActionType::MethodCall {
1406 object,
1407 method,
1408 args,
1409 });
1410 }
1411
1412 if let Some(plus_eq_pos) = trimmed.find("+=") {
1414 let field = trimmed[..plus_eq_pos].trim().to_string();
1416 let value_str = trimmed[plus_eq_pos + 2..].trim();
1417 let value = self.parse_value(value_str)?;
1418
1419 return Ok(ActionType::Append { field, value });
1420 }
1421
1422 if let Some(eq_pos) = trimmed.find('=') {
1424 let field = trimmed[..eq_pos].trim().to_string();
1425 let value_str = trimmed[eq_pos + 1..].trim();
1426 let value = self.parse_value(value_str)?;
1427
1428 return Ok(ActionType::Set { field, value });
1429 }
1430
1431 if let Some(captures) = function_binding_regex().captures(trimmed) {
1433 let function_name = captures.get(1).unwrap();
1434 let args_str = captures.get(2).unwrap_or("");
1435
1436 match function_name.to_lowercase().as_str() {
1437 "retract" => {
1438 let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
1440 stripped.to_string()
1441 } else {
1442 args_str.to_string()
1443 };
1444 Ok(ActionType::Retract {
1445 object: object_name,
1446 })
1447 }
1448 "log" => {
1449 let message = if args_str.is_empty() {
1450 "Log message".to_string()
1451 } else {
1452 let value = self.parse_value(args_str.trim())?;
1453 value.to_string()
1454 };
1455 Ok(ActionType::Log { message })
1456 }
1457 "activateagendagroup" | "activate_agenda_group" => {
1458 let agenda_group = if args_str.is_empty() {
1459 return Err(RuleEngineError::ParseError {
1460 message: "ActivateAgendaGroup requires agenda group name".to_string(),
1461 });
1462 } else {
1463 let value = self.parse_value(args_str.trim())?;
1464 match value {
1465 Value::String(s) => s,
1466 _ => value.to_string(),
1467 }
1468 };
1469 Ok(ActionType::ActivateAgendaGroup {
1470 group: agenda_group,
1471 })
1472 }
1473 "schedulerule" | "schedule_rule" => {
1474 let parts: Vec<&str> = args_str.split(',').collect();
1476 if parts.len() != 2 {
1477 return Err(RuleEngineError::ParseError {
1478 message: "ScheduleRule requires delay_ms and rule_name".to_string(),
1479 });
1480 }
1481
1482 let delay_ms = self.parse_value(parts[0].trim())?;
1483 let rule_name = self.parse_value(parts[1].trim())?;
1484
1485 let delay_ms = match delay_ms {
1486 Value::Integer(i) => i as u64,
1487 Value::Number(f) => f as u64,
1488 _ => {
1489 return Err(RuleEngineError::ParseError {
1490 message: "ScheduleRule delay_ms must be a number".to_string(),
1491 })
1492 }
1493 };
1494
1495 let rule_name = match rule_name {
1496 Value::String(s) => s,
1497 _ => rule_name.to_string(),
1498 };
1499
1500 Ok(ActionType::ScheduleRule {
1501 delay_ms,
1502 rule_name,
1503 })
1504 }
1505 "completeworkflow" | "complete_workflow" => {
1506 let workflow_id = if args_str.is_empty() {
1507 return Err(RuleEngineError::ParseError {
1508 message: "CompleteWorkflow requires workflow_id".to_string(),
1509 });
1510 } else {
1511 let value = self.parse_value(args_str.trim())?;
1512 match value {
1513 Value::String(s) => s,
1514 _ => value.to_string(),
1515 }
1516 };
1517 Ok(ActionType::CompleteWorkflow {
1518 workflow_name: workflow_id,
1519 })
1520 }
1521 "setworkflowdata" | "set_workflow_data" => {
1522 let data_str = args_str.trim();
1524
1525 let (key, value) = if let Some(eq_pos) = data_str.find('=') {
1527 let key = data_str[..eq_pos].trim().trim_matches('"');
1528 let value_str = data_str[eq_pos + 1..].trim();
1529 let value = self.parse_value(value_str)?;
1530 (key.to_string(), value)
1531 } else {
1532 return Err(RuleEngineError::ParseError {
1533 message: "SetWorkflowData data must be in key=value format".to_string(),
1534 });
1535 };
1536
1537 Ok(ActionType::SetWorkflowData { key, value })
1538 }
1539 _ => {
1540 let params = if args_str.is_empty() {
1542 HashMap::new()
1543 } else {
1544 self.parse_function_args_as_params(args_str)?
1545 };
1546
1547 Ok(ActionType::Custom {
1548 action_type: function_name.to_string(),
1549 params,
1550 })
1551 }
1552 }
1553 } else {
1554 Ok(ActionType::Custom {
1556 action_type: "statement".to_string(),
1557 params: {
1558 let mut params = HashMap::new();
1559 params.insert("statement".to_string(), Value::String(trimmed.to_string()));
1560 params
1561 },
1562 })
1563 }
1564 }
1565
1566 fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
1567 if args_str.trim().is_empty() {
1568 return Ok(Vec::new());
1569 }
1570
1571 let mut args = Vec::new();
1573 let parts: Vec<&str> = args_str.split(',').collect();
1574
1575 for part in parts {
1576 let trimmed = part.trim();
1577
1578 if trimmed.contains('+')
1580 || trimmed.contains('-')
1581 || trimmed.contains('*')
1582 || trimmed.contains('/')
1583 {
1584 args.push(Value::String(trimmed.to_string()));
1586 } else {
1587 args.push(self.parse_value(trimmed)?);
1588 }
1589 }
1590
1591 Ok(args)
1592 }
1593
1594 fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
1596 let mut params = HashMap::new();
1597
1598 if args_str.trim().is_empty() {
1599 return Ok(params);
1600 }
1601
1602 let parts: Vec<&str> = args_str.split(',').collect();
1604 for (i, part) in parts.iter().enumerate() {
1605 let trimmed = part.trim();
1606 let value = self.parse_value(trimmed)?;
1607
1608 params.insert(i.to_string(), value);
1610 }
1611
1612 Ok(params)
1613 }
1614
1615 #[cfg(feature = "streaming")]
1618 fn parse_stream_pattern_condition(&self, clause: &str) -> Result<ConditionGroup> {
1619 use crate::engine::rule::{StreamWindow, StreamWindowType};
1620 use crate::parser::grl::stream_syntax::parse_stream_pattern;
1621
1622 let parse_result =
1624 parse_stream_pattern(clause).map_err(|e| RuleEngineError::ParseError {
1625 message: format!("Failed to parse stream pattern: {:?}", e),
1626 })?;
1627
1628 let (_, pattern) = parse_result;
1629
1630 let window = pattern.source.window.map(|w| StreamWindow {
1632 duration: w.duration,
1633 window_type: match w.window_type {
1634 crate::parser::grl::stream_syntax::WindowType::Sliding => StreamWindowType::Sliding,
1635 crate::parser::grl::stream_syntax::WindowType::Tumbling => {
1636 StreamWindowType::Tumbling
1637 }
1638 crate::parser::grl::stream_syntax::WindowType::Session { timeout } => {
1639 StreamWindowType::Session { timeout }
1640 }
1641 },
1642 });
1643
1644 Ok(ConditionGroup::stream_pattern(
1645 pattern.var_name,
1646 pattern.event_type,
1647 pattern.source.stream_name,
1648 window,
1649 ))
1650 }
1651}
1652
1653#[cfg(test)]
1654mod tests {
1655 use super::GRLParser;
1656
1657 #[test]
1658 fn test_parse_simple_rule() {
1659 let grl = r#"
1660 rule "CheckAge" salience 10 {
1661 when
1662 User.Age >= 18
1663 then
1664 log("User is adult");
1665 }
1666 "#;
1667
1668 let rules = GRLParser::parse_rules(grl).unwrap();
1669 assert_eq!(rules.len(), 1);
1670 let rule = &rules[0];
1671 assert_eq!(rule.name, "CheckAge");
1672 assert_eq!(rule.salience, 10);
1673 assert_eq!(rule.actions.len(), 1);
1674 }
1675
1676 #[test]
1677 fn test_parse_complex_condition() {
1678 let grl = r#"
1679 rule "ComplexRule" {
1680 when
1681 User.Age >= 18 && User.Country == "US"
1682 then
1683 User.Qualified = true;
1684 }
1685 "#;
1686
1687 let rules = GRLParser::parse_rules(grl).unwrap();
1688 assert_eq!(rules.len(), 1);
1689 let rule = &rules[0];
1690 assert_eq!(rule.name, "ComplexRule");
1691 }
1692
1693 #[test]
1694 fn test_parse_new_syntax_with_parentheses() {
1695 let grl = r#"
1696 rule "Default Rule" salience 10 {
1697 when
1698 (user.age >= 18)
1699 then
1700 set(user.status, "approved");
1701 }
1702 "#;
1703
1704 let rules = GRLParser::parse_rules(grl).unwrap();
1705 assert_eq!(rules.len(), 1);
1706 let rule = &rules[0];
1707 assert_eq!(rule.name, "Default Rule");
1708 assert_eq!(rule.salience, 10);
1709 assert_eq!(rule.actions.len(), 1);
1710
1711 match &rule.actions[0] {
1713 crate::types::ActionType::Custom {
1714 action_type,
1715 params,
1716 } => {
1717 assert_eq!(action_type, "set");
1718 assert_eq!(
1719 params.get("0"),
1720 Some(&crate::types::Value::String("user.status".to_string()))
1721 );
1722 assert_eq!(
1723 params.get("1"),
1724 Some(&crate::types::Value::String("approved".to_string()))
1725 );
1726 }
1727 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1728 }
1729 }
1730
1731 #[test]
1732 fn test_parse_complex_nested_conditions() {
1733 let grl = r#"
1734 rule "Complex Business Rule" salience 10 {
1735 when
1736 (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
1737 then
1738 apply_discount(20000);
1739 }
1740 "#;
1741
1742 let rules = GRLParser::parse_rules(grl).unwrap();
1743 assert_eq!(rules.len(), 1);
1744 let rule = &rules[0];
1745 assert_eq!(rule.name, "Complex Business Rule");
1746 assert_eq!(rule.salience, 10);
1747 assert_eq!(rule.actions.len(), 1);
1748
1749 match &rule.actions[0] {
1751 crate::types::ActionType::Custom {
1752 action_type,
1753 params,
1754 } => {
1755 assert_eq!(action_type, "apply_discount");
1756 assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1757 }
1758 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1759 }
1760 }
1761
1762 #[test]
1763 fn test_parse_no_loop_attribute() {
1764 let grl = r#"
1765 rule "NoLoopRule" no-loop salience 15 {
1766 when
1767 User.Score < 100
1768 then
1769 set(User.Score, User.Score + 10);
1770 }
1771 "#;
1772
1773 let rules = GRLParser::parse_rules(grl).unwrap();
1774 assert_eq!(rules.len(), 1);
1775 let rule = &rules[0];
1776 assert_eq!(rule.name, "NoLoopRule");
1777 assert_eq!(rule.salience, 15);
1778 assert!(rule.no_loop, "Rule should have no-loop=true");
1779 }
1780
1781 #[test]
1782 fn test_parse_no_loop_different_positions() {
1783 let grl1 = r#"
1785 rule "Rule1" no-loop salience 10 {
1786 when User.Age >= 18
1787 then log("adult");
1788 }
1789 "#;
1790
1791 let grl2 = r#"
1793 rule "Rule2" salience 10 no-loop {
1794 when User.Age >= 18
1795 then log("adult");
1796 }
1797 "#;
1798
1799 let rules1 = GRLParser::parse_rules(grl1).unwrap();
1800 let rules2 = GRLParser::parse_rules(grl2).unwrap();
1801
1802 assert_eq!(rules1.len(), 1);
1803 assert_eq!(rules2.len(), 1);
1804
1805 assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1806 assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1807
1808 assert_eq!(rules1[0].salience, 10);
1809 assert_eq!(rules2[0].salience, 10);
1810 }
1811
1812 #[test]
1813 fn test_parse_without_no_loop() {
1814 let grl = r#"
1815 rule "RegularRule" salience 5 {
1816 when
1817 User.Active == true
1818 then
1819 log("active user");
1820 }
1821 "#;
1822
1823 let rules = GRLParser::parse_rules(grl).unwrap();
1824 assert_eq!(rules.len(), 1);
1825 let rule = &rules[0];
1826 assert_eq!(rule.name, "RegularRule");
1827 assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1828 }
1829
1830 #[test]
1831 fn test_parse_exists_pattern() {
1832 let grl = r#"
1833 rule "ExistsRule" salience 20 {
1834 when
1835 exists(Customer.tier == "VIP")
1836 then
1837 System.premiumActive = true;
1838 }
1839 "#;
1840
1841 let rules = GRLParser::parse_rules(grl).unwrap();
1842 assert_eq!(rules.len(), 1);
1843 let rule = &rules[0];
1844 assert_eq!(rule.name, "ExistsRule");
1845 assert_eq!(rule.salience, 20);
1846
1847 match &rule.conditions {
1849 crate::engine::rule::ConditionGroup::Exists(_) => {
1850 }
1852 _ => panic!(
1853 "Expected EXISTS condition group, got: {:?}",
1854 rule.conditions
1855 ),
1856 }
1857 }
1858
1859 #[test]
1860 fn test_parse_forall_pattern() {
1861 let grl = r#"
1862 rule "ForallRule" salience 15 {
1863 when
1864 forall(Order.status == "processed")
1865 then
1866 Shipping.enabled = true;
1867 }
1868 "#;
1869
1870 let rules = GRLParser::parse_rules(grl).unwrap();
1871 assert_eq!(rules.len(), 1);
1872 let rule = &rules[0];
1873 assert_eq!(rule.name, "ForallRule");
1874
1875 match &rule.conditions {
1877 crate::engine::rule::ConditionGroup::Forall(_) => {
1878 }
1880 _ => panic!(
1881 "Expected FORALL condition group, got: {:?}",
1882 rule.conditions
1883 ),
1884 }
1885 }
1886
1887 #[test]
1888 fn test_parse_combined_patterns() {
1889 let grl = r#"
1890 rule "CombinedRule" salience 25 {
1891 when
1892 exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1893 then
1894 System.vipMode = true;
1895 }
1896 "#;
1897
1898 let rules = GRLParser::parse_rules(grl).unwrap();
1899 assert_eq!(rules.len(), 1);
1900 let rule = &rules[0];
1901 assert_eq!(rule.name, "CombinedRule");
1902
1903 match &rule.conditions {
1905 crate::engine::rule::ConditionGroup::Compound {
1906 left,
1907 operator,
1908 right,
1909 } => {
1910 assert_eq!(*operator, crate::types::LogicalOperator::And);
1911
1912 match left.as_ref() {
1914 crate::engine::rule::ConditionGroup::Exists(_) => {
1915 }
1917 _ => panic!("Expected EXISTS in left side, got: {:?}", left),
1918 }
1919
1920 match right.as_ref() {
1922 crate::engine::rule::ConditionGroup::Not(inner) => {
1923 match inner.as_ref() {
1924 crate::engine::rule::ConditionGroup::Exists(_) => {
1925 }
1927 _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
1928 }
1929 }
1930 _ => panic!("Expected NOT in right side, got: {:?}", right),
1931 }
1932 }
1933 _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
1934 }
1935 }
1936}