1use crate::engine::rule::{Condition, ConditionGroup, Rule};
2use crate::errors::{Result, RuleEngineError};
3use crate::types::{ActionType, Operator, Value};
4use chrono::{DateTime, Utc};
5use regex::Regex;
6use once_cell::sync::Lazy;
7use std::collections::HashMap;
8
9static RULE_REGEX: Lazy<Regex> = Lazy::new(|| {
11 Regex::new(r#"rule\s+(?:"([^"]+)"|([a-zA-Z_]\w*))\s*([^{]*)\{(.+)\}"#)
12 .expect("Invalid rule regex pattern")
13});
14
15static RULE_SPLIT_REGEX: Lazy<Regex> = Lazy::new(|| {
16 Regex::new(r#"(?s)rule\s+(?:"[^"]+"|[a-zA-Z_]\w*).*?\}"#)
17 .expect("Invalid rule split regex pattern")
18});
19
20static WHEN_THEN_REGEX: Lazy<Regex> = Lazy::new(|| {
21 Regex::new(r"when\s+(.+?)\s+then\s+(.+)")
22 .expect("Invalid when-then regex pattern")
23});
24
25static SALIENCE_REGEX: Lazy<Regex> = Lazy::new(|| {
26 Regex::new(r"salience\s+(\d+)")
27 .expect("Invalid salience regex pattern")
28});
29
30static TEST_CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
31 Regex::new(r#"^test\s*\(\s*([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*\)$"#)
32 .expect("Invalid test condition regex")
33});
34
35static TYPED_TEST_CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
36 Regex::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#)
37 .expect("Invalid typed test condition regex")
38});
39
40static FUNCTION_CALL_REGEX: Lazy<Regex> = Lazy::new(|| {
41 Regex::new(r#"([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#)
42 .expect("Invalid function call regex")
43});
44
45static CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
46 Regex::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*(.+)"#)
47 .expect("Invalid condition regex")
48});
49
50static METHOD_CALL_REGEX: Lazy<Regex> = Lazy::new(|| {
51 Regex::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#)
52 .expect("Invalid method call regex")
53});
54
55static FUNCTION_BINDING_REGEX: Lazy<Regex> = Lazy::new(|| {
56 Regex::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#)
57 .expect("Invalid function binding regex")
58});
59
60static MULTIFIELD_COLLECT_REGEX: Lazy<Regex> = Lazy::new(|| {
62 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+(\$\?[a-zA-Z_]\w*)$"#)
63 .expect("Invalid multifield collect regex")
64});
65
66static MULTIFIELD_COUNT_REGEX: Lazy<Regex> = Lazy::new(|| {
67 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+count\s*(>=|<=|==|!=|>|<)\s*(.+)$"#)
68 .expect("Invalid multifield count regex")
69});
70
71static MULTIFIELD_FIRST_REGEX: Lazy<Regex> = Lazy::new(|| {
72 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+first(?:\s+(\$[a-zA-Z_]\w*))?$"#)
73 .expect("Invalid multifield first regex")
74});
75
76static MULTIFIELD_LAST_REGEX: Lazy<Regex> = Lazy::new(|| {
77 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+last(?:\s+(\$[a-zA-Z_]\w*))?$"#)
78 .expect("Invalid multifield last regex")
79});
80
81static MULTIFIELD_EMPTY_REGEX: Lazy<Regex> = Lazy::new(|| {
82 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+empty$"#)
83 .expect("Invalid multifield empty regex")
84});
85
86static MULTIFIELD_NOT_EMPTY_REGEX: Lazy<Regex> = Lazy::new(|| {
87 Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+not_empty$"#)
88 .expect("Invalid multifield not_empty regex")
89});
90
91static SIMPLE_CONDITION_REGEX: Lazy<Regex> = Lazy::new(|| {
92 Regex::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#)
93 .expect("Invalid simple condition regex")
94});
95
96pub struct GRLParser;
99
100#[derive(Debug, Default)]
102struct RuleAttributes {
103 pub no_loop: bool,
104 pub lock_on_active: bool,
105 pub agenda_group: Option<String>,
106 pub activation_group: Option<String>,
107 pub date_effective: Option<DateTime<Utc>>,
108 pub date_expires: Option<DateTime<Utc>>,
109}
110
111impl GRLParser {
112 pub fn parse_rule(grl_text: &str) -> Result<Rule> {
125 let mut parser = GRLParser;
126 parser.parse_single_rule(grl_text)
127 }
128
129 pub fn parse_rules(grl_text: &str) -> Result<Vec<Rule>> {
131 let mut parser = GRLParser;
132 parser.parse_multiple_rules(grl_text)
133 }
134
135 fn parse_single_rule(&mut self, grl_text: &str) -> Result<Rule> {
136 let cleaned = self.clean_text(grl_text);
137
138 let captures =
140 RULE_REGEX
141 .captures(&cleaned)
142 .ok_or_else(|| RuleEngineError::ParseError {
143 message: format!("Invalid GRL rule format. Input: {}", cleaned),
144 })?;
145
146 let rule_name = if let Some(quoted_name) = captures.get(1) {
148 quoted_name.as_str().to_string()
149 } else if let Some(unquoted_name) = captures.get(2) {
150 unquoted_name.as_str().to_string()
151 } else {
152 return Err(RuleEngineError::ParseError {
153 message: "Could not extract rule name".to_string(),
154 });
155 };
156
157 let attributes_section = captures.get(3).map(|m| m.as_str()).unwrap_or("");
159
160 let rule_body = captures.get(4).unwrap().as_str();
162
163 let salience = self.extract_salience(attributes_section)?;
165
166 let when_then_captures =
168 WHEN_THEN_REGEX
169 .captures(rule_body)
170 .ok_or_else(|| RuleEngineError::ParseError {
171 message: "Missing when or then clause".to_string(),
172 })?;
173
174 let when_clause = when_then_captures.get(1).unwrap().as_str().trim();
175 let then_clause = when_then_captures.get(2).unwrap().as_str().trim();
176
177 let conditions = self.parse_when_clause(when_clause)?;
179 let actions = self.parse_then_clause(then_clause)?;
180
181 let attributes = self.parse_rule_attributes(attributes_section)?;
183
184 let mut rule = Rule::new(rule_name, conditions, actions);
186 rule = rule.with_priority(salience);
187
188 if attributes.no_loop {
190 rule = rule.with_no_loop(true);
191 }
192 if attributes.lock_on_active {
193 rule = rule.with_lock_on_active(true);
194 }
195 if let Some(agenda_group) = attributes.agenda_group {
196 rule = rule.with_agenda_group(agenda_group);
197 }
198 if let Some(activation_group) = attributes.activation_group {
199 rule = rule.with_activation_group(activation_group);
200 }
201 if let Some(date_effective) = attributes.date_effective {
202 rule = rule.with_date_effective(date_effective);
203 }
204 if let Some(date_expires) = attributes.date_expires {
205 rule = rule.with_date_expires(date_expires);
206 }
207
208 Ok(rule)
209 }
210
211 fn parse_multiple_rules(&mut self, grl_text: &str) -> Result<Vec<Rule>> {
212 let mut rules = Vec::new();
215
216 for rule_match in RULE_SPLIT_REGEX.find_iter(grl_text) {
217 let rule_text = rule_match.as_str();
218 let rule = self.parse_single_rule(rule_text)?;
219 rules.push(rule);
220 }
221
222 Ok(rules)
223 }
224
225 fn parse_rule_attributes(&self, rule_header: &str) -> Result<RuleAttributes> {
227 let mut attributes = RuleAttributes::default();
228
229 let mut attrs_section = rule_header.to_string();
233
234 let quoted_regex = Regex::new(r#""[^"]*""#).map_err(|e| RuleEngineError::ParseError {
236 message: format!("Invalid quoted string regex: {}", e),
237 })?;
238 attrs_section = quoted_regex.replace_all(&attrs_section, "").to_string();
239
240 if let Some(rule_pos) = attrs_section.find("rule") {
242 let after_rule = &attrs_section[rule_pos + 4..];
244 if let Some(first_keyword) = after_rule.find("salience")
245 .or_else(|| after_rule.find("no-loop"))
246 .or_else(|| after_rule.find("lock-on-active"))
247 .or_else(|| after_rule.find("agenda-group"))
248 .or_else(|| after_rule.find("activation-group"))
249 .or_else(|| after_rule.find("date-effective"))
250 .or_else(|| after_rule.find("date-expires"))
251 {
252 attrs_section = after_rule[first_keyword..].to_string();
253 }
254 }
255
256 let no_loop_regex = Regex::new(r"\bno-loop\b").map_err(|e| RuleEngineError::ParseError {
258 message: format!("Invalid no-loop regex: {}", e),
259 })?;
260 let lock_on_active_regex = Regex::new(r"\block-on-active\b").map_err(|e| RuleEngineError::ParseError {
261 message: format!("Invalid lock-on-active regex: {}", e),
262 })?;
263
264 if no_loop_regex.is_match(&attrs_section) {
265 attributes.no_loop = true;
266 }
267 if lock_on_active_regex.is_match(&attrs_section) {
268 attributes.lock_on_active = true;
269 }
270
271 if let Some(agenda_group) = self.extract_quoted_attribute(rule_header, "agenda-group")? {
273 attributes.agenda_group = Some(agenda_group);
274 }
275
276 if let Some(activation_group) =
278 self.extract_quoted_attribute(rule_header, "activation-group")?
279 {
280 attributes.activation_group = Some(activation_group);
281 }
282
283 if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-effective")? {
285 attributes.date_effective = Some(self.parse_date_string(&date_str)?);
286 }
287
288 if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-expires")? {
290 attributes.date_expires = Some(self.parse_date_string(&date_str)?);
291 }
292
293 Ok(attributes)
294 }
295
296 fn extract_quoted_attribute(&self, header: &str, attribute: &str) -> Result<Option<String>> {
298 let pattern = format!(r#"{}\s+"([^"]+)""#, attribute);
299 let regex = Regex::new(&pattern).map_err(|e| RuleEngineError::ParseError {
300 message: format!("Invalid attribute regex for {}: {}", attribute, e),
301 })?;
302
303 if let Some(captures) = regex.captures(header) {
304 if let Some(value) = captures.get(1) {
305 return Ok(Some(value.as_str().to_string()));
306 }
307 }
308
309 Ok(None)
310 }
311
312 fn parse_date_string(&self, date_str: &str) -> Result<DateTime<Utc>> {
314 if let Ok(date) = DateTime::parse_from_rfc3339(date_str) {
316 return Ok(date.with_timezone(&Utc));
317 }
318
319 let formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%d-%b-%Y", "%d-%m-%Y"];
321
322 for format in &formats {
323 if let Ok(naive_date) = chrono::NaiveDateTime::parse_from_str(date_str, format) {
324 return Ok(naive_date.and_utc());
325 }
326 if let Ok(naive_date) = chrono::NaiveDate::parse_from_str(date_str, format) {
327 return Ok(naive_date.and_hms_opt(0, 0, 0).unwrap().and_utc());
328 }
329 }
330
331 Err(RuleEngineError::ParseError {
332 message: format!("Unable to parse date: {}", date_str),
333 })
334 }
335
336 fn extract_salience(&self, attributes_section: &str) -> Result<i32> {
338 if let Some(captures) = SALIENCE_REGEX.captures(attributes_section) {
339 if let Some(salience_match) = captures.get(1) {
340 return salience_match.as_str().parse::<i32>().map_err(|e| {
341 RuleEngineError::ParseError {
342 message: format!("Invalid salience value: {}", e),
343 }
344 });
345 }
346 }
347
348 Ok(0) }
350
351 fn clean_text(&self, text: &str) -> String {
352 text.lines()
353 .map(|line| line.trim())
354 .filter(|line| !line.is_empty() && !line.starts_with("//"))
355 .collect::<Vec<_>>()
356 .join(" ")
357 }
358
359 fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
360 let trimmed = when_clause.trim();
362
363 let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
365 let inner = &trimmed[1..trimmed.len() - 1];
367 if self.is_balanced_parentheses(inner) {
368 inner
369 } else {
370 trimmed
371 }
372 } else {
373 trimmed
374 };
375
376 if let Some(parts) = self.split_logical_operator(clause, "||") {
378 return self.parse_or_parts(parts);
379 }
380
381 if let Some(parts) = self.split_logical_operator(clause, "&&") {
383 return self.parse_and_parts(parts);
384 }
385
386 if clause.trim_start().starts_with("!") {
388 return self.parse_not_condition(clause);
389 }
390
391 if clause.trim_start().starts_with("exists(") {
393 return self.parse_exists_condition(clause);
394 }
395
396 if clause.trim_start().starts_with("forall(") {
398 return self.parse_forall_condition(clause);
399 }
400
401 if clause.trim_start().starts_with("accumulate(") {
403 return self.parse_accumulate_condition(clause);
404 }
405
406 self.parse_single_condition(clause)
408 }
409
410 fn is_balanced_parentheses(&self, text: &str) -> bool {
411 let mut count = 0;
412 for ch in text.chars() {
413 match ch {
414 '(' => count += 1,
415 ')' => {
416 count -= 1;
417 if count < 0 {
418 return false;
419 }
420 }
421 _ => {}
422 }
423 }
424 count == 0
425 }
426
427 fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
428 let mut parts = Vec::new();
429 let mut current_part = String::new();
430 let mut paren_count = 0;
431 let mut chars = clause.chars().peekable();
432
433 while let Some(ch) = chars.next() {
434 match ch {
435 '(' => {
436 paren_count += 1;
437 current_part.push(ch);
438 }
439 ')' => {
440 paren_count -= 1;
441 current_part.push(ch);
442 }
443 '&' if operator == "&&" && paren_count == 0 => {
444 if chars.peek() == Some(&'&') {
445 chars.next(); parts.push(current_part.trim().to_string());
447 current_part.clear();
448 } else {
449 current_part.push(ch);
450 }
451 }
452 '|' if operator == "||" && paren_count == 0 => {
453 if chars.peek() == Some(&'|') {
454 chars.next(); parts.push(current_part.trim().to_string());
456 current_part.clear();
457 } else {
458 current_part.push(ch);
459 }
460 }
461 _ => {
462 current_part.push(ch);
463 }
464 }
465 }
466
467 if !current_part.trim().is_empty() {
468 parts.push(current_part.trim().to_string());
469 }
470
471 if parts.len() > 1 {
472 Some(parts)
473 } else {
474 None
475 }
476 }
477
478 fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
479 let mut conditions = Vec::new();
480 for part in parts {
481 let condition = self.parse_when_clause(&part)?;
482 conditions.push(condition);
483 }
484
485 if conditions.is_empty() {
486 return Err(RuleEngineError::ParseError {
487 message: "No conditions found in OR".to_string(),
488 });
489 }
490
491 let mut iter = conditions.into_iter();
492 let mut result = iter.next().unwrap();
493 for condition in iter {
494 result = ConditionGroup::or(result, condition);
495 }
496
497 Ok(result)
498 }
499
500 fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
501 let mut conditions = Vec::new();
502 for part in parts {
503 let condition = self.parse_when_clause(&part)?;
504 conditions.push(condition);
505 }
506
507 if conditions.is_empty() {
508 return Err(RuleEngineError::ParseError {
509 message: "No conditions found in AND".to_string(),
510 });
511 }
512
513 let mut iter = conditions.into_iter();
514 let mut result = iter.next().unwrap();
515 for condition in iter {
516 result = ConditionGroup::and(result, condition);
517 }
518
519 Ok(result)
520 }
521
522 fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
523 let inner_clause = clause.strip_prefix("!").unwrap().trim();
524 let inner_condition = self.parse_when_clause(inner_clause)?;
525 Ok(ConditionGroup::not(inner_condition))
526 }
527
528 fn parse_exists_condition(&self, clause: &str) -> Result<ConditionGroup> {
529 let clause = clause.trim_start();
530 if !clause.starts_with("exists(") || !clause.ends_with(")") {
531 return Err(RuleEngineError::ParseError {
532 message: "Invalid exists syntax. Expected: exists(condition)".to_string(),
533 });
534 }
535
536 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
539 Ok(ConditionGroup::exists(inner_condition))
540 }
541
542 fn parse_forall_condition(&self, clause: &str) -> Result<ConditionGroup> {
543 let clause = clause.trim_start();
544 if !clause.starts_with("forall(") || !clause.ends_with(")") {
545 return Err(RuleEngineError::ParseError {
546 message: "Invalid forall syntax. Expected: forall(condition)".to_string(),
547 });
548 }
549
550 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
553 Ok(ConditionGroup::forall(inner_condition))
554 }
555
556 fn parse_accumulate_condition(&self, clause: &str) -> Result<ConditionGroup> {
557 let clause = clause.trim_start();
558 if !clause.starts_with("accumulate(") || !clause.ends_with(")") {
559 return Err(RuleEngineError::ParseError {
560 message: "Invalid accumulate syntax. Expected: accumulate(pattern, function)".to_string(),
561 });
562 }
563
564 let inner = &clause[11..clause.len() - 1]; let parts = self.split_accumulate_parts(inner)?;
569
570 if parts.len() != 2 {
571 return Err(RuleEngineError::ParseError {
572 message: format!(
573 "Invalid accumulate syntax. Expected 2 parts (pattern, function), got {}",
574 parts.len()
575 ),
576 });
577 }
578
579 let pattern_part = parts[0].trim();
580 let function_part = parts[1].trim();
581
582 let (source_pattern, extract_field, source_conditions) =
584 self.parse_accumulate_pattern(pattern_part)?;
585
586 let (function, function_arg) = self.parse_accumulate_function(function_part)?;
588
589 let result_var = "$result".to_string();
593
594 Ok(ConditionGroup::accumulate(
595 result_var,
596 source_pattern,
597 extract_field,
598 source_conditions,
599 function,
600 function_arg,
601 ))
602 }
603
604 fn split_accumulate_parts(&self, content: &str) -> Result<Vec<String>> {
605 let mut parts = Vec::new();
606 let mut current = String::new();
607 let mut paren_depth = 0;
608
609 for ch in content.chars() {
610 match ch {
611 '(' => {
612 paren_depth += 1;
613 current.push(ch);
614 }
615 ')' => {
616 paren_depth -= 1;
617 current.push(ch);
618 }
619 ',' if paren_depth == 0 => {
620 parts.push(current.trim().to_string());
621 current.clear();
622 }
623 _ => {
624 current.push(ch);
625 }
626 }
627 }
628
629 if !current.trim().is_empty() {
630 parts.push(current.trim().to_string());
631 }
632
633 Ok(parts)
634 }
635
636 fn parse_accumulate_pattern(&self, pattern: &str) -> Result<(String, String, Vec<String>)> {
637 let pattern = pattern.trim();
644
645 let paren_pos = pattern.find('(').ok_or_else(|| RuleEngineError::ParseError {
647 message: format!("Invalid accumulate pattern: missing '(' in '{}'", pattern),
648 })?;
649
650 let source_pattern = pattern[..paren_pos].trim().to_string();
651
652 if !pattern.ends_with(')') {
654 return Err(RuleEngineError::ParseError {
655 message: format!("Invalid accumulate pattern: missing ')' in '{}'", pattern),
656 });
657 }
658
659 let inner = &pattern[paren_pos + 1..pattern.len() - 1];
660
661 let parts = self.split_pattern_parts(inner)?;
663
664 let mut extract_field = String::new();
665 let mut source_conditions = Vec::new();
666
667 for part in parts {
668 let part = part.trim();
669
670 if part.contains(':') && part.starts_with('$') {
672 let colon_pos = part.find(':').unwrap();
673 let _var_name = part[..colon_pos].trim();
674 let field_name = part[colon_pos + 1..].trim();
675 extract_field = field_name.to_string();
676 } else if part.contains("==") || part.contains("!=") ||
677 part.contains(">=") || part.contains("<=") ||
678 part.contains('>') || part.contains('<') {
679 source_conditions.push(part.to_string());
681 }
682 }
683
684 Ok((source_pattern, extract_field, source_conditions))
685 }
686
687 fn split_pattern_parts(&self, content: &str) -> Result<Vec<String>> {
688 let mut parts = Vec::new();
689 let mut current = String::new();
690 let mut paren_depth = 0;
691 let mut in_quotes = false;
692 let mut quote_char = ' ';
693
694 for ch in content.chars() {
695 match ch {
696 '"' | '\'' if !in_quotes => {
697 in_quotes = true;
698 quote_char = ch;
699 current.push(ch);
700 }
701 '"' | '\'' if in_quotes && ch == quote_char => {
702 in_quotes = false;
703 current.push(ch);
704 }
705 '(' if !in_quotes => {
706 paren_depth += 1;
707 current.push(ch);
708 }
709 ')' if !in_quotes => {
710 paren_depth -= 1;
711 current.push(ch);
712 }
713 ',' if !in_quotes && paren_depth == 0 => {
714 parts.push(current.trim().to_string());
715 current.clear();
716 }
717 _ => {
718 current.push(ch);
719 }
720 }
721 }
722
723 if !current.trim().is_empty() {
724 parts.push(current.trim().to_string());
725 }
726
727 Ok(parts)
728 }
729
730 fn parse_accumulate_function(&self, function_str: &str) -> Result<(String, String)> {
731 let function_str = function_str.trim();
734
735 let paren_pos = function_str.find('(').ok_or_else(|| RuleEngineError::ParseError {
736 message: format!("Invalid accumulate function: missing '(' in '{}'", function_str),
737 })?;
738
739 let function_name = function_str[..paren_pos].trim().to_string();
740
741 if !function_str.ends_with(')') {
742 return Err(RuleEngineError::ParseError {
743 message: format!("Invalid accumulate function: missing ')' in '{}'", function_str),
744 });
745 }
746
747 let args = &function_str[paren_pos + 1..function_str.len() - 1];
748 let function_arg = args.trim().to_string();
749
750 Ok((function_name, function_arg))
751 }
752
753 fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
754 let trimmed_clause = clause.trim();
756 let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
757 trimmed_clause[1..trimmed_clause.len() - 1].trim()
758 } else {
759 trimmed_clause
760 };
761
762 if let Some(captures) = MULTIFIELD_COLLECT_REGEX.captures(clause_to_parse) {
769 let field = captures.get(1).unwrap().as_str().to_string();
770 let variable = captures.get(2).unwrap().as_str().to_string();
771
772 let condition = Condition::with_multifield_collect(field, variable);
775 return Ok(ConditionGroup::single(condition));
776 }
777
778 if let Some(captures) = MULTIFIELD_COUNT_REGEX.captures(clause_to_parse) {
785 let field = captures.get(1).unwrap().as_str().to_string();
786 let operator_str = captures.get(2).unwrap().as_str();
787 let value_str = captures.get(3).unwrap().as_str().trim();
788
789 let operator = Operator::from_str(operator_str)
790 .ok_or_else(|| RuleEngineError::InvalidOperator {
791 operator: operator_str.to_string(),
792 })?;
793
794 let value = self.parse_value(value_str)?;
795
796 let condition = Condition::with_multifield_count(field, operator, value);
797 return Ok(ConditionGroup::single(condition));
798 }
799
800 if let Some(captures) = MULTIFIELD_FIRST_REGEX.captures(clause_to_parse) {
803 let field = captures.get(1).unwrap().as_str().to_string();
804 let variable = captures.get(2).map(|m| m.as_str().to_string());
805
806 let condition = Condition::with_multifield_first(field, variable);
807 return Ok(ConditionGroup::single(condition));
808 }
809
810 if let Some(captures) = MULTIFIELD_LAST_REGEX.captures(clause_to_parse) {
813 let field = captures.get(1).unwrap().as_str().to_string();
814 let variable = captures.get(2).map(|m| m.as_str().to_string());
815
816 let condition = Condition::with_multifield_last(field, variable);
817 return Ok(ConditionGroup::single(condition));
818 }
819
820 if let Some(captures) = MULTIFIELD_EMPTY_REGEX.captures(clause_to_parse) {
823 let field = captures.get(1).unwrap().as_str().to_string();
824
825 let condition = Condition::with_multifield_empty(field);
826 return Ok(ConditionGroup::single(condition));
827 }
828
829 if let Some(captures) = MULTIFIELD_NOT_EMPTY_REGEX.captures(clause_to_parse) {
832 let field = captures.get(1).unwrap().as_str().to_string();
833
834 let condition = Condition::with_multifield_not_empty(field);
835 return Ok(ConditionGroup::single(condition));
836 }
837
838 if let Some(captures) = TEST_CONDITION_REGEX.captures(clause_to_parse) {
843 let function_name = captures.get(1).unwrap().as_str().to_string();
844 let args_str = captures.get(2).unwrap().as_str();
845
846 let args: Vec<String> = if args_str.trim().is_empty() {
848 Vec::new()
849 } else {
850 args_str
851 .split(',')
852 .map(|arg| arg.trim().to_string())
853 .collect()
854 };
855
856 let condition = Condition::with_test(function_name, args);
857 return Ok(ConditionGroup::single(condition));
858 }
859
860 if let Some(captures) = TYPED_TEST_CONDITION_REGEX.captures(clause_to_parse) {
862 let _object_name = captures.get(1).unwrap().as_str();
863 let _object_type = captures.get(2).unwrap().as_str();
864 let conditions_str = captures.get(3).unwrap().as_str();
865
866 return self.parse_conditions_within_object(conditions_str);
868 }
869
870 if let Some(captures) = FUNCTION_CALL_REGEX.captures(clause_to_parse) {
872 let function_name = captures.get(1).unwrap().as_str().to_string();
873 let args_str = captures.get(2).unwrap().as_str();
874 let operator_str = captures.get(3).unwrap().as_str();
875 let value_str = captures.get(4).unwrap().as_str().trim();
876
877 let args: Vec<String> = if args_str.trim().is_empty() {
879 Vec::new()
880 } else {
881 args_str
882 .split(',')
883 .map(|arg| arg.trim().to_string())
884 .collect()
885 };
886
887 let operator =
888 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
889 operator: operator_str.to_string(),
890 })?;
891
892 let value = self.parse_value(value_str)?;
893
894 let condition = Condition::with_function(function_name, args, operator, value);
895 return Ok(ConditionGroup::single(condition));
896 }
897
898 let captures = CONDITION_REGEX.captures(clause_to_parse).ok_or_else(|| {
902 RuleEngineError::ParseError {
903 message: format!("Invalid condition format: {}", clause_to_parse),
904 }
905 })?;
906
907 let left_side = captures.get(1).unwrap().as_str().trim().to_string();
908 let operator_str = captures.get(2).unwrap().as_str();
909 let value_str = captures.get(3).unwrap().as_str().trim();
910
911 let operator =
912 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
913 operator: operator_str.to_string(),
914 })?;
915
916 let value = self.parse_value(value_str)?;
917
918 if left_side.contains('+') || left_side.contains('-') || left_side.contains('*')
920 || left_side.contains('/') || left_side.contains('%') {
921 let test_expr = format!("{} {} {}", left_side, operator_str, value_str);
924 let condition = Condition::with_test(test_expr, vec![]);
925 Ok(ConditionGroup::single(condition))
926 } else {
927 let condition = Condition::new(left_side, operator, value);
929 Ok(ConditionGroup::single(condition))
930 }
931 }
932
933 fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
934 let parts: Vec<&str> = conditions_str.split("&&").collect();
936
937 let mut conditions = Vec::new();
938 for part in parts {
939 let trimmed = part.trim();
940 let condition = self.parse_simple_condition(trimmed)?;
941 conditions.push(condition);
942 }
943
944 if conditions.is_empty() {
946 return Err(RuleEngineError::ParseError {
947 message: "No conditions found".to_string(),
948 });
949 }
950
951 let mut iter = conditions.into_iter();
952 let mut result = iter.next().unwrap();
953 for condition in iter {
954 result = ConditionGroup::and(result, condition);
955 }
956
957 Ok(result)
958 }
959
960 fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
961 let captures =
963 SIMPLE_CONDITION_REGEX
964 .captures(clause)
965 .ok_or_else(|| RuleEngineError::ParseError {
966 message: format!("Invalid simple condition format: {}", clause),
967 })?;
968
969 let field = captures.get(1).unwrap().as_str().to_string();
970 let operator_str = captures.get(2).unwrap().as_str();
971 let value_str = captures.get(3).unwrap().as_str().trim();
972
973 let operator =
974 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
975 operator: operator_str.to_string(),
976 })?;
977
978 let value = self.parse_value(value_str)?;
979
980 let condition = Condition::new(field, operator, value);
981 Ok(ConditionGroup::single(condition))
982 }
983
984 fn parse_value(&self, value_str: &str) -> Result<Value> {
985 let trimmed = value_str.trim();
986
987 if (trimmed.starts_with('"') && trimmed.ends_with('"'))
989 || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
990 {
991 let unquoted = &trimmed[1..trimmed.len() - 1];
992 return Ok(Value::String(unquoted.to_string()));
993 }
994
995 if trimmed.eq_ignore_ascii_case("true") {
997 return Ok(Value::Boolean(true));
998 }
999 if trimmed.eq_ignore_ascii_case("false") {
1000 return Ok(Value::Boolean(false));
1001 }
1002
1003 if trimmed.eq_ignore_ascii_case("null") {
1005 return Ok(Value::Null);
1006 }
1007
1008 if let Ok(int_val) = trimmed.parse::<i64>() {
1010 return Ok(Value::Integer(int_val));
1011 }
1012
1013 if let Ok(float_val) = trimmed.parse::<f64>() {
1014 return Ok(Value::Number(float_val));
1015 }
1016
1017 if self.is_expression(trimmed) {
1020 return Ok(Value::Expression(trimmed.to_string()));
1021 }
1022
1023 if trimmed.contains('.') {
1025 return Ok(Value::String(trimmed.to_string()));
1026 }
1027
1028 if self.is_identifier(trimmed) {
1032 return Ok(Value::Expression(trimmed.to_string()));
1033 }
1034
1035 Ok(Value::String(trimmed.to_string()))
1037 }
1038
1039 fn is_identifier(&self, s: &str) -> bool {
1042 if s.is_empty() {
1043 return false;
1044 }
1045
1046 let first_char = s.chars().next().unwrap();
1048 if !first_char.is_alphabetic() && first_char != '_' {
1049 return false;
1050 }
1051
1052 s.chars().all(|c| c.is_alphanumeric() || c == '_')
1054 }
1055
1056 fn is_expression(&self, s: &str) -> bool {
1058 let has_operator = s.contains('+') || s.contains('-') || s.contains('*') || s.contains('/') || s.contains('%');
1060
1061 let has_field_ref = s.contains('.');
1063
1064 let has_spaces = s.contains(' ');
1066
1067 has_operator && (has_field_ref || has_spaces)
1069 }
1070
1071 fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
1072 let statements: Vec<&str> = then_clause
1073 .split(';')
1074 .map(|s| s.trim())
1075 .filter(|s| !s.is_empty())
1076 .collect();
1077
1078 let mut actions = Vec::new();
1079
1080 for statement in statements {
1081 let action = self.parse_action_statement(statement)?;
1082 actions.push(action);
1083 }
1084
1085 Ok(actions)
1086 }
1087
1088 fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
1089 let trimmed = statement.trim();
1090
1091 if let Some(captures) = METHOD_CALL_REGEX.captures(trimmed) {
1093 let object = captures.get(1).unwrap().as_str().to_string();
1094 let method = captures.get(2).unwrap().as_str().to_string();
1095 let args_str = captures.get(3).unwrap().as_str();
1096
1097 let args = if args_str.trim().is_empty() {
1098 Vec::new()
1099 } else {
1100 self.parse_method_args(args_str)?
1101 };
1102
1103 return Ok(ActionType::MethodCall {
1104 object,
1105 method,
1106 args,
1107 });
1108 }
1109
1110 if let Some(eq_pos) = trimmed.find('=') {
1112 let field = trimmed[..eq_pos].trim().to_string();
1113 let value_str = trimmed[eq_pos + 1..].trim();
1114 let value = self.parse_value(value_str)?;
1115
1116 return Ok(ActionType::Set { field, value });
1117 }
1118
1119 if let Some(captures) = FUNCTION_BINDING_REGEX.captures(trimmed) {
1121 let function_name = captures.get(1).unwrap().as_str();
1122 let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
1123
1124 match function_name.to_lowercase().as_str() {
1125 "retract" => {
1126 let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
1128 stripped.to_string()
1129 } else {
1130 args_str.to_string()
1131 };
1132 Ok(ActionType::Retract {
1133 object: object_name,
1134 })
1135 }
1136 "log" => {
1137 let message = if args_str.is_empty() {
1138 "Log message".to_string()
1139 } else {
1140 let value = self.parse_value(args_str.trim())?;
1141 value.to_string()
1142 };
1143 Ok(ActionType::Log { message })
1144 }
1145 "activateagendagroup" | "activate_agenda_group" => {
1146 let agenda_group = if args_str.is_empty() {
1147 return Err(RuleEngineError::ParseError {
1148 message: "ActivateAgendaGroup requires agenda group name".to_string(),
1149 });
1150 } else {
1151 let value = self.parse_value(args_str.trim())?;
1152 match value {
1153 Value::String(s) => s,
1154 _ => value.to_string(),
1155 }
1156 };
1157 Ok(ActionType::ActivateAgendaGroup {
1158 group: agenda_group,
1159 })
1160 }
1161 "schedulerule" | "schedule_rule" => {
1162 let parts: Vec<&str> = args_str.split(',').collect();
1164 if parts.len() != 2 {
1165 return Err(RuleEngineError::ParseError {
1166 message: "ScheduleRule requires delay_ms and rule_name".to_string(),
1167 });
1168 }
1169
1170 let delay_ms = self.parse_value(parts[0].trim())?;
1171 let rule_name = self.parse_value(parts[1].trim())?;
1172
1173 let delay_ms = match delay_ms {
1174 Value::Integer(i) => i as u64,
1175 Value::Number(f) => f as u64,
1176 _ => {
1177 return Err(RuleEngineError::ParseError {
1178 message: "ScheduleRule delay_ms must be a number".to_string(),
1179 })
1180 }
1181 };
1182
1183 let rule_name = match rule_name {
1184 Value::String(s) => s,
1185 _ => rule_name.to_string(),
1186 };
1187
1188 Ok(ActionType::ScheduleRule {
1189 delay_ms,
1190 rule_name,
1191 })
1192 }
1193 "completeworkflow" | "complete_workflow" => {
1194 let workflow_id = if args_str.is_empty() {
1195 return Err(RuleEngineError::ParseError {
1196 message: "CompleteWorkflow requires workflow_id".to_string(),
1197 });
1198 } else {
1199 let value = self.parse_value(args_str.trim())?;
1200 match value {
1201 Value::String(s) => s,
1202 _ => value.to_string(),
1203 }
1204 };
1205 Ok(ActionType::CompleteWorkflow {
1206 workflow_name: workflow_id,
1207 })
1208 }
1209 "setworkflowdata" | "set_workflow_data" => {
1210 let data_str = args_str.trim();
1212
1213 let (key, value) = if let Some(eq_pos) = data_str.find('=') {
1215 let key = data_str[..eq_pos].trim().trim_matches('"');
1216 let value_str = data_str[eq_pos + 1..].trim();
1217 let value = self.parse_value(value_str)?;
1218 (key.to_string(), value)
1219 } else {
1220 return Err(RuleEngineError::ParseError {
1221 message: "SetWorkflowData data must be in key=value format".to_string(),
1222 });
1223 };
1224
1225 Ok(ActionType::SetWorkflowData { key, value })
1226 }
1227 _ => {
1228 let params = if args_str.is_empty() {
1230 HashMap::new()
1231 } else {
1232 self.parse_function_args_as_params(args_str)?
1233 };
1234
1235 Ok(ActionType::Custom {
1236 action_type: function_name.to_string(),
1237 params,
1238 })
1239 }
1240 }
1241 } else {
1242 Ok(ActionType::Custom {
1244 action_type: "statement".to_string(),
1245 params: {
1246 let mut params = HashMap::new();
1247 params.insert("statement".to_string(), Value::String(trimmed.to_string()));
1248 params
1249 },
1250 })
1251 }
1252 }
1253
1254 fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
1255 if args_str.trim().is_empty() {
1256 return Ok(Vec::new());
1257 }
1258
1259 let mut args = Vec::new();
1261 let parts: Vec<&str> = args_str.split(',').collect();
1262
1263 for part in parts {
1264 let trimmed = part.trim();
1265
1266 if trimmed.contains('+')
1268 || trimmed.contains('-')
1269 || trimmed.contains('*')
1270 || trimmed.contains('/')
1271 {
1272 args.push(Value::String(trimmed.to_string()));
1274 } else {
1275 args.push(self.parse_value(trimmed)?);
1276 }
1277 }
1278
1279 Ok(args)
1280 }
1281
1282 fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
1284 let mut params = HashMap::new();
1285
1286 if args_str.trim().is_empty() {
1287 return Ok(params);
1288 }
1289
1290 let parts: Vec<&str> = args_str.split(',').collect();
1292 for (i, part) in parts.iter().enumerate() {
1293 let trimmed = part.trim();
1294 let value = self.parse_value(trimmed)?;
1295
1296 params.insert(i.to_string(), value);
1298 }
1299
1300 Ok(params)
1301 }
1302}
1303
1304#[cfg(test)]
1305mod tests {
1306 use super::GRLParser;
1307
1308 #[test]
1309 fn test_parse_simple_rule() {
1310 let grl = r#"
1311 rule "CheckAge" salience 10 {
1312 when
1313 User.Age >= 18
1314 then
1315 log("User is adult");
1316 }
1317 "#;
1318
1319 let rules = GRLParser::parse_rules(grl).unwrap();
1320 assert_eq!(rules.len(), 1);
1321 let rule = &rules[0];
1322 assert_eq!(rule.name, "CheckAge");
1323 assert_eq!(rule.salience, 10);
1324 assert_eq!(rule.actions.len(), 1);
1325 }
1326
1327 #[test]
1328 fn test_parse_complex_condition() {
1329 let grl = r#"
1330 rule "ComplexRule" {
1331 when
1332 User.Age >= 18 && User.Country == "US"
1333 then
1334 User.Qualified = true;
1335 }
1336 "#;
1337
1338 let rules = GRLParser::parse_rules(grl).unwrap();
1339 assert_eq!(rules.len(), 1);
1340 let rule = &rules[0];
1341 assert_eq!(rule.name, "ComplexRule");
1342 }
1343
1344 #[test]
1345 fn test_parse_new_syntax_with_parentheses() {
1346 let grl = r#"
1347 rule "Default Rule" salience 10 {
1348 when
1349 (user.age >= 18)
1350 then
1351 set(user.status, "approved");
1352 }
1353 "#;
1354
1355 let rules = GRLParser::parse_rules(grl).unwrap();
1356 assert_eq!(rules.len(), 1);
1357 let rule = &rules[0];
1358 assert_eq!(rule.name, "Default Rule");
1359 assert_eq!(rule.salience, 10);
1360 assert_eq!(rule.actions.len(), 1);
1361
1362 match &rule.actions[0] {
1364 crate::types::ActionType::Custom {
1365 action_type,
1366 params,
1367 } => {
1368 assert_eq!(action_type, "set");
1369 assert_eq!(
1370 params.get("0"),
1371 Some(&crate::types::Value::String("user.status".to_string()))
1372 );
1373 assert_eq!(
1374 params.get("1"),
1375 Some(&crate::types::Value::String("approved".to_string()))
1376 );
1377 }
1378 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1379 }
1380 }
1381
1382 #[test]
1383 fn test_parse_complex_nested_conditions() {
1384 let grl = r#"
1385 rule "Complex Business Rule" salience 10 {
1386 when
1387 (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
1388 then
1389 apply_discount(20000);
1390 }
1391 "#;
1392
1393 let rules = GRLParser::parse_rules(grl).unwrap();
1394 assert_eq!(rules.len(), 1);
1395 let rule = &rules[0];
1396 assert_eq!(rule.name, "Complex Business Rule");
1397 assert_eq!(rule.salience, 10);
1398 assert_eq!(rule.actions.len(), 1);
1399
1400 match &rule.actions[0] {
1402 crate::types::ActionType::Custom {
1403 action_type,
1404 params,
1405 } => {
1406 assert_eq!(action_type, "apply_discount");
1407 assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1408 }
1409 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1410 }
1411 }
1412
1413 #[test]
1414 fn test_parse_no_loop_attribute() {
1415 let grl = r#"
1416 rule "NoLoopRule" no-loop salience 15 {
1417 when
1418 User.Score < 100
1419 then
1420 set(User.Score, User.Score + 10);
1421 }
1422 "#;
1423
1424 let rules = GRLParser::parse_rules(grl).unwrap();
1425 assert_eq!(rules.len(), 1);
1426 let rule = &rules[0];
1427 assert_eq!(rule.name, "NoLoopRule");
1428 assert_eq!(rule.salience, 15);
1429 assert!(rule.no_loop, "Rule should have no-loop=true");
1430 }
1431
1432 #[test]
1433 fn test_parse_no_loop_different_positions() {
1434 let grl1 = r#"
1436 rule "Rule1" no-loop salience 10 {
1437 when User.Age >= 18
1438 then log("adult");
1439 }
1440 "#;
1441
1442 let grl2 = r#"
1444 rule "Rule2" salience 10 no-loop {
1445 when User.Age >= 18
1446 then log("adult");
1447 }
1448 "#;
1449
1450 let rules1 = GRLParser::parse_rules(grl1).unwrap();
1451 let rules2 = GRLParser::parse_rules(grl2).unwrap();
1452
1453 assert_eq!(rules1.len(), 1);
1454 assert_eq!(rules2.len(), 1);
1455
1456 assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1457 assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1458
1459 assert_eq!(rules1[0].salience, 10);
1460 assert_eq!(rules2[0].salience, 10);
1461 }
1462
1463 #[test]
1464 fn test_parse_without_no_loop() {
1465 let grl = r#"
1466 rule "RegularRule" salience 5 {
1467 when
1468 User.Active == true
1469 then
1470 log("active user");
1471 }
1472 "#;
1473
1474 let rules = GRLParser::parse_rules(grl).unwrap();
1475 assert_eq!(rules.len(), 1);
1476 let rule = &rules[0];
1477 assert_eq!(rule.name, "RegularRule");
1478 assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1479 }
1480
1481 #[test]
1482 fn test_parse_exists_pattern() {
1483 let grl = r#"
1484 rule "ExistsRule" salience 20 {
1485 when
1486 exists(Customer.tier == "VIP")
1487 then
1488 System.premiumActive = true;
1489 }
1490 "#;
1491
1492 let rules = GRLParser::parse_rules(grl).unwrap();
1493 assert_eq!(rules.len(), 1);
1494 let rule = &rules[0];
1495 assert_eq!(rule.name, "ExistsRule");
1496 assert_eq!(rule.salience, 20);
1497
1498 match &rule.conditions {
1500 crate::engine::rule::ConditionGroup::Exists(_) => {
1501 }
1503 _ => panic!(
1504 "Expected EXISTS condition group, got: {:?}",
1505 rule.conditions
1506 ),
1507 }
1508 }
1509
1510 #[test]
1511 fn test_parse_forall_pattern() {
1512 let grl = r#"
1513 rule "ForallRule" salience 15 {
1514 when
1515 forall(Order.status == "processed")
1516 then
1517 Shipping.enabled = true;
1518 }
1519 "#;
1520
1521 let rules = GRLParser::parse_rules(grl).unwrap();
1522 assert_eq!(rules.len(), 1);
1523 let rule = &rules[0];
1524 assert_eq!(rule.name, "ForallRule");
1525
1526 match &rule.conditions {
1528 crate::engine::rule::ConditionGroup::Forall(_) => {
1529 }
1531 _ => panic!(
1532 "Expected FORALL condition group, got: {:?}",
1533 rule.conditions
1534 ),
1535 }
1536 }
1537
1538 #[test]
1539 fn test_parse_combined_patterns() {
1540 let grl = r#"
1541 rule "CombinedRule" salience 25 {
1542 when
1543 exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1544 then
1545 System.vipMode = true;
1546 }
1547 "#;
1548
1549 let rules = GRLParser::parse_rules(grl).unwrap();
1550 assert_eq!(rules.len(), 1);
1551 let rule = &rules[0];
1552 assert_eq!(rule.name, "CombinedRule");
1553
1554 match &rule.conditions {
1556 crate::engine::rule::ConditionGroup::Compound {
1557 left,
1558 operator,
1559 right,
1560 } => {
1561 assert_eq!(*operator, crate::types::LogicalOperator::And);
1562
1563 match left.as_ref() {
1565 crate::engine::rule::ConditionGroup::Exists(_) => {
1566 }
1568 _ => panic!("Expected EXISTS in left side, got: {:?}", left),
1569 }
1570
1571 match right.as_ref() {
1573 crate::engine::rule::ConditionGroup::Not(inner) => {
1574 match inner.as_ref() {
1575 crate::engine::rule::ConditionGroup::Exists(_) => {
1576 }
1578 _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
1579 }
1580 }
1581 _ => panic!("Expected NOT in right side, got: {:?}", right),
1582 }
1583 }
1584 _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
1585 }
1586 }
1587}