1use crate::errors::RuleEngineError;
87use crate::{Facts, Value};
88use super::backward_engine::{BackwardEngine, BackwardConfig};
89use super::search::SearchStrategy;
90use super::query::{QueryResult, QueryStats, ProofTrace};
91
92use std::collections::HashMap;
93
94#[derive(Debug, Clone, PartialEq)]
96pub enum GRLSearchStrategy {
97 DepthFirst,
98 BreadthFirst,
99 Iterative,
100}
101
102impl Default for GRLSearchStrategy {
103 fn default() -> Self {
104 GRLSearchStrategy::DepthFirst
105 }
106}
107
108#[derive(Debug, Clone)]
110pub struct QueryAction {
111 pub assignments: Vec<(String, String)>,
113 pub calls: Vec<String>,
115}
116
117impl QueryAction {
118 pub fn new() -> Self {
119 QueryAction {
120 assignments: Vec::new(),
121 calls: Vec::new(),
122 }
123 }
124
125 pub fn execute(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
127 for (var_name, value_str) in &self.assignments {
129 let value = if value_str == "true" {
131 Value::Boolean(true)
132 } else if value_str == "false" {
133 Value::Boolean(false)
134 } else if let Ok(n) = value_str.parse::<f64>() {
135 Value::Number(n)
136 } else {
137 let cleaned = value_str.trim_matches('"');
139 Value::String(cleaned.to_string())
140 };
141
142 facts.set(var_name, value);
143 }
144
145 for call in &self.calls {
147 self.execute_function_call(call)?;
148 }
149
150 Ok(())
151 }
152
153 fn execute_function_call(&self, call: &str) -> Result<(), RuleEngineError> {
155 let call = call.trim();
156
157 if let Some(open_paren) = call.find('(') {
159 let func_name = call[..open_paren].trim();
160
161 if let Some(close_paren) = call.rfind(')') {
163 let args_str = &call[open_paren + 1..close_paren];
164
165 match func_name {
166 "LogMessage" => {
167 let message = args_str.trim().trim_matches('"').trim_matches('\'');
169 println!("[LOG] {}", message);
170 }
171 "Request" => {
172 let message = args_str.trim().trim_matches('"').trim_matches('\'');
174 println!("[REQUEST] {}", message);
175 }
176 "Print" => {
177 let message = args_str.trim().trim_matches('"').trim_matches('\'');
179 println!("{}", message);
180 }
181 "Debug" => {
182 let message = args_str.trim().trim_matches('"').trim_matches('\'');
184 eprintln!("[DEBUG] {}", message);
185 }
186 other => {
187 eprintln!("[WARNING] Unknown function call in query action: {}({})", other, args_str);
189 }
190 }
191 } else {
192 return Err(RuleEngineError::ParseError {
193 message: format!("Malformed function call (missing closing paren): {}", call),
194 });
195 }
196 } else {
197 return Err(RuleEngineError::ParseError {
198 message: format!("Malformed function call (missing opening paren): {}", call),
199 });
200 }
201
202 Ok(())
203 }
204}
205
206#[derive(Debug, Clone)]
208pub struct GRLQuery {
209 pub name: String,
211
212 pub goal: String,
214
215 pub strategy: GRLSearchStrategy,
217
218 pub max_depth: usize,
220
221 pub max_solutions: usize,
223
224 pub enable_memoization: bool,
226
227 pub on_success: Option<QueryAction>,
229
230 pub on_failure: Option<QueryAction>,
232
233 pub on_missing: Option<QueryAction>,
235
236 pub params: HashMap<String, String>, pub when_condition: Option<String>,
241}
242
243impl GRLQuery {
244 pub fn new(name: String, goal: String) -> Self {
246 GRLQuery {
247 name,
248 goal,
249 strategy: GRLSearchStrategy::default(),
250 max_depth: 10,
251 max_solutions: 1,
252 enable_memoization: true,
253 on_success: None,
254 on_failure: None,
255 on_missing: None,
256 params: HashMap::new(),
257 when_condition: None,
258 }
259 }
260
261 pub fn with_strategy(mut self, strategy: GRLSearchStrategy) -> Self {
263 self.strategy = strategy;
264 self
265 }
266
267 pub fn with_max_depth(mut self, max_depth: usize) -> Self {
269 self.max_depth = max_depth;
270 self
271 }
272
273 pub fn with_max_solutions(mut self, max_solutions: usize) -> Self {
275 self.max_solutions = max_solutions;
276 self
277 }
278
279 pub fn with_memoization(mut self, enable: bool) -> Self {
281 self.enable_memoization = enable;
282 self
283 }
284
285 pub fn with_on_success(mut self, action: QueryAction) -> Self {
287 self.on_success = Some(action);
288 self
289 }
290
291 pub fn with_on_failure(mut self, action: QueryAction) -> Self {
293 self.on_failure = Some(action);
294 self
295 }
296
297 pub fn with_on_missing(mut self, action: QueryAction) -> Self {
299 self.on_missing = Some(action);
300 self
301 }
302
303 pub fn with_param(mut self, name: String, type_name: String) -> Self {
305 self.params.insert(name, type_name);
306 self
307 }
308
309 pub fn with_when(mut self, condition: String) -> Self {
311 self.when_condition = Some(condition);
312 self
313 }
314
315 pub fn should_execute(&self, _facts: &Facts) -> Result<bool, RuleEngineError> {
317 if self.when_condition.is_none() {
319 return Ok(true);
320 }
321
322 if let Some(ref cond_str) = self.when_condition {
324 use crate::backward::expression::ExpressionParser;
325
326 match ExpressionParser::parse(cond_str) {
327 Ok(expr) => Ok(expr.is_satisfied(_facts)),
328 Err(e) => Err(e),
329 }
330 } else {
331 Ok(true)
332 }
333 }
334
335 pub fn execute_success_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
337 if let Some(ref action) = self.on_success {
338 action.execute(facts)?;
339 }
340 Ok(())
341 }
342
343 pub fn execute_failure_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
345 if let Some(ref action) = self.on_failure {
346 action.execute(facts)?;
347 }
348 Ok(())
349 }
350
351 pub fn execute_missing_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
353 if let Some(ref action) = self.on_missing {
354 action.execute(facts)?;
355 }
356 Ok(())
357 }
358
359 pub fn to_config(&self) -> BackwardConfig {
361 let search_strategy = match self.strategy {
362 GRLSearchStrategy::DepthFirst => SearchStrategy::DepthFirst,
363 GRLSearchStrategy::BreadthFirst => SearchStrategy::BreadthFirst,
364 GRLSearchStrategy::Iterative => SearchStrategy::Iterative,
365 };
366
367 BackwardConfig {
368 strategy: search_strategy,
369 max_depth: self.max_depth,
370 enable_memoization: self.enable_memoization,
371 max_solutions: self.max_solutions,
372 }
373 }
374}
375
376pub struct GRLQueryParser;
378
379impl GRLQueryParser {
380 pub fn parse(input: &str) -> Result<GRLQuery, RuleEngineError> {
393 let input = input.trim();
394
395 let name = Self::extract_query_name(input)?;
397
398 let goal = Self::extract_goal(input)?;
400
401 let mut query = GRLQuery::new(name, goal);
403
404 if let Some(strategy) = Self::extract_strategy(input) {
406 query.strategy = strategy;
407 }
408
409 if let Some(max_depth) = Self::extract_max_depth(input) {
410 query.max_depth = max_depth;
411 }
412
413 if let Some(max_solutions) = Self::extract_max_solutions(input) {
414 query.max_solutions = max_solutions;
415 }
416
417 if let Some(enable_memo) = Self::extract_memoization(input) {
418 query.enable_memoization = enable_memo;
419 }
420
421 if let Some(action) = Self::extract_on_success(input)? {
423 query.on_success = Some(action);
424 }
425
426 if let Some(action) = Self::extract_on_failure(input)? {
427 query.on_failure = Some(action);
428 }
429
430 if let Some(action) = Self::extract_on_missing(input)? {
431 query.on_missing = Some(action);
432 }
433
434 if let Some(condition) = Self::extract_when_condition(input)? {
436 query.when_condition = Some(condition);
437 }
438
439 Ok(query)
440 }
441
442 fn extract_query_name(input: &str) -> Result<String, RuleEngineError> {
443 let re = regex::Regex::new(r#"query\s+"([^"]+)"\s*\{"#).unwrap();
444 if let Some(caps) = re.captures(input) {
445 Ok(caps[1].to_string())
446 } else {
447 Err(RuleEngineError::ParseError {
448 message: "Invalid query syntax: missing query name".to_string(),
449 })
450 }
451 }
452
453 fn extract_goal(input: &str) -> Result<String, RuleEngineError> {
454 if let Some(goal_start) = input.find("goal:") {
456 let after_goal = &input[goal_start + 5..]; let goal_end = Self::find_goal_end(after_goal)?;
461 let goal_str = after_goal[..goal_end].trim().to_string();
462
463 if goal_str.is_empty() {
464 return Err(RuleEngineError::ParseError {
465 message: "Invalid query syntax: empty goal".to_string(),
466 });
467 }
468
469 Ok(goal_str)
470 } else {
471 Err(RuleEngineError::ParseError {
472 message: "Invalid query syntax: missing goal".to_string(),
473 })
474 }
475 }
476
477 fn find_goal_end(input: &str) -> Result<usize, RuleEngineError> {
478 let mut paren_depth = 0;
479 let mut in_string = false;
480 let mut escape_next = false;
481
482 for (i, ch) in input.chars().enumerate() {
483 if escape_next {
484 escape_next = false;
485 continue;
486 }
487
488 match ch {
489 '\\' if in_string => escape_next = true,
490 '"' => in_string = !in_string,
491 '(' if !in_string => paren_depth += 1,
492 ')' if !in_string => {
493 if paren_depth == 0 {
494 return Err(RuleEngineError::ParseError {
495 message: format!("Parse error: Unexpected closing parenthesis at position {}", i),
496 });
497 }
498 paren_depth -= 1;
499 }
500 '\n' if !in_string && paren_depth == 0 => return Ok(i),
501 _ => {}
502 }
503 }
504
505 if in_string {
506 return Err(RuleEngineError::ParseError {
507 message: "Parse error: Unclosed string in goal".to_string(),
508 });
509 }
510
511 if paren_depth > 0 {
512 return Err(RuleEngineError::ParseError {
513 message: format!("Parse error: {} unclosed parentheses in goal", paren_depth),
514 });
515 }
516
517 Ok(input.len())
519 }
520
521 fn extract_strategy(input: &str) -> Option<GRLSearchStrategy> {
522 let re = regex::Regex::new(r"strategy:\s*([a-z-]+)").unwrap();
523 re.captures(input).and_then(|caps| {
524 match caps[1].trim() {
525 "depth-first" => Some(GRLSearchStrategy::DepthFirst),
526 "breadth-first" => Some(GRLSearchStrategy::BreadthFirst),
527 "iterative" => Some(GRLSearchStrategy::Iterative),
528 _ => None,
529 }
530 })
531 }
532
533 fn extract_max_depth(input: &str) -> Option<usize> {
534 let re = regex::Regex::new(r"max-depth:\s*(\d+)").unwrap();
535 re.captures(input)
536 .and_then(|caps| caps[1].parse().ok())
537 }
538
539 fn extract_max_solutions(input: &str) -> Option<usize> {
540 let re = regex::Regex::new(r"max-solutions:\s*(\d+)").unwrap();
541 re.captures(input)
542 .and_then(|caps| caps[1].parse().ok())
543 }
544
545 fn extract_memoization(input: &str) -> Option<bool> {
546 let re = regex::Regex::new(r"enable-memoization:\s*(true|false)").unwrap();
547 re.captures(input).and_then(|caps| {
548 match caps[1].trim() {
549 "true" => Some(true),
550 "false" => Some(false),
551 _ => None,
552 }
553 })
554 }
555
556 fn extract_on_success(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
557 Self::extract_action_block(input, "on-success")
558 }
559
560 fn extract_on_failure(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
561 Self::extract_action_block(input, "on-failure")
562 }
563
564 fn extract_on_missing(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
565 Self::extract_action_block(input, "on-missing")
566 }
567
568 fn extract_action_block(input: &str, action_name: &str) -> Result<Option<QueryAction>, RuleEngineError> {
569 let pattern = format!(r"{}:\s*\{{([^}}]+)\}}", action_name);
570 let re = regex::Regex::new(&pattern).unwrap();
571
572 if let Some(caps) = re.captures(input) {
573 let block = caps[1].trim();
574 let mut action = QueryAction::new();
575
576 let assign_re = regex::Regex::new(r"([A-Za-z_][A-Za-z0-9_.]*)\s*=\s*([^;]+);").unwrap();
578 for caps in assign_re.captures_iter(block) {
579 let var_name = caps[1].trim().to_string();
580 let value_str = caps[2].trim().to_string();
581 action.assignments.push((var_name, value_str));
582 }
583
584 let call_re = regex::Regex::new(r"([A-Za-z_][A-Za-z0-9_]*\([^)]*\));").unwrap();
586 for caps in call_re.captures_iter(block) {
587 action.calls.push(caps[1].trim().to_string());
588 }
589
590 Ok(Some(action))
591 } else {
592 Ok(None)
593 }
594 }
595
596 fn extract_when_condition(input: &str) -> Result<Option<String>, RuleEngineError> {
597 let re = regex::Regex::new(r"when:\s*([^\n}]+)").unwrap();
598 if let Some(caps) = re.captures(input) {
599 let condition_str = caps[1].trim().to_string();
600 Ok(Some(condition_str))
601 } else {
602 Ok(None)
603 }
604 }
605
606 pub fn parse_queries(input: &str) -> Result<Vec<GRLQuery>, RuleEngineError> {
608 let mut queries = Vec::new();
609
610 let parts: Vec<&str> = input.split("query").collect();
613
614 for part in parts.iter().skip(1) { let query_str = format!("query{}", part);
616 if let Some(end_idx) = find_matching_brace(&query_str) {
618 let complete_query = &query_str[..end_idx];
619 if let Ok(query) = Self::parse(complete_query) {
620 queries.push(query);
621 }
622 }
623 }
624
625 Ok(queries)
626 }
627}
628
629fn find_matching_brace(input: &str) -> Option<usize> {
631 let mut depth = 0;
632 let mut in_string = false;
633 let mut escape_next = false;
634
635 for (i, ch) in input.chars().enumerate() {
636 if escape_next {
637 escape_next = false;
638 continue;
639 }
640
641 match ch {
642 '\\' => escape_next = true,
643 '"' => in_string = !in_string,
644 '{' if !in_string => depth += 1,
645 '}' if !in_string => {
646 depth -= 1;
647 if depth == 0 {
648 return Some(i + 1);
649 }
650 }
651 _ => {}
652 }
653 }
654
655 None
656}
657
658pub struct GRLQueryExecutor;
660
661impl GRLQueryExecutor {
662 pub fn execute(
664 query: &GRLQuery,
665 bc_engine: &mut BackwardEngine,
666 facts: &mut Facts,
667 ) -> Result<QueryResult, RuleEngineError> {
668 if !query.should_execute(facts)? {
670 return Ok(QueryResult {
671 provable: false,
672 bindings: HashMap::new(),
673 proof_trace: ProofTrace { goal: String::new(), steps: Vec::new() },
674 missing_facts: Vec::new(),
675 stats: QueryStats::default(),
676 solutions: Vec::new(),
677 });
678 }
679
680 bc_engine.set_config(query.to_config());
682
683 let result = if query.goal.contains("&&") && query.goal.contains("||") {
685 Self::execute_complex_goal(&query.goal, bc_engine, facts)?
688 } else if query.goal.contains("||") {
689 Self::execute_compound_or_goal(&query.goal, bc_engine, facts)?
691 } else if query.goal.contains("&&") {
692 Self::execute_compound_and_goal(&query.goal, bc_engine, facts)?
694 } else {
695 bc_engine.query(&query.goal, facts)?
697 };
698
699 if result.provable {
701 query.execute_success_actions(facts)?;
702 } else if !result.missing_facts.is_empty() {
703 query.execute_missing_actions(facts)?;
704 } else {
705 query.execute_failure_actions(facts)?;
706 }
707
708 Ok(result)
709 }
710
711 fn execute_compound_and_goal(
713 goal_expr: &str,
714 bc_engine: &mut BackwardEngine,
715 facts: &mut Facts,
716 ) -> Result<QueryResult, RuleEngineError> {
717 let sub_goals: Vec<&str> = goal_expr.split("&&").map(|s| s.trim()).collect();
718
719 let mut all_provable = true;
720 let mut combined_bindings = HashMap::new();
721 let mut all_missing = Vec::new();
722 let mut combined_stats = QueryStats::default();
723
724 for (i, sub_goal) in sub_goals.iter().enumerate() {
725 let goal_satisfied = if sub_goal.contains("!=") {
727 use crate::backward::expression::ExpressionParser;
729
730 match ExpressionParser::parse(sub_goal) {
731 Ok(expr) => expr.is_satisfied(facts),
732 Err(_) => false,
733 }
734 } else {
735 let result = bc_engine.query(sub_goal, facts)?;
737 result.provable
738 };
739
740 if !goal_satisfied {
741 all_provable = false;
742 }
743
744 }
747
748 Ok(QueryResult {
749 provable: all_provable,
750 bindings: combined_bindings,
751 proof_trace: ProofTrace {
752 goal: goal_expr.to_string(),
753 steps: Vec::new()
754 },
755 missing_facts: all_missing,
756 stats: combined_stats,
757 solutions: Vec::new(),
758 })
759 }
760
761 fn execute_compound_or_goal(
763 goal_expr: &str,
764 bc_engine: &mut BackwardEngine,
765 facts: &mut Facts,
766 ) -> Result<QueryResult, RuleEngineError> {
767 let sub_goals: Vec<&str> = goal_expr.split("||").map(|s| s.trim()).collect();
768
769 let mut any_provable = false;
770 let mut combined_bindings = HashMap::new();
771 let mut all_missing = Vec::new();
772 let mut combined_stats = QueryStats::default();
773 let mut all_solutions = Vec::new();
774
775 for sub_goal in sub_goals.iter() {
776 let (goal_satisfied, result_opt) = if sub_goal.contains("!=") {
778 use crate::backward::expression::ExpressionParser;
780
781 match ExpressionParser::parse(sub_goal) {
782 Ok(expr) => (expr.is_satisfied(facts), None),
783 Err(_) => (false, None),
784 }
785 } else {
786 let result = bc_engine.query(sub_goal, facts)?;
788 let provable = result.provable;
789 (provable, Some(result))
790 };
791
792 if goal_satisfied {
793 any_provable = true;
794
795 if let Some(result) = result_opt {
797 combined_bindings.extend(result.bindings);
798 all_missing.extend(result.missing_facts);
799 combined_stats.goals_explored += result.stats.goals_explored;
800 combined_stats.rules_evaluated += result.stats.rules_evaluated;
801 if let Some(dur) = result.stats.duration_ms {
802 combined_stats.duration_ms = Some(combined_stats.duration_ms.unwrap_or(0) + dur);
803 }
804 all_solutions.extend(result.solutions);
805 }
806 }
807 }
808
809 Ok(QueryResult {
810 provable: any_provable,
811 bindings: combined_bindings,
812 proof_trace: ProofTrace {
813 goal: goal_expr.to_string(),
814 steps: Vec::new()
815 },
816 missing_facts: all_missing,
817 stats: combined_stats,
818 solutions: all_solutions,
819 })
820 }
821
822 fn strip_outer_parens(expr: &str) -> &str {
824 let trimmed = expr.trim();
825 if trimmed.starts_with('(') && trimmed.ends_with(')') {
826 let inner = &trimmed[1..trimmed.len()-1];
828 let mut depth = 0;
829 for ch in inner.chars() {
830 match ch {
831 '(' => depth += 1,
832 ')' => {
833 depth -= 1;
834 if depth < 0 {
835 return trimmed;
837 }
838 }
839 _ => {}
840 }
841 }
842 if depth == 0 {
843 return inner.trim();
845 }
846 }
847 trimmed
848 }
849
850 fn execute_complex_goal(
854 goal_expr: &str,
855 bc_engine: &mut BackwardEngine,
856 facts: &mut Facts,
857 ) -> Result<QueryResult, RuleEngineError> {
858 let cleaned_expr = Self::strip_outer_parens(goal_expr);
860
861 let or_parts: Vec<&str> = cleaned_expr.split("||").map(|s| s.trim()).collect();
863
864 let mut any_provable = false;
865 let mut combined_bindings = HashMap::new();
866 let mut all_missing = Vec::new();
867 let mut combined_stats = QueryStats::default();
868 let mut all_solutions = Vec::new();
869
870 for or_part in or_parts.iter() {
871 let cleaned_part = Self::strip_outer_parens(or_part);
873
874 let result = if cleaned_part.contains("&&") {
876 Self::execute_compound_and_goal(cleaned_part, bc_engine, facts)?
877 } else {
878 bc_engine.query(cleaned_part, facts)?
879 };
880
881 if result.provable {
882 any_provable = true;
883 combined_bindings.extend(result.bindings);
884 all_missing.extend(result.missing_facts);
885 combined_stats.goals_explored += result.stats.goals_explored;
886 combined_stats.rules_evaluated += result.stats.rules_evaluated;
887 if let Some(dur) = result.stats.duration_ms {
888 combined_stats.duration_ms = Some(combined_stats.duration_ms.unwrap_or(0) + dur);
889 }
890 all_solutions.extend(result.solutions);
891 }
892 }
893
894 Ok(QueryResult {
895 provable: any_provable,
896 bindings: combined_bindings,
897 proof_trace: ProofTrace {
898 goal: goal_expr.to_string(),
899 steps: Vec::new()
900 },
901 missing_facts: all_missing,
902 stats: combined_stats,
903 solutions: all_solutions,
904 })
905 }
906
907 pub fn execute_queries(
909 queries: &[GRLQuery],
910 bc_engine: &mut BackwardEngine,
911 facts: &mut Facts,
912 ) -> Result<Vec<QueryResult>, RuleEngineError> {
913 let mut results = Vec::new();
914
915 for query in queries {
916 let result = Self::execute(query, bc_engine, facts)?;
917 results.push(result);
918 }
919
920 Ok(results)
921 }
922}
923
924#[cfg(test)]
925mod tests {
926 use super::*;
927
928 #[test]
929 fn test_parse_simple_query() {
930 let input = r#"
931 query "TestQuery" {
932 goal: User.IsVIP == true
933 }
934 "#;
935
936 let query = GRLQueryParser::parse(input).unwrap();
937 assert_eq!(query.name, "TestQuery");
938 assert_eq!(query.strategy, GRLSearchStrategy::DepthFirst);
939 assert_eq!(query.max_depth, 10);
940 }
941
942 #[test]
943 fn test_parse_query_with_strategy() {
944 let input = r#"
945 query "TestQuery" {
946 goal: User.IsVIP == true
947 strategy: breadth-first
948 max-depth: 5
949 }
950 "#;
951
952 let query = GRLQueryParser::parse(input).unwrap();
953 assert_eq!(query.strategy, GRLSearchStrategy::BreadthFirst);
954 assert_eq!(query.max_depth, 5);
955 }
956
957 #[test]
958 fn test_parse_query_with_actions() {
959 let input = r#"
960 query "TestQuery" {
961 goal: User.IsVIP == true
962 on-success: {
963 User.DiscountRate = 0.2;
964 LogMessage("VIP confirmed");
965 }
966 }
967 "#;
968
969 let query = GRLQueryParser::parse(input).unwrap();
970 assert!(query.on_success.is_some());
971
972 let action = query.on_success.unwrap();
973 assert_eq!(action.assignments.len(), 1);
974 assert_eq!(action.calls.len(), 1);
975 }
976
977 #[test]
978 fn test_parse_query_with_when_condition() {
979 let input = r#"
980 query "TestQuery" {
981 goal: User.IsVIP == true
982 when: Environment.Mode == "Production"
983 }
984 "#;
985
986 let query = GRLQueryParser::parse(input).unwrap();
987 assert!(query.when_condition.is_some());
988 }
989
990 #[test]
991 fn test_parse_multiple_queries() {
992 let input = r#"
993 query "Query1" {
994 goal: A == true
995 }
996
997 query "Query2" {
998 goal: B == true
999 strategy: breadth-first
1000 }
1001 "#;
1002
1003 let queries = GRLQueryParser::parse_queries(input).unwrap();
1004 assert_eq!(queries.len(), 2);
1005 assert_eq!(queries[0].name, "Query1");
1006 assert_eq!(queries[1].name, "Query2");
1007 }
1008
1009 #[test]
1010 fn test_query_config_conversion() {
1011 let query = GRLQuery::new(
1012 "Test".to_string(),
1013 "X == true".to_string(),
1014 )
1015 .with_strategy(GRLSearchStrategy::BreadthFirst)
1016 .with_max_depth(15)
1017 .with_memoization(false);
1018
1019 let config = query.to_config();
1020 assert_eq!(config.max_depth, 15);
1021 assert_eq!(config.enable_memoization, false);
1022 }
1023
1024 #[test]
1025 fn test_action_execution() {
1026 let mut facts = Facts::new();
1027
1028 let mut action = QueryAction::new();
1029 action.assignments.push((
1030 "User.DiscountRate".to_string(),
1031 "0.2".to_string(),
1032 ));
1033
1034 action.execute(&mut facts).unwrap();
1035
1036 let value = facts.get("User.DiscountRate");
1038 assert!(value.is_some());
1039 }
1040
1041 #[test]
1042 fn test_should_execute_no_condition() {
1043 let query = GRLQuery::new("Q".to_string(), "X == true".to_string());
1044 let facts = Facts::new();
1045 let res = query.should_execute(&facts).unwrap();
1047 assert!(res);
1048 }
1049
1050 #[test]
1051 fn test_should_execute_condition_true() {
1052 let mut facts = Facts::new();
1053 facts.set("Environment.Mode", Value::String("Production".to_string()));
1054
1055 let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
1056 .with_when("Environment.Mode == \"Production\"".to_string());
1057
1058 let res = query.should_execute(&facts).unwrap();
1059 assert!(res, "expected when condition to be satisfied");
1060 }
1061
1062 #[test]
1063 fn test_should_execute_condition_false() {
1064 let mut facts = Facts::new();
1065 facts.set("Environment.Mode", Value::String("Development".to_string()));
1066
1067 let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
1068 .with_when("Environment.Mode == \"Production\"".to_string());
1069
1070 let res = query.should_execute(&facts).unwrap();
1071 assert!(!res, "expected when condition to be unsatisfied");
1072 }
1073
1074 #[test]
1075 fn test_should_execute_parse_error_propagates() {
1076 let facts = Facts::new();
1077 let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
1079 .with_when("Environment.Mode == \"Production".to_string());
1080
1081 let res = query.should_execute(&facts);
1082 assert!(res.is_err(), "expected parse error to propagate");
1083 }
1084
1085 #[test]
1086 fn test_parse_query_with_or_goal() {
1087 let input = r#"
1088 query "TestOR" {
1089 goal: User.IsVIP == true || User.TotalSpent > 10000
1090 }
1091 "#;
1092
1093 let query = GRLQueryParser::parse(input).unwrap();
1094 assert_eq!(query.name, "TestOR");
1095 assert!(query.goal.contains("||"));
1096 }
1097
1098 #[test]
1099 fn test_parse_query_with_complex_goal() {
1100 let input = r#"
1101 query "ComplexQuery" {
1102 goal: (User.IsVIP == true && User.Active == true) || User.TotalSpent > 10000
1103 }
1104 "#;
1105
1106 let query = GRLQueryParser::parse(input).unwrap();
1107 assert!(query.goal.contains("||"));
1108 assert!(query.goal.contains("&&"));
1109 }
1110
1111 #[test]
1112 fn test_parse_query_with_multiple_or_branches() {
1113 let input = r#"
1114 query "MultiOR" {
1115 goal: Employee.IsManager == true || Employee.IsSenior == true || Employee.IsDirector == true
1116 }
1117 "#;
1118
1119 let query = GRLQueryParser::parse(input).unwrap();
1120 let branches: Vec<&str> = query.goal.split("||").collect();
1121 assert_eq!(branches.len(), 3);
1122 }
1123
1124 #[test]
1125 fn test_parse_query_with_parentheses() {
1126 let input = r#"
1127 query "ParenQuery" {
1128 goal: (User.IsVIP == true && User.Active == true) || User.TotalSpent > 10000
1129 }
1130 "#;
1131
1132 let query = GRLQueryParser::parse(input).unwrap();
1133 assert!(query.goal.contains("("));
1134 assert!(query.goal.contains(")"));
1135 assert!(query.goal.contains("||"));
1136 assert!(query.goal.contains("&&"));
1137 }
1138
1139 #[test]
1140 fn test_parse_query_with_nested_parentheses() {
1141 let input = r#"
1142 query "NestedParen" {
1143 goal: ((A == true && B == true) || C == true) && D == true
1144 }
1145 "#;
1146
1147 let query = GRLQueryParser::parse(input).unwrap();
1148 assert_eq!(query.name, "NestedParen");
1149 assert!(query.goal.starts_with("(("));
1151 }
1152
1153 #[test]
1154 fn test_parse_query_unclosed_parenthesis() {
1155 let input = r#"
1156 query "BadParen" {
1157 goal: (User.IsVIP == true && User.Active == true
1158 }
1159 "#;
1160
1161 let result = GRLQueryParser::parse(input);
1162 assert!(result.is_err());
1163 if let Err(e) = result {
1164 let msg = format!("{:?}", e);
1165 assert!(msg.contains("unclosed parentheses") || msg.contains("parenthesis"));
1166 }
1167 }
1168}