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 self.parse_single_condition(clause)
303 }
304
305 fn is_balanced_parentheses(&self, text: &str) -> bool {
306 let mut count = 0;
307 for ch in text.chars() {
308 match ch {
309 '(' => count += 1,
310 ')' => {
311 count -= 1;
312 if count < 0 {
313 return false;
314 }
315 }
316 _ => {}
317 }
318 }
319 count == 0
320 }
321
322 fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
323 let mut parts = Vec::new();
324 let mut current_part = String::new();
325 let mut paren_count = 0;
326 let mut chars = clause.chars().peekable();
327
328 while let Some(ch) = chars.next() {
329 match ch {
330 '(' => {
331 paren_count += 1;
332 current_part.push(ch);
333 }
334 ')' => {
335 paren_count -= 1;
336 current_part.push(ch);
337 }
338 '&' if operator == "&&" && paren_count == 0 => {
339 if chars.peek() == Some(&'&') {
340 chars.next(); parts.push(current_part.trim().to_string());
342 current_part.clear();
343 } else {
344 current_part.push(ch);
345 }
346 }
347 '|' if operator == "||" && paren_count == 0 => {
348 if chars.peek() == Some(&'|') {
349 chars.next(); parts.push(current_part.trim().to_string());
351 current_part.clear();
352 } else {
353 current_part.push(ch);
354 }
355 }
356 _ => {
357 current_part.push(ch);
358 }
359 }
360 }
361
362 if !current_part.trim().is_empty() {
363 parts.push(current_part.trim().to_string());
364 }
365
366 if parts.len() > 1 {
367 Some(parts)
368 } else {
369 None
370 }
371 }
372
373 fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
374 let mut conditions = Vec::new();
375 for part in parts {
376 let condition = self.parse_when_clause(&part)?;
377 conditions.push(condition);
378 }
379
380 if conditions.is_empty() {
381 return Err(RuleEngineError::ParseError {
382 message: "No conditions found in OR".to_string(),
383 });
384 }
385
386 let mut iter = conditions.into_iter();
387 let mut result = iter.next().unwrap();
388 for condition in iter {
389 result = ConditionGroup::or(result, condition);
390 }
391
392 Ok(result)
393 }
394
395 fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
396 let mut conditions = Vec::new();
397 for part in parts {
398 let condition = self.parse_when_clause(&part)?;
399 conditions.push(condition);
400 }
401
402 if conditions.is_empty() {
403 return Err(RuleEngineError::ParseError {
404 message: "No conditions found in AND".to_string(),
405 });
406 }
407
408 let mut iter = conditions.into_iter();
409 let mut result = iter.next().unwrap();
410 for condition in iter {
411 result = ConditionGroup::and(result, condition);
412 }
413
414 Ok(result)
415 }
416
417 fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
418 let inner_clause = clause.strip_prefix("!").unwrap().trim();
419 let inner_condition = self.parse_when_clause(inner_clause)?;
420 Ok(ConditionGroup::not(inner_condition))
421 }
422
423 fn parse_exists_condition(&self, clause: &str) -> Result<ConditionGroup> {
424 let clause = clause.trim_start();
425 if !clause.starts_with("exists(") || !clause.ends_with(")") {
426 return Err(RuleEngineError::ParseError {
427 message: "Invalid exists syntax. Expected: exists(condition)".to_string(),
428 });
429 }
430
431 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
434 Ok(ConditionGroup::exists(inner_condition))
435 }
436
437 fn parse_forall_condition(&self, clause: &str) -> Result<ConditionGroup> {
438 let clause = clause.trim_start();
439 if !clause.starts_with("forall(") || !clause.ends_with(")") {
440 return Err(RuleEngineError::ParseError {
441 message: "Invalid forall syntax. Expected: forall(condition)".to_string(),
442 });
443 }
444
445 let inner_clause = &clause[7..clause.len() - 1]; let inner_condition = self.parse_when_clause(inner_clause)?;
448 Ok(ConditionGroup::forall(inner_condition))
449 }
450
451 fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
452 let trimmed_clause = clause.trim();
454 let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
455 trimmed_clause[1..trimmed_clause.len() - 1].trim()
456 } else {
457 trimmed_clause
458 };
459
460 let test_regex = Regex::new(r#"^test\s*\(\s*([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*\)$"#)
463 .map_err(|e| RuleEngineError::ParseError {
464 message: format!("Test CE regex error: {}", e),
465 })?;
466
467 if let Some(captures) = test_regex.captures(clause_to_parse) {
468 let function_name = captures.get(1).unwrap().as_str().to_string();
469 let args_str = captures.get(2).unwrap().as_str();
470
471 let args: Vec<String> = if args_str.trim().is_empty() {
473 Vec::new()
474 } else {
475 args_str
476 .split(',')
477 .map(|arg| arg.trim().to_string())
478 .collect()
479 };
480
481 let condition = Condition::with_test(function_name, args);
482 return Ok(ConditionGroup::single(condition));
483 }
484
485 let typed_object_regex =
487 Regex::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#).map_err(|e| {
488 RuleEngineError::ParseError {
489 message: format!("Typed object regex error: {}", e),
490 }
491 })?;
492
493 if let Some(captures) = typed_object_regex.captures(clause_to_parse) {
494 let _object_name = captures.get(1).unwrap().as_str();
495 let _object_type = captures.get(2).unwrap().as_str();
496 let conditions_str = captures.get(3).unwrap().as_str();
497
498 return self.parse_conditions_within_object(conditions_str);
500 }
501
502 let function_regex = Regex::new(
504 r#"([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#,
505 )
506 .map_err(|e| RuleEngineError::ParseError {
507 message: format!("Function regex error: {}", e),
508 })?;
509
510 if let Some(captures) = function_regex.captures(clause_to_parse) {
511 let function_name = captures.get(1).unwrap().as_str().to_string();
512 let args_str = captures.get(2).unwrap().as_str();
513 let operator_str = captures.get(3).unwrap().as_str();
514 let value_str = captures.get(4).unwrap().as_str().trim();
515
516 let args: Vec<String> = if args_str.trim().is_empty() {
518 Vec::new()
519 } else {
520 args_str
521 .split(',')
522 .map(|arg| arg.trim().to_string())
523 .collect()
524 };
525
526 let operator =
527 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
528 operator: operator_str.to_string(),
529 })?;
530
531 let value = self.parse_value(value_str)?;
532
533 let condition = Condition::with_function(function_name, args, operator, value);
534 return Ok(ConditionGroup::single(condition));
535 }
536
537 let condition_regex = Regex::new(
540 r#"([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#,
541 )
542 .map_err(|e| RuleEngineError::ParseError {
543 message: format!("Condition regex error: {}", e),
544 })?;
545
546 let captures = condition_regex.captures(clause_to_parse).ok_or_else(|| {
547 RuleEngineError::ParseError {
548 message: format!("Invalid condition format: {}", clause_to_parse),
549 }
550 })?;
551
552 let field = captures.get(1).unwrap().as_str().to_string();
553 let operator_str = captures.get(2).unwrap().as_str();
554 let value_str = captures.get(3).unwrap().as_str().trim();
555
556 let operator =
557 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
558 operator: operator_str.to_string(),
559 })?;
560
561 let value = self.parse_value(value_str)?;
562
563 let condition = Condition::new(field, operator, value);
564 Ok(ConditionGroup::single(condition))
565 }
566
567 fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
568 let parts: Vec<&str> = conditions_str.split("&&").collect();
570
571 let mut conditions = Vec::new();
572 for part in parts {
573 let trimmed = part.trim();
574 let condition = self.parse_simple_condition(trimmed)?;
575 conditions.push(condition);
576 }
577
578 if conditions.is_empty() {
580 return Err(RuleEngineError::ParseError {
581 message: "No conditions found".to_string(),
582 });
583 }
584
585 let mut iter = conditions.into_iter();
586 let mut result = iter.next().unwrap();
587 for condition in iter {
588 result = ConditionGroup::and(result, condition);
589 }
590
591 Ok(result)
592 }
593
594 fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
595 let condition_regex = Regex::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#).map_err(|e| {
597 RuleEngineError::ParseError {
598 message: format!("Simple condition regex error: {}", e),
599 }
600 })?;
601
602 let captures =
603 condition_regex
604 .captures(clause)
605 .ok_or_else(|| RuleEngineError::ParseError {
606 message: format!("Invalid simple condition format: {}", clause),
607 })?;
608
609 let field = captures.get(1).unwrap().as_str().to_string();
610 let operator_str = captures.get(2).unwrap().as_str();
611 let value_str = captures.get(3).unwrap().as_str().trim();
612
613 let operator =
614 Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
615 operator: operator_str.to_string(),
616 })?;
617
618 let value = self.parse_value(value_str)?;
619
620 let condition = Condition::new(field, operator, value);
621 Ok(ConditionGroup::single(condition))
622 }
623
624 fn parse_value(&self, value_str: &str) -> Result<Value> {
625 let trimmed = value_str.trim();
626
627 if (trimmed.starts_with('"') && trimmed.ends_with('"'))
629 || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
630 {
631 let unquoted = &trimmed[1..trimmed.len() - 1];
632 return Ok(Value::String(unquoted.to_string()));
633 }
634
635 if trimmed.eq_ignore_ascii_case("true") {
637 return Ok(Value::Boolean(true));
638 }
639 if trimmed.eq_ignore_ascii_case("false") {
640 return Ok(Value::Boolean(false));
641 }
642
643 if trimmed.eq_ignore_ascii_case("null") {
645 return Ok(Value::Null);
646 }
647
648 if let Ok(int_val) = trimmed.parse::<i64>() {
650 return Ok(Value::Integer(int_val));
651 }
652
653 if let Ok(float_val) = trimmed.parse::<f64>() {
654 return Ok(Value::Number(float_val));
655 }
656
657 if trimmed.contains('.') {
659 return Ok(Value::String(trimmed.to_string()));
660 }
661
662 Ok(Value::String(trimmed.to_string()))
664 }
665
666 fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
667 let statements: Vec<&str> = then_clause
668 .split(';')
669 .map(|s| s.trim())
670 .filter(|s| !s.is_empty())
671 .collect();
672
673 let mut actions = Vec::new();
674
675 for statement in statements {
676 let action = self.parse_action_statement(statement)?;
677 actions.push(action);
678 }
679
680 Ok(actions)
681 }
682
683 fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
684 let trimmed = statement.trim();
685
686 let method_regex = Regex::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#).map_err(|e| {
688 RuleEngineError::ParseError {
689 message: format!("Method regex error: {}", e),
690 }
691 })?;
692
693 if let Some(captures) = method_regex.captures(trimmed) {
694 let object = captures.get(1).unwrap().as_str().to_string();
695 let method = captures.get(2).unwrap().as_str().to_string();
696 let args_str = captures.get(3).unwrap().as_str();
697
698 let args = if args_str.trim().is_empty() {
699 Vec::new()
700 } else {
701 self.parse_method_args(args_str)?
702 };
703
704 return Ok(ActionType::MethodCall {
705 object,
706 method,
707 args,
708 });
709 }
710
711 if let Some(eq_pos) = trimmed.find('=') {
713 let field = trimmed[..eq_pos].trim().to_string();
714 let value_str = trimmed[eq_pos + 1..].trim();
715 let value = self.parse_value(value_str)?;
716
717 return Ok(ActionType::Set { field, value });
718 }
719
720 let func_regex =
722 Regex::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#).map_err(|e| RuleEngineError::ParseError {
723 message: format!("Function regex error: {}", e),
724 })?;
725
726 if let Some(captures) = func_regex.captures(trimmed) {
727 let function_name = captures.get(1).unwrap().as_str();
728 let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
729
730 match function_name.to_lowercase().as_str() {
731 "update" => {
732 let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
734 stripped.to_string()
735 } else {
736 args_str.to_string()
737 };
738 Ok(ActionType::Update {
739 object: object_name,
740 })
741 }
742 "log" => {
743 let message = if args_str.is_empty() {
744 "Log message".to_string()
745 } else {
746 let value = self.parse_value(args_str.trim())?;
747 value.to_string()
748 };
749 Ok(ActionType::Log { message })
750 }
751 "activateagendagroup" | "activate_agenda_group" => {
752 let agenda_group = if args_str.is_empty() {
753 return Err(RuleEngineError::ParseError {
754 message: "ActivateAgendaGroup requires agenda group name".to_string(),
755 });
756 } else {
757 let value = self.parse_value(args_str.trim())?;
758 match value {
759 Value::String(s) => s,
760 _ => value.to_string(),
761 }
762 };
763 Ok(ActionType::ActivateAgendaGroup {
764 group: agenda_group,
765 })
766 }
767 "schedulerule" | "schedule_rule" => {
768 let parts: Vec<&str> = args_str.split(',').collect();
770 if parts.len() != 2 {
771 return Err(RuleEngineError::ParseError {
772 message: "ScheduleRule requires delay_ms and rule_name".to_string(),
773 });
774 }
775
776 let delay_ms = self.parse_value(parts[0].trim())?;
777 let rule_name = self.parse_value(parts[1].trim())?;
778
779 let delay_ms = match delay_ms {
780 Value::Integer(i) => i as u64,
781 Value::Number(f) => f as u64,
782 _ => {
783 return Err(RuleEngineError::ParseError {
784 message: "ScheduleRule delay_ms must be a number".to_string(),
785 })
786 }
787 };
788
789 let rule_name = match rule_name {
790 Value::String(s) => s,
791 _ => rule_name.to_string(),
792 };
793
794 Ok(ActionType::ScheduleRule {
795 delay_ms,
796 rule_name,
797 })
798 }
799 "completeworkflow" | "complete_workflow" => {
800 let workflow_id = if args_str.is_empty() {
801 return Err(RuleEngineError::ParseError {
802 message: "CompleteWorkflow requires workflow_id".to_string(),
803 });
804 } else {
805 let value = self.parse_value(args_str.trim())?;
806 match value {
807 Value::String(s) => s,
808 _ => value.to_string(),
809 }
810 };
811 Ok(ActionType::CompleteWorkflow {
812 workflow_name: workflow_id,
813 })
814 }
815 "setworkflowdata" | "set_workflow_data" => {
816 let data_str = args_str.trim();
818
819 let (key, value) = if let Some(eq_pos) = data_str.find('=') {
821 let key = data_str[..eq_pos].trim().trim_matches('"');
822 let value_str = data_str[eq_pos + 1..].trim();
823 let value = self.parse_value(value_str)?;
824 (key.to_string(), value)
825 } else {
826 return Err(RuleEngineError::ParseError {
827 message: "SetWorkflowData data must be in key=value format".to_string(),
828 });
829 };
830
831 Ok(ActionType::SetWorkflowData { key, value })
832 }
833 _ => {
834 let params = if args_str.is_empty() {
836 HashMap::new()
837 } else {
838 self.parse_function_args_as_params(args_str)?
839 };
840
841 Ok(ActionType::Custom {
842 action_type: function_name.to_string(),
843 params,
844 })
845 }
846 }
847 } else {
848 Ok(ActionType::Custom {
850 action_type: "statement".to_string(),
851 params: {
852 let mut params = HashMap::new();
853 params.insert("statement".to_string(), Value::String(trimmed.to_string()));
854 params
855 },
856 })
857 }
858 }
859
860 fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
861 if args_str.trim().is_empty() {
862 return Ok(Vec::new());
863 }
864
865 let mut args = Vec::new();
867 let parts: Vec<&str> = args_str.split(',').collect();
868
869 for part in parts {
870 let trimmed = part.trim();
871
872 if trimmed.contains('+')
874 || trimmed.contains('-')
875 || trimmed.contains('*')
876 || trimmed.contains('/')
877 {
878 args.push(Value::String(trimmed.to_string()));
880 } else {
881 args.push(self.parse_value(trimmed)?);
882 }
883 }
884
885 Ok(args)
886 }
887
888 fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
890 let mut params = HashMap::new();
891
892 if args_str.trim().is_empty() {
893 return Ok(params);
894 }
895
896 let parts: Vec<&str> = args_str.split(',').collect();
898 for (i, part) in parts.iter().enumerate() {
899 let trimmed = part.trim();
900 let value = self.parse_value(trimmed)?;
901
902 params.insert(i.to_string(), value);
904 }
905
906 Ok(params)
907 }
908}
909
910#[cfg(test)]
911mod tests {
912 use super::GRLParser;
913
914 #[test]
915 fn test_parse_simple_rule() {
916 let grl = r#"
917 rule "CheckAge" salience 10 {
918 when
919 User.Age >= 18
920 then
921 log("User is adult");
922 }
923 "#;
924
925 let rules = GRLParser::parse_rules(grl).unwrap();
926 assert_eq!(rules.len(), 1);
927 let rule = &rules[0];
928 assert_eq!(rule.name, "CheckAge");
929 assert_eq!(rule.salience, 10);
930 assert_eq!(rule.actions.len(), 1);
931 }
932
933 #[test]
934 fn test_parse_complex_condition() {
935 let grl = r#"
936 rule "ComplexRule" {
937 when
938 User.Age >= 18 && User.Country == "US"
939 then
940 User.Qualified = true;
941 }
942 "#;
943
944 let rules = GRLParser::parse_rules(grl).unwrap();
945 assert_eq!(rules.len(), 1);
946 let rule = &rules[0];
947 assert_eq!(rule.name, "ComplexRule");
948 }
949
950 #[test]
951 fn test_parse_new_syntax_with_parentheses() {
952 let grl = r#"
953 rule "Default Rule" salience 10 {
954 when
955 (user.age >= 18)
956 then
957 set(user.status, "approved");
958 }
959 "#;
960
961 let rules = GRLParser::parse_rules(grl).unwrap();
962 assert_eq!(rules.len(), 1);
963 let rule = &rules[0];
964 assert_eq!(rule.name, "Default Rule");
965 assert_eq!(rule.salience, 10);
966 assert_eq!(rule.actions.len(), 1);
967
968 match &rule.actions[0] {
970 crate::types::ActionType::Custom {
971 action_type,
972 params,
973 } => {
974 assert_eq!(action_type, "set");
975 assert_eq!(
976 params.get("0"),
977 Some(&crate::types::Value::String("user.status".to_string()))
978 );
979 assert_eq!(
980 params.get("1"),
981 Some(&crate::types::Value::String("approved".to_string()))
982 );
983 }
984 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
985 }
986 }
987
988 #[test]
989 fn test_parse_complex_nested_conditions() {
990 let grl = r#"
991 rule "Complex Business Rule" salience 10 {
992 when
993 (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
994 then
995 apply_discount(20000);
996 }
997 "#;
998
999 let rules = GRLParser::parse_rules(grl).unwrap();
1000 assert_eq!(rules.len(), 1);
1001 let rule = &rules[0];
1002 assert_eq!(rule.name, "Complex Business Rule");
1003 assert_eq!(rule.salience, 10);
1004 assert_eq!(rule.actions.len(), 1);
1005
1006 match &rule.actions[0] {
1008 crate::types::ActionType::Custom {
1009 action_type,
1010 params,
1011 } => {
1012 assert_eq!(action_type, "apply_discount");
1013 assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1014 }
1015 _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1016 }
1017 }
1018
1019 #[test]
1020 fn test_parse_no_loop_attribute() {
1021 let grl = r#"
1022 rule "NoLoopRule" no-loop salience 15 {
1023 when
1024 User.Score < 100
1025 then
1026 set(User.Score, User.Score + 10);
1027 }
1028 "#;
1029
1030 let rules = GRLParser::parse_rules(grl).unwrap();
1031 assert_eq!(rules.len(), 1);
1032 let rule = &rules[0];
1033 assert_eq!(rule.name, "NoLoopRule");
1034 assert_eq!(rule.salience, 15);
1035 assert!(rule.no_loop, "Rule should have no-loop=true");
1036 }
1037
1038 #[test]
1039 fn test_parse_no_loop_different_positions() {
1040 let grl1 = r#"
1042 rule "Rule1" no-loop salience 10 {
1043 when User.Age >= 18
1044 then log("adult");
1045 }
1046 "#;
1047
1048 let grl2 = r#"
1050 rule "Rule2" salience 10 no-loop {
1051 when User.Age >= 18
1052 then log("adult");
1053 }
1054 "#;
1055
1056 let rules1 = GRLParser::parse_rules(grl1).unwrap();
1057 let rules2 = GRLParser::parse_rules(grl2).unwrap();
1058
1059 assert_eq!(rules1.len(), 1);
1060 assert_eq!(rules2.len(), 1);
1061
1062 assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1063 assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1064
1065 assert_eq!(rules1[0].salience, 10);
1066 assert_eq!(rules2[0].salience, 10);
1067 }
1068
1069 #[test]
1070 fn test_parse_without_no_loop() {
1071 let grl = r#"
1072 rule "RegularRule" salience 5 {
1073 when
1074 User.Active == true
1075 then
1076 log("active user");
1077 }
1078 "#;
1079
1080 let rules = GRLParser::parse_rules(grl).unwrap();
1081 assert_eq!(rules.len(), 1);
1082 let rule = &rules[0];
1083 assert_eq!(rule.name, "RegularRule");
1084 assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1085 }
1086
1087 #[test]
1088 fn test_parse_exists_pattern() {
1089 let grl = r#"
1090 rule "ExistsRule" salience 20 {
1091 when
1092 exists(Customer.tier == "VIP")
1093 then
1094 System.premiumActive = true;
1095 }
1096 "#;
1097
1098 let rules = GRLParser::parse_rules(grl).unwrap();
1099 assert_eq!(rules.len(), 1);
1100 let rule = &rules[0];
1101 assert_eq!(rule.name, "ExistsRule");
1102 assert_eq!(rule.salience, 20);
1103
1104 match &rule.conditions {
1106 crate::engine::rule::ConditionGroup::Exists(_) => {
1107 }
1109 _ => panic!(
1110 "Expected EXISTS condition group, got: {:?}",
1111 rule.conditions
1112 ),
1113 }
1114 }
1115
1116 #[test]
1117 fn test_parse_forall_pattern() {
1118 let grl = r#"
1119 rule "ForallRule" salience 15 {
1120 when
1121 forall(Order.status == "processed")
1122 then
1123 Shipping.enabled = true;
1124 }
1125 "#;
1126
1127 let rules = GRLParser::parse_rules(grl).unwrap();
1128 assert_eq!(rules.len(), 1);
1129 let rule = &rules[0];
1130 assert_eq!(rule.name, "ForallRule");
1131
1132 match &rule.conditions {
1134 crate::engine::rule::ConditionGroup::Forall(_) => {
1135 }
1137 _ => panic!(
1138 "Expected FORALL condition group, got: {:?}",
1139 rule.conditions
1140 ),
1141 }
1142 }
1143
1144 #[test]
1145 fn test_parse_combined_patterns() {
1146 let grl = r#"
1147 rule "CombinedRule" salience 25 {
1148 when
1149 exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1150 then
1151 System.vipMode = true;
1152 }
1153 "#;
1154
1155 let rules = GRLParser::parse_rules(grl).unwrap();
1156 assert_eq!(rules.len(), 1);
1157 let rule = &rules[0];
1158 assert_eq!(rule.name, "CombinedRule");
1159
1160 match &rule.conditions {
1162 crate::engine::rule::ConditionGroup::Compound {
1163 left,
1164 operator,
1165 right,
1166 } => {
1167 assert_eq!(*operator, crate::types::LogicalOperator::And);
1168
1169 match left.as_ref() {
1171 crate::engine::rule::ConditionGroup::Exists(_) => {
1172 }
1174 _ => panic!("Expected EXISTS in left side, got: {:?}", left),
1175 }
1176
1177 match right.as_ref() {
1179 crate::engine::rule::ConditionGroup::Not(inner) => {
1180 match inner.as_ref() {
1181 crate::engine::rule::ConditionGroup::Exists(_) => {
1182 }
1184 _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
1185 }
1186 }
1187 _ => panic!("Expected NOT in right side, got: {:?}", right),
1188 }
1189 }
1190 _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
1191 }
1192 }
1193}