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