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|startsWith|endsWith|matches|in)\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|startsWith|endsWith|matches|in)\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 let datetime =
599 naive_date
600 .and_hms_opt(0, 0, 0)
601 .ok_or_else(|| RuleEngineError::ParseError {
602 message: format!("Invalid time for date: {}", naive_date),
603 })?;
604 return Ok(datetime.and_utc());
605 }
606 }
607
608 Err(RuleEngineError::ParseError {
609 message: format!("Unable to parse date: {}", date_str),
610 })
611 }
612
613 fn extract_salience(&self, attributes_section: &str) -> Result<i32> {
615 if let Some(captures) = salience_regex().captures(attributes_section) {
616 if let Some(salience_match) = captures.get(1) {
617 return salience_match
618 .parse::<i32>()
619 .map_err(|e| RuleEngineError::ParseError {
620 message: format!("Invalid salience value: {}", e),
621 });
622 }
623 }
624
625 Ok(0) }
627
628 fn clean_text(&self, text: &str) -> String {
629 text.lines()
630 .map(|line| line.trim())
631 .filter(|line| !line.is_empty() && !line.starts_with("//"))
632 .collect::<Vec<_>>()
633 .join(" ")
634 }
635
636 fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
637 let trimmed = when_clause.trim();
639
640 let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
642 let inner = &trimmed[1..trimmed.len() - 1];
644 if self.is_balanced_parentheses(inner) {
645 inner
646 } else {
647 trimmed
648 }
649 } else {
650 trimmed
651 };
652
653 if let Some(parts) = self.split_logical_operator(clause, "||") {
655 return self.parse_or_parts(parts);
656 }
657
658 if let Some(parts) = self.split_logical_operator(clause, "&&") {
660 return self.parse_and_parts(parts);
661 }
662
663 if clause.trim_start().starts_with("!") {
665 return self.parse_not_condition(clause);
666 }
667
668 if clause.trim_start().starts_with("exists(") {
670 return self.parse_exists_condition(clause);
671 }
672
673 if clause.trim_start().starts_with("forall(") {
675 return self.parse_forall_condition(clause);
676 }
677
678 if clause.trim_start().starts_with("accumulate(") {
680 return self.parse_accumulate_condition(clause);
681 }
682
683 self.parse_single_condition(clause)
685 }
686
687 fn is_balanced_parentheses(&self, text: &str) -> bool {
688 let mut count = 0;
689 for ch in text.chars() {
690 match ch {
691 '(' => count += 1,
692 ')' => {
693 count -= 1;
694 if count < 0 {
695 return false;
696 }
697 }
698 _ => {}
699 }
700 }
701 count == 0
702 }
703
704 fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
705 let mut parts = Vec::new();
706 let mut current_part = String::new();
707 let mut paren_count = 0;
708 let mut chars = clause.chars().peekable();
709
710 while let Some(ch) = chars.next() {
711 match ch {
712 '(' => {
713 paren_count += 1;
714 current_part.push(ch);
715 }
716 ')' => {
717 paren_count -= 1;
718 current_part.push(ch);
719 }
720 '&' if operator == "&&" && paren_count == 0 => {
721 if chars.peek() == Some(&'&') {
722 chars.next(); parts.push(current_part.trim().to_string());
724 current_part.clear();
725 } else {
726 current_part.push(ch);
727 }
728 }
729 '|' if operator == "||" && paren_count == 0 => {
730 if chars.peek() == Some(&'|') {
731 chars.next(); parts.push(current_part.trim().to_string());
733 current_part.clear();
734 } else {
735 current_part.push(ch);
736 }
737 }
738 _ => {
739 current_part.push(ch);
740 }
741 }
742 }
743
744 if !current_part.trim().is_empty() {
745 parts.push(current_part.trim().to_string());
746 }
747
748 if parts.len() > 1 {
749 Some(parts)
750 } else {
751 None
752 }
753 }
754
755 fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
756 let mut conditions = Vec::new();
757 for part in parts {
758 let condition = self.parse_when_clause(&part)?;
759 conditions.push(condition);
760 }
761
762 if conditions.is_empty() {
763 return Err(RuleEngineError::ParseError {
764 message: "No conditions found in OR".to_string(),
765 });
766 }
767
768 let mut iter = conditions.into_iter();
769 let mut result = iter
770 .next()
771 .expect("Iterator cannot be empty after empty check");
772 for condition in iter {
773 result = ConditionGroup::or(result, condition);
774 }
775
776 Ok(result)
777 }
778
779 fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
780 let mut conditions = Vec::new();
781 for part in parts {
782 let condition = self.parse_when_clause(&part)?;
783 conditions.push(condition);
784 }
785
786 if conditions.is_empty() {
787 return Err(RuleEngineError::ParseError {
788 message: "No conditions found in AND".to_string(),
789 });
790 }
791
792 let mut iter = conditions.into_iter();
793 let mut result = iter
794 .next()
795 .expect("Iterator cannot be empty after empty check");
796 for condition in iter {
797 result = ConditionGroup::and(result, condition);
798 }
799
800 Ok(result)
801 }
802
803 fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
804 let inner_clause = clause
805 .strip_prefix('!')
806 .ok_or_else(|| RuleEngineError::ParseError {
807 message: format!("Expected '!' prefix in NOT condition: {}", clause),
808 })?
809 .trim();
810 let inner_condition = self.parse_when_clause(inner_clause)?;
811 Ok(ConditionGroup::not(inner_condition))
812 }
813
814 fn parse_exists_condition(&self, clause: &str) -> Result<ConditionGroup> {
815 let clause = clause.trim_start();
816 if !clause.starts_with("exists(") || !clause.ends_with(")") {
817 return Err(RuleEngineError::ParseError {
818 message: "Invalid exists syntax. Expected: exists(condition)".to_string(),
819 });
820 }
821
822 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
825 Ok(ConditionGroup::exists(inner_condition))
826 }
827
828 fn parse_forall_condition(&self, clause: &str) -> Result<ConditionGroup> {
829 let clause = clause.trim_start();
830 if !clause.starts_with("forall(") || !clause.ends_with(")") {
831 return Err(RuleEngineError::ParseError {
832 message: "Invalid forall syntax. Expected: forall(condition)".to_string(),
833 });
834 }
835
836 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
839 Ok(ConditionGroup::forall(inner_condition))
840 }
841
842 fn parse_accumulate_condition(&self, clause: &str) -> Result<ConditionGroup> {
843 let clause = clause.trim_start();
844 if !clause.starts_with("accumulate(") || !clause.ends_with(")") {
845 return Err(RuleEngineError::ParseError {
846 message: "Invalid accumulate syntax. Expected: accumulate(pattern, function)"
847 .to_string(),
848 });
849 }
850
851 let inner = &clause[11..clause.len() - 1]; let parts = self.split_accumulate_parts(inner)?;
856
857 if parts.len() != 2 {
858 return Err(RuleEngineError::ParseError {
859 message: format!(
860 "Invalid accumulate syntax. Expected 2 parts (pattern, function), got {}",
861 parts.len()
862 ),
863 });
864 }
865
866 let pattern_part = parts[0].trim();
867 let function_part = parts[1].trim();
868
869 let (source_pattern, extract_field, source_conditions) =
871 self.parse_accumulate_pattern(pattern_part)?;
872
873 let (function, function_arg) = self.parse_accumulate_function(function_part)?;
875
876 let result_var = "$result".to_string();
880
881 Ok(ConditionGroup::accumulate(
882 result_var,
883 source_pattern,
884 extract_field,
885 source_conditions,
886 function,
887 function_arg,
888 ))
889 }
890
891 fn split_accumulate_parts(&self, content: &str) -> Result<Vec<String>> {
892 let mut parts = Vec::new();
893 let mut current = String::new();
894 let mut paren_depth = 0;
895
896 for ch in content.chars() {
897 match ch {
898 '(' => {
899 paren_depth += 1;
900 current.push(ch);
901 }
902 ')' => {
903 paren_depth -= 1;
904 current.push(ch);
905 }
906 ',' if paren_depth == 0 => {
907 parts.push(current.trim().to_string());
908 current.clear();
909 }
910 _ => {
911 current.push(ch);
912 }
913 }
914 }
915
916 if !current.trim().is_empty() {
917 parts.push(current.trim().to_string());
918 }
919
920 Ok(parts)
921 }
922
923 fn parse_accumulate_pattern(&self, pattern: &str) -> Result<(String, String, Vec<String>)> {
924 let pattern = pattern.trim();
931
932 let paren_pos = pattern
934 .find('(')
935 .ok_or_else(|| RuleEngineError::ParseError {
936 message: format!("Invalid accumulate pattern: missing '(' in '{}'", pattern),
937 })?;
938
939 let source_pattern = pattern[..paren_pos].trim().to_string();
940
941 if !pattern.ends_with(')') {
943 return Err(RuleEngineError::ParseError {
944 message: format!("Invalid accumulate pattern: missing ')' in '{}'", pattern),
945 });
946 }
947
948 let inner = &pattern[paren_pos + 1..pattern.len() - 1];
949
950 let parts = self.split_pattern_parts(inner)?;
952
953 let mut extract_field = String::new();
954 let mut source_conditions = Vec::new();
955
956 for part in parts {
957 let part = part.trim();
958
959 if part.contains(':') && part.starts_with('$') {
961 if let Some(colon_pos) = part.find(':') {
962 extract_field = part[colon_pos + 1..].trim().to_string();
963 }
964 } else if part.contains("==")
965 || part.contains("!=")
966 || part.contains(">=")
967 || part.contains("<=")
968 || part.contains('>')
969 || part.contains('<')
970 {
971 source_conditions.push(part.to_string());
973 }
974 }
975
976 Ok((source_pattern, extract_field, source_conditions))
977 }
978
979 fn split_pattern_parts(&self, content: &str) -> Result<Vec<String>> {
980 let mut parts = Vec::new();
981 let mut current = String::new();
982 let mut paren_depth = 0;
983 let mut in_quotes = false;
984 let mut quote_char = ' ';
985
986 for ch in content.chars() {
987 match ch {
988 '"' | '\'' if !in_quotes => {
989 in_quotes = true;
990 quote_char = ch;
991 current.push(ch);
992 }
993 '"' | '\'' if in_quotes && ch == quote_char => {
994 in_quotes = false;
995 current.push(ch);
996 }
997 '(' if !in_quotes => {
998 paren_depth += 1;
999 current.push(ch);
1000 }
1001 ')' if !in_quotes => {
1002 paren_depth -= 1;
1003 current.push(ch);
1004 }
1005 ',' if !in_quotes && paren_depth == 0 => {
1006 parts.push(current.trim().to_string());
1007 current.clear();
1008 }
1009 _ => {
1010 current.push(ch);
1011 }
1012 }
1013 }
1014
1015 if !current.trim().is_empty() {
1016 parts.push(current.trim().to_string());
1017 }
1018
1019 Ok(parts)
1020 }
1021
1022 fn parse_accumulate_function(&self, function_str: &str) -> Result<(String, String)> {
1023 let function_str = function_str.trim();
1026
1027 let paren_pos = function_str
1028 .find('(')
1029 .ok_or_else(|| RuleEngineError::ParseError {
1030 message: format!(
1031 "Invalid accumulate function: missing '(' in '{}'",
1032 function_str
1033 ),
1034 })?;
1035
1036 let function_name = function_str[..paren_pos].trim().to_string();
1037
1038 if !function_str.ends_with(')') {
1039 return Err(RuleEngineError::ParseError {
1040 message: format!(
1041 "Invalid accumulate function: missing ')' in '{}'",
1042 function_str
1043 ),
1044 });
1045 }
1046
1047 let args = &function_str[paren_pos + 1..function_str.len() - 1];
1048 let function_arg = args.trim().to_string();
1049
1050 Ok((function_name, function_arg))
1051 }
1052
1053 fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
1054 let trimmed_clause = clause.trim();
1056 let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
1057 trimmed_clause[1..trimmed_clause.len() - 1].trim()
1058 } else {
1059 trimmed_clause
1060 };
1061
1062 #[cfg(feature = "streaming")]
1065 if clause_to_parse.contains("from stream(") {
1066 return self.parse_stream_pattern_condition(clause_to_parse);
1067 }
1068
1069 if let Some(captures) = multifield_collect_regex().captures(clause_to_parse) {
1076 let field = captures.get(1).unwrap().to_string();
1077 let variable = captures.get(2).unwrap().to_string();
1078
1079 let condition = Condition::with_multifield_collect(field, variable);
1082 return Ok(ConditionGroup::single(condition));
1083 }
1084
1085 if let Some(captures) = multifield_count_regex().captures(clause_to_parse) {
1092 let field = captures.get(1).unwrap().to_string();
1093 let operator_str = captures.get(2).unwrap();
1094 let value_str = captures.get(3).unwrap().trim();
1095
1096 let operator = Operator::from_str(operator_str).ok_or_else(|| {
1097 RuleEngineError::InvalidOperator {
1098 operator: operator_str.to_string(),
1099 }
1100 })?;
1101
1102 let value = self.parse_value(value_str)?;
1103
1104 let condition = Condition::with_multifield_count(field, operator, value);
1105 return Ok(ConditionGroup::single(condition));
1106 }
1107
1108 if let Some(captures) = multifield_first_regex().captures(clause_to_parse) {
1111 let field = captures.get(1).unwrap().to_string();
1112 let variable = captures.get(2).map(|m| m.to_string());
1113
1114 let condition = Condition::with_multifield_first(field, variable);
1115 return Ok(ConditionGroup::single(condition));
1116 }
1117
1118 if let Some(captures) = multifield_last_regex().captures(clause_to_parse) {
1121 let field = captures.get(1).unwrap().to_string();
1122 let variable = captures.get(2).map(|m| m.to_string());
1123
1124 let condition = Condition::with_multifield_last(field, variable);
1125 return Ok(ConditionGroup::single(condition));
1126 }
1127
1128 if let Some(captures) = multifield_empty_regex().captures(clause_to_parse) {
1131 let field = captures.get(1).unwrap().to_string();
1132
1133 let condition = Condition::with_multifield_empty(field);
1134 return Ok(ConditionGroup::single(condition));
1135 }
1136
1137 if let Some(captures) = multifield_not_empty_regex().captures(clause_to_parse) {
1140 let field = captures.get(1).unwrap().to_string();
1141
1142 let condition = Condition::with_multifield_not_empty(field);
1143 return Ok(ConditionGroup::single(condition));
1144 }
1145
1146 if let Some(captures) = test_condition_regex().captures(clause_to_parse) {
1151 let function_name = captures.get(1).unwrap().to_string();
1152 let args_str = captures.get(2).unwrap();
1153
1154 let args: Vec<String> = if args_str.trim().is_empty() {
1156 Vec::new()
1157 } else {
1158 args_str
1159 .split(',')
1160 .map(|arg| arg.trim().to_string())
1161 .collect()
1162 };
1163
1164 let condition = Condition::with_test(function_name, args);
1165 return Ok(ConditionGroup::single(condition));
1166 }
1167
1168 if let Some(captures) = typed_test_condition_regex().captures(clause_to_parse) {
1170 let _object_name = captures.get(1).unwrap();
1171 let _object_type = captures.get(2).unwrap();
1172 let conditions_str = captures.get(3).unwrap();
1173
1174 return self.parse_conditions_within_object(conditions_str);
1176 }
1177
1178 if let Some(captures) = function_call_regex().captures(clause_to_parse) {
1180 let function_name = captures.get(1).unwrap().to_string();
1181 let args_str = captures.get(2).unwrap();
1182 let operator_str = captures.get(3).unwrap();
1183 let value_str = captures.get(4).unwrap().trim();
1184
1185 let args: Vec<String> = if args_str.trim().is_empty() {
1187 Vec::new()
1188 } else {
1189 args_str
1190 .split(',')
1191 .map(|arg| arg.trim().to_string())
1192 .collect()
1193 };
1194
1195 let operator = Operator::from_str(operator_str).ok_or_else(|| {
1196 RuleEngineError::InvalidOperator {
1197 operator: operator_str.to_string(),
1198 }
1199 })?;
1200
1201 let value = self.parse_value(value_str)?;
1202
1203 let condition = Condition::with_function(function_name, args, operator, value);
1204 return Ok(ConditionGroup::single(condition));
1205 }
1206
1207 let captures = condition_regex().captures(clause_to_parse).ok_or_else(|| {
1211 RuleEngineError::ParseError {
1212 message: format!("Invalid condition format: {}", clause_to_parse),
1213 }
1214 })?;
1215
1216 let left_side = captures.get(1).unwrap().trim().to_string();
1217 let operator_str = captures.get(2).unwrap();
1218 let value_str = captures.get(3).unwrap().trim();
1219
1220 let operator =
1221 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1222 operator: operator_str.to_string(),
1223 })?;
1224
1225 let value = self.parse_value(value_str)?;
1226
1227 if left_side.contains('+')
1229 || left_side.contains('-')
1230 || left_side.contains('*')
1231 || left_side.contains('/')
1232 || left_side.contains('%')
1233 {
1234 let test_expr = format!("{} {} {}", left_side, operator_str, value_str);
1237 let condition = Condition::with_test(test_expr, vec![]);
1238 Ok(ConditionGroup::single(condition))
1239 } else {
1240 let condition = Condition::new(left_side, operator, value);
1242 Ok(ConditionGroup::single(condition))
1243 }
1244 }
1245
1246 fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
1247 let parts: Vec<&str> = conditions_str.split("&&").collect();
1249
1250 let mut conditions = Vec::new();
1251 for part in parts {
1252 let trimmed = part.trim();
1253 let condition = self.parse_simple_condition(trimmed)?;
1254 conditions.push(condition);
1255 }
1256
1257 if conditions.is_empty() {
1259 return Err(RuleEngineError::ParseError {
1260 message: "No conditions found".to_string(),
1261 });
1262 }
1263
1264 let mut iter = conditions.into_iter();
1265 let mut result = iter
1266 .next()
1267 .expect("Iterator cannot be empty after empty check");
1268 for condition in iter {
1269 result = ConditionGroup::and(result, condition);
1270 }
1271
1272 Ok(result)
1273 }
1274
1275 fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
1276 let captures = simple_condition_regex().captures(clause).ok_or_else(|| {
1278 RuleEngineError::ParseError {
1279 message: format!("Invalid simple condition format: {}", clause),
1280 }
1281 })?;
1282
1283 let field = captures.get(1).unwrap().to_string();
1284 let operator_str = captures.get(2).unwrap();
1285 let value_str = captures.get(3).unwrap().trim();
1286
1287 let operator =
1288 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1289 operator: operator_str.to_string(),
1290 })?;
1291
1292 let value = self.parse_value(value_str)?;
1293
1294 let condition = Condition::new(field, operator, value);
1295 Ok(ConditionGroup::single(condition))
1296 }
1297
1298 fn parse_value(&self, value_str: &str) -> Result<Value> {
1299 let trimmed = value_str.trim();
1300
1301 if trimmed.starts_with('[') && trimmed.ends_with(']') {
1303 return self.parse_array_literal(trimmed);
1304 }
1305
1306 if (trimmed.starts_with('"') && trimmed.ends_with('"'))
1308 || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
1309 {
1310 let unquoted = &trimmed[1..trimmed.len() - 1];
1311 return Ok(Value::String(unquoted.to_string()));
1312 }
1313
1314 if trimmed.eq_ignore_ascii_case("true") {
1316 return Ok(Value::Boolean(true));
1317 }
1318 if trimmed.eq_ignore_ascii_case("false") {
1319 return Ok(Value::Boolean(false));
1320 }
1321
1322 if trimmed.eq_ignore_ascii_case("null") {
1324 return Ok(Value::Null);
1325 }
1326
1327 if let Ok(int_val) = trimmed.parse::<i64>() {
1329 return Ok(Value::Integer(int_val));
1330 }
1331
1332 if let Ok(float_val) = trimmed.parse::<f64>() {
1333 return Ok(Value::Number(float_val));
1334 }
1335
1336 if self.is_expression(trimmed) {
1339 return Ok(Value::Expression(trimmed.to_string()));
1340 }
1341
1342 if trimmed.contains('.') {
1344 return Ok(Value::String(trimmed.to_string()));
1345 }
1346
1347 if self.is_identifier(trimmed) {
1351 return Ok(Value::Expression(trimmed.to_string()));
1352 }
1353
1354 Ok(Value::String(trimmed.to_string()))
1356 }
1357
1358 fn is_identifier(&self, s: &str) -> bool {
1361 if s.is_empty() {
1362 return false;
1363 }
1364 let first_char = s.chars().next().expect("Cannot be empty after empty check");
1365 if !first_char.is_alphabetic() && first_char != '_' {
1366 return false;
1367 }
1368
1369 let first_char = s.chars().next().unwrap();
1371 if !first_char.is_alphabetic() && first_char != '_' {
1372 return false;
1373 }
1374
1375 s.chars().all(|c| c.is_alphanumeric() || c == '_')
1377 }
1378
1379 fn is_expression(&self, s: &str) -> bool {
1381 let has_operator = s.contains('+')
1383 || s.contains('-')
1384 || s.contains('*')
1385 || s.contains('/')
1386 || s.contains('%');
1387
1388 let has_field_ref = s.contains('.');
1390
1391 let has_spaces = s.contains(' ');
1393
1394 has_operator && (has_field_ref || has_spaces)
1396 }
1397
1398 fn parse_array_literal(&self, array_str: &str) -> Result<Value> {
1400 let content = array_str.trim();
1401 if !content.starts_with('[') || !content.ends_with(']') {
1402 return Err(RuleEngineError::ParseError {
1403 message: format!("Invalid array literal: {}", array_str),
1404 });
1405 }
1406
1407 let inner = content[1..content.len() - 1].trim();
1408 if inner.is_empty() {
1409 return Ok(Value::Array(vec![]));
1410 }
1411
1412 let mut elements = Vec::new();
1414 let mut current_element = String::new();
1415 let mut in_quotes = false;
1416 let mut quote_char = ' ';
1417
1418 for ch in inner.chars() {
1419 match ch {
1420 '"' | '\'' if !in_quotes => {
1421 in_quotes = true;
1422 quote_char = ch;
1423 current_element.push(ch);
1424 }
1425 c if in_quotes && c == quote_char => {
1426 in_quotes = false;
1427 current_element.push(ch);
1428 }
1429 ',' if !in_quotes => {
1430 if !current_element.trim().is_empty() {
1431 elements.push(current_element.trim().to_string());
1432 }
1433 current_element.clear();
1434 }
1435 _ => {
1436 current_element.push(ch);
1437 }
1438 }
1439 }
1440
1441 if !current_element.trim().is_empty() {
1443 elements.push(current_element.trim().to_string());
1444 }
1445
1446 let mut array_values = Vec::new();
1448 for elem in elements {
1449 let value = self.parse_value(&elem)?;
1450 array_values.push(value);
1451 }
1452
1453 Ok(Value::Array(array_values))
1454 }
1455
1456 fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
1457 let statements: Vec<&str> = then_clause
1458 .split(';')
1459 .map(|s| s.trim())
1460 .filter(|s| !s.is_empty())
1461 .collect();
1462
1463 let mut actions = Vec::new();
1464
1465 for statement in statements {
1466 let action = self.parse_action_statement(statement)?;
1467 actions.push(action);
1468 }
1469
1470 Ok(actions)
1471 }
1472
1473 fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
1474 let trimmed = statement.trim();
1475
1476 if let Some(captures) = method_call_regex().captures(trimmed) {
1478 let object = captures.get(1).unwrap().to_string();
1479 let method = captures.get(2).unwrap().to_string();
1480 let args_str = captures.get(3).unwrap();
1481
1482 let args = if args_str.trim().is_empty() {
1483 Vec::new()
1484 } else {
1485 self.parse_method_args(args_str)?
1486 };
1487
1488 return Ok(ActionType::MethodCall {
1489 object,
1490 method,
1491 args,
1492 });
1493 }
1494
1495 if let Some(plus_eq_pos) = trimmed.find("+=") {
1497 let field = trimmed[..plus_eq_pos].trim().to_string();
1499 let value_str = trimmed[plus_eq_pos + 2..].trim();
1500 let value = self.parse_value(value_str)?;
1501
1502 return Ok(ActionType::Append { field, value });
1503 }
1504
1505 if let Some(eq_pos) = trimmed.find('=') {
1507 let field = trimmed[..eq_pos].trim().to_string();
1508 let value_str = trimmed[eq_pos + 1..].trim();
1509 let value = self.parse_value(value_str)?;
1510
1511 return Ok(ActionType::Set { field, value });
1512 }
1513
1514 if let Some(captures) = function_binding_regex().captures(trimmed) {
1516 let function_name = captures.get(1).unwrap();
1517 let args_str = captures.get(2).unwrap_or("");
1518
1519 match function_name.to_lowercase().as_str() {
1520 "retract" => {
1521 let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
1523 stripped.to_string()
1524 } else {
1525 args_str.to_string()
1526 };
1527 Ok(ActionType::Retract {
1528 object: object_name,
1529 })
1530 }
1531 "log" => {
1532 let message = if args_str.is_empty() {
1533 "Log message".to_string()
1534 } else {
1535 let value = self.parse_value(args_str.trim())?;
1536 value.to_string()
1537 };
1538 Ok(ActionType::Log { message })
1539 }
1540 "activateagendagroup" | "activate_agenda_group" => {
1541 let agenda_group = if args_str.is_empty() {
1542 return Err(RuleEngineError::ParseError {
1543 message: "ActivateAgendaGroup requires agenda group name".to_string(),
1544 });
1545 } else {
1546 let value = self.parse_value(args_str.trim())?;
1547 match value {
1548 Value::String(s) => s,
1549 _ => value.to_string(),
1550 }
1551 };
1552 Ok(ActionType::ActivateAgendaGroup {
1553 group: agenda_group,
1554 })
1555 }
1556 "schedulerule" | "schedule_rule" => {
1557 let parts: Vec<&str> = args_str.split(',').collect();
1559 if parts.len() != 2 {
1560 return Err(RuleEngineError::ParseError {
1561 message: "ScheduleRule requires delay_ms and rule_name".to_string(),
1562 });
1563 }
1564
1565 let delay_ms = self.parse_value(parts[0].trim())?;
1566 let rule_name = self.parse_value(parts[1].trim())?;
1567
1568 let delay_ms = match delay_ms {
1569 Value::Integer(i) => i as u64,
1570 Value::Number(f) => f as u64,
1571 _ => {
1572 return Err(RuleEngineError::ParseError {
1573 message: "ScheduleRule delay_ms must be a number".to_string(),
1574 })
1575 }
1576 };
1577
1578 let rule_name = match rule_name {
1579 Value::String(s) => s,
1580 _ => rule_name.to_string(),
1581 };
1582
1583 Ok(ActionType::ScheduleRule {
1584 delay_ms,
1585 rule_name,
1586 })
1587 }
1588 "completeworkflow" | "complete_workflow" => {
1589 let workflow_id = if args_str.is_empty() {
1590 return Err(RuleEngineError::ParseError {
1591 message: "CompleteWorkflow requires workflow_id".to_string(),
1592 });
1593 } else {
1594 let value = self.parse_value(args_str.trim())?;
1595 match value {
1596 Value::String(s) => s,
1597 _ => value.to_string(),
1598 }
1599 };
1600 Ok(ActionType::CompleteWorkflow {
1601 workflow_name: workflow_id,
1602 })
1603 }
1604 "setworkflowdata" | "set_workflow_data" => {
1605 let data_str = args_str.trim();
1607
1608 let (key, value) = if let Some(eq_pos) = data_str.find('=') {
1610 let key = data_str[..eq_pos].trim().trim_matches('"');
1611 let value_str = data_str[eq_pos + 1..].trim();
1612 let value = self.parse_value(value_str)?;
1613 (key.to_string(), value)
1614 } else {
1615 return Err(RuleEngineError::ParseError {
1616 message: "SetWorkflowData data must be in key=value format".to_string(),
1617 });
1618 };
1619
1620 Ok(ActionType::SetWorkflowData { key, value })
1621 }
1622 _ => {
1623 let params = if args_str.is_empty() {
1625 HashMap::new()
1626 } else {
1627 self.parse_function_args_as_params(args_str)?
1628 };
1629
1630 Ok(ActionType::Custom {
1631 action_type: function_name.to_string(),
1632 params,
1633 })
1634 }
1635 }
1636 } else {
1637 Ok(ActionType::Custom {
1639 action_type: "statement".to_string(),
1640 params: {
1641 let mut params = HashMap::new();
1642 params.insert("statement".to_string(), Value::String(trimmed.to_string()));
1643 params
1644 },
1645 })
1646 }
1647 }
1648
1649 fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
1650 if args_str.trim().is_empty() {
1651 return Ok(Vec::new());
1652 }
1653
1654 let mut args = Vec::new();
1656 let parts: Vec<&str> = args_str.split(',').collect();
1657
1658 for part in parts {
1659 let trimmed = part.trim();
1660
1661 if trimmed.contains('+')
1663 || trimmed.contains('-')
1664 || trimmed.contains('*')
1665 || trimmed.contains('/')
1666 {
1667 args.push(Value::String(trimmed.to_string()));
1669 } else {
1670 args.push(self.parse_value(trimmed)?);
1671 }
1672 }
1673
1674 Ok(args)
1675 }
1676
1677 fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
1679 let mut params = HashMap::new();
1680
1681 if args_str.trim().is_empty() {
1682 return Ok(params);
1683 }
1684
1685 let parts: Vec<&str> = args_str.split(',').collect();
1687 for (i, part) in parts.iter().enumerate() {
1688 let trimmed = part.trim();
1689 let value = self.parse_value(trimmed)?;
1690
1691 params.insert(i.to_string(), value);
1693 }
1694
1695 Ok(params)
1696 }
1697
1698 #[cfg(feature = "streaming")]
1701 fn parse_stream_pattern_condition(&self, clause: &str) -> Result<ConditionGroup> {
1702 use crate::engine::rule::{StreamWindow, StreamWindowType};
1703 use crate::parser::grl::stream_syntax::parse_stream_pattern;
1704
1705 let parse_result =
1707 parse_stream_pattern(clause).map_err(|e| RuleEngineError::ParseError {
1708 message: format!("Failed to parse stream pattern: {:?}", e),
1709 })?;
1710
1711 let (_, pattern) = parse_result;
1712
1713 let window = pattern.source.window.map(|w| StreamWindow {
1715 duration: w.duration,
1716 window_type: match w.window_type {
1717 crate::parser::grl::stream_syntax::WindowType::Sliding => StreamWindowType::Sliding,
1718 crate::parser::grl::stream_syntax::WindowType::Tumbling => {
1719 StreamWindowType::Tumbling
1720 }
1721 crate::parser::grl::stream_syntax::WindowType::Session { timeout } => {
1722 StreamWindowType::Session { timeout }
1723 }
1724 },
1725 });
1726
1727 Ok(ConditionGroup::stream_pattern(
1728 pattern.var_name,
1729 pattern.event_type,
1730 pattern.source.stream_name,
1731 window,
1732 ))
1733 }
1734}
1735
1736#[cfg(test)]
1737mod tests {
1738 use super::GRLParser;
1739
1740 #[test]
1741 fn test_parse_simple_rule() {
1742 let grl = r#"
1743 rule "CheckAge" salience 10 {
1744 when
1745 User.Age >= 18
1746 then
1747 log("User is adult");
1748 }
1749 "#;
1750
1751 let rules = GRLParser::parse_rules(grl).unwrap();
1752 assert_eq!(rules.len(), 1);
1753 let rule = &rules[0];
1754 assert_eq!(rule.name, "CheckAge");
1755 assert_eq!(rule.salience, 10);
1756 assert_eq!(rule.actions.len(), 1);
1757 }
1758
1759 #[test]
1760 fn test_parse_complex_condition() {
1761 let grl = r#"
1762 rule "ComplexRule" {
1763 when
1764 User.Age >= 18 && User.Country == "US"
1765 then
1766 User.Qualified = true;
1767 }
1768 "#;
1769
1770 let rules = GRLParser::parse_rules(grl).unwrap();
1771 assert_eq!(rules.len(), 1);
1772 let rule = &rules[0];
1773 assert_eq!(rule.name, "ComplexRule");
1774 }
1775
1776 #[test]
1777 fn test_parse_new_syntax_with_parentheses() {
1778 let grl = r#"
1779 rule "Default Rule" salience 10 {
1780 when
1781 (user.age >= 18)
1782 then
1783 set(user.status, "approved");
1784 }
1785 "#;
1786
1787 let rules = GRLParser::parse_rules(grl).unwrap();
1788 assert_eq!(rules.len(), 1);
1789 let rule = &rules[0];
1790 assert_eq!(rule.name, "Default Rule");
1791 assert_eq!(rule.salience, 10);
1792 assert_eq!(rule.actions.len(), 1);
1793
1794 match &rule.actions[0] {
1796 crate::types::ActionType::Custom {
1797 action_type,
1798 params,
1799 } => {
1800 assert_eq!(action_type, "set");
1801 assert_eq!(
1802 params.get("0"),
1803 Some(&crate::types::Value::String("user.status".to_string()))
1804 );
1805 assert_eq!(
1806 params.get("1"),
1807 Some(&crate::types::Value::String("approved".to_string()))
1808 );
1809 }
1810 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1811 }
1812 }
1813
1814 #[test]
1815 fn test_parse_complex_nested_conditions() {
1816 let grl = r#"
1817 rule "Complex Business Rule" salience 10 {
1818 when
1819 (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
1820 then
1821 apply_discount(20000);
1822 }
1823 "#;
1824
1825 let rules = GRLParser::parse_rules(grl).unwrap();
1826 assert_eq!(rules.len(), 1);
1827 let rule = &rules[0];
1828 assert_eq!(rule.name, "Complex Business Rule");
1829 assert_eq!(rule.salience, 10);
1830 assert_eq!(rule.actions.len(), 1);
1831
1832 match &rule.actions[0] {
1834 crate::types::ActionType::Custom {
1835 action_type,
1836 params,
1837 } => {
1838 assert_eq!(action_type, "apply_discount");
1839 assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1840 }
1841 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1842 }
1843 }
1844
1845 #[test]
1846 fn test_parse_no_loop_attribute() {
1847 let grl = r#"
1848 rule "NoLoopRule" no-loop salience 15 {
1849 when
1850 User.Score < 100
1851 then
1852 set(User.Score, User.Score + 10);
1853 }
1854 "#;
1855
1856 let rules = GRLParser::parse_rules(grl).unwrap();
1857 assert_eq!(rules.len(), 1);
1858 let rule = &rules[0];
1859 assert_eq!(rule.name, "NoLoopRule");
1860 assert_eq!(rule.salience, 15);
1861 assert!(rule.no_loop, "Rule should have no-loop=true");
1862 }
1863
1864 #[test]
1865 fn test_parse_no_loop_different_positions() {
1866 let grl1 = r#"
1868 rule "Rule1" no-loop salience 10 {
1869 when User.Age >= 18
1870 then log("adult");
1871 }
1872 "#;
1873
1874 let grl2 = r#"
1876 rule "Rule2" salience 10 no-loop {
1877 when User.Age >= 18
1878 then log("adult");
1879 }
1880 "#;
1881
1882 let rules1 = GRLParser::parse_rules(grl1).unwrap();
1883 let rules2 = GRLParser::parse_rules(grl2).unwrap();
1884
1885 assert_eq!(rules1.len(), 1);
1886 assert_eq!(rules2.len(), 1);
1887
1888 assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1889 assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1890
1891 assert_eq!(rules1[0].salience, 10);
1892 assert_eq!(rules2[0].salience, 10);
1893 }
1894
1895 #[test]
1896 fn test_parse_without_no_loop() {
1897 let grl = r#"
1898 rule "RegularRule" salience 5 {
1899 when
1900 User.Active == true
1901 then
1902 log("active user");
1903 }
1904 "#;
1905
1906 let rules = GRLParser::parse_rules(grl).unwrap();
1907 assert_eq!(rules.len(), 1);
1908 let rule = &rules[0];
1909 assert_eq!(rule.name, "RegularRule");
1910 assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1911 }
1912
1913 #[test]
1914 fn test_parse_exists_pattern() {
1915 let grl = r#"
1916 rule "ExistsRule" salience 20 {
1917 when
1918 exists(Customer.tier == "VIP")
1919 then
1920 System.premiumActive = true;
1921 }
1922 "#;
1923
1924 let rules = GRLParser::parse_rules(grl).unwrap();
1925 assert_eq!(rules.len(), 1);
1926 let rule = &rules[0];
1927 assert_eq!(rule.name, "ExistsRule");
1928 assert_eq!(rule.salience, 20);
1929
1930 match &rule.conditions {
1932 crate::engine::rule::ConditionGroup::Exists(_) => {
1933 }
1935 _ => panic!(
1936 "Expected EXISTS condition group, got: {:?}",
1937 rule.conditions
1938 ),
1939 }
1940 }
1941
1942 #[test]
1943 fn test_parse_forall_pattern() {
1944 let grl = r#"
1945 rule "ForallRule" salience 15 {
1946 when
1947 forall(Order.status == "processed")
1948 then
1949 Shipping.enabled = true;
1950 }
1951 "#;
1952
1953 let rules = GRLParser::parse_rules(grl).unwrap();
1954 assert_eq!(rules.len(), 1);
1955 let rule = &rules[0];
1956 assert_eq!(rule.name, "ForallRule");
1957
1958 match &rule.conditions {
1960 crate::engine::rule::ConditionGroup::Forall(_) => {
1961 }
1963 _ => panic!(
1964 "Expected FORALL condition group, got: {:?}",
1965 rule.conditions
1966 ),
1967 }
1968 }
1969
1970 #[test]
1971 fn test_parse_combined_patterns() {
1972 let grl = r#"
1973 rule "CombinedRule" salience 25 {
1974 when
1975 exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1976 then
1977 System.vipMode = true;
1978 }
1979 "#;
1980
1981 let rules = GRLParser::parse_rules(grl).unwrap();
1982 assert_eq!(rules.len(), 1);
1983 let rule = &rules[0];
1984 assert_eq!(rule.name, "CombinedRule");
1985
1986 match &rule.conditions {
1988 crate::engine::rule::ConditionGroup::Compound {
1989 left,
1990 operator,
1991 right,
1992 } => {
1993 assert_eq!(*operator, crate::types::LogicalOperator::And);
1994
1995 match left.as_ref() {
1997 crate::engine::rule::ConditionGroup::Exists(_) => {
1998 }
2000 _ => panic!("Expected EXISTS in left side, got: {:?}", left),
2001 }
2002
2003 match right.as_ref() {
2005 crate::engine::rule::ConditionGroup::Not(inner) => {
2006 match inner.as_ref() {
2007 crate::engine::rule::ConditionGroup::Exists(_) => {
2008 }
2010 _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
2011 }
2012 }
2013 _ => panic!("Expected NOT in right side, got: {:?}", right),
2014 }
2015 }
2016 _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
2017 }
2018 }
2019
2020 #[test]
2021 fn test_parse_in_operator() {
2022 let grl = r#"
2023 rule "TestInOperator" salience 75 {
2024 when
2025 User.role in ["admin", "moderator", "vip"]
2026 then
2027 User.access = "granted";
2028 }
2029 "#;
2030
2031 let rules = GRLParser::parse_rules(grl).unwrap();
2032 assert_eq!(rules.len(), 1);
2033 let rule = &rules[0];
2034 assert_eq!(rule.name, "TestInOperator");
2035 assert_eq!(rule.salience, 75);
2036
2037 match &rule.conditions {
2039 crate::engine::rule::ConditionGroup::Single(cond) => {
2040 println!("Condition: {:?}", cond);
2042 assert_eq!(cond.operator, crate::types::Operator::In);
2043
2044 match &cond.value {
2046 crate::types::Value::Array(arr) => {
2047 assert_eq!(arr.len(), 3);
2048 assert_eq!(arr[0], crate::types::Value::String("admin".to_string()));
2049 assert_eq!(arr[1], crate::types::Value::String("moderator".to_string()));
2050 assert_eq!(arr[2], crate::types::Value::String("vip".to_string()));
2051 }
2052 _ => panic!("Expected Array value, got {:?}", cond.value),
2053 }
2054 }
2055 _ => panic!("Expected Single condition, got: {:?}", rule.conditions),
2056 }
2057 }
2058
2059 #[test]
2060 fn test_parse_startswith_endswith_operators() {
2061 let grl = r#"
2062 rule "StringMethods" salience 50 {
2063 when
2064 User.email startsWith "admin@" &&
2065 User.filename endsWith ".txt"
2066 then
2067 User.validated = true;
2068 }
2069 "#;
2070
2071 let rules = GRLParser::parse_rules(grl).unwrap();
2072 assert_eq!(rules.len(), 1);
2073 let rule = &rules[0];
2074 assert_eq!(rule.name, "StringMethods");
2075 assert_eq!(rule.salience, 50);
2076
2077 match &rule.conditions {
2079 crate::engine::rule::ConditionGroup::Compound {
2080 left,
2081 operator,
2082 right,
2083 } => {
2084 assert_eq!(*operator, crate::types::LogicalOperator::And);
2085
2086 match left.as_ref() {
2088 crate::engine::rule::ConditionGroup::Single(cond) => {
2089 assert_eq!(cond.operator, crate::types::Operator::StartsWith);
2090 }
2091 _ => panic!("Expected Single condition for startsWith, got: {:?}", left),
2092 }
2093
2094 match right.as_ref() {
2096 crate::engine::rule::ConditionGroup::Single(cond) => {
2097 assert_eq!(cond.operator, crate::types::Operator::EndsWith);
2098 }
2099 _ => panic!("Expected Single condition for endsWith, got: {:?}", right),
2100 }
2101 }
2102 _ => panic!("Expected Compound condition, got: {:?}", rule.conditions),
2103 }
2104 }
2105}