1use super::engine::{RhaiScriptEngine, ScriptContext, ScriptEngineConfig};
11use anyhow::{Result, anyhow};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::sync::Arc;
15use tokio::sync::RwLock;
16use tracing::{info, warn};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
24pub enum RulePriority {
25 Lowest = 0,
26 Low = 25,
27 #[default]
28 Normal = 50,
29 High = 75,
30 Highest = 100,
31 Critical = 200,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, Default)]
36pub enum RuleMatchMode {
37 #[default]
39 FirstMatch,
40 AllMatch,
42 AllMatchOrdered,
44 FirstSuccess,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(tag = "type")]
51pub enum RuleAction {
52 ReturnValue { value: serde_json::Value },
54 ExecuteScript { script: String },
56 CallFunction {
58 function: String,
59 args: Vec<serde_json::Value>,
60 },
61 SetVariable {
63 name: String,
64 value: serde_json::Value,
65 },
66 TriggerEvent {
68 event_type: String,
69 data: serde_json::Value,
70 },
71 GotoRule { rule_id: String },
73 Stop,
75 Composite { actions: Vec<RuleAction> },
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct RuleDefinition {
82 pub id: String,
84 pub name: String,
86 #[serde(default)]
88 pub description: String,
89 #[serde(default)]
91 pub priority: RulePriority,
92 #[serde(default = "default_true")]
94 pub enabled: bool,
95 pub condition: String,
97 pub action: RuleAction,
99 #[serde(default)]
101 pub tags: Vec<String>,
102 #[serde(default)]
104 pub metadata: HashMap<String, String>,
105}
106
107fn default_true() -> bool {
108 true
109}
110
111impl RuleDefinition {
112 pub fn new(id: &str, name: &str, condition: &str, action: RuleAction) -> Self {
113 Self {
114 id: id.to_string(),
115 name: name.to_string(),
116 description: String::new(),
117 priority: RulePriority::Normal,
118 enabled: true,
119 condition: condition.to_string(),
120 action,
121 tags: Vec::new(),
122 metadata: HashMap::new(),
123 }
124 }
125
126 pub fn with_priority(mut self, priority: RulePriority) -> Self {
127 self.priority = priority;
128 self
129 }
130
131 pub fn with_description(mut self, desc: &str) -> Self {
132 self.description = desc.to_string();
133 self
134 }
135
136 pub fn with_tag(mut self, tag: &str) -> Self {
137 self.tags.push(tag.to_string());
138 self
139 }
140
141 pub fn disabled(mut self) -> Self {
142 self.enabled = false;
143 self
144 }
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct RuleGroupDefinition {
154 pub id: String,
156 pub name: String,
158 #[serde(default)]
160 pub description: String,
161 #[serde(default)]
163 pub match_mode: RuleMatchMode,
164 pub rule_ids: Vec<String>,
166 #[serde(default = "default_true")]
168 pub enabled: bool,
169 pub default_action: Option<RuleAction>,
171}
172
173impl RuleGroupDefinition {
174 pub fn new(id: &str, name: &str) -> Self {
175 Self {
176 id: id.to_string(),
177 name: name.to_string(),
178 description: String::new(),
179 match_mode: RuleMatchMode::FirstMatch,
180 rule_ids: Vec::new(),
181 enabled: true,
182 default_action: None,
183 }
184 }
185
186 pub fn with_match_mode(mut self, mode: RuleMatchMode) -> Self {
187 self.match_mode = mode;
188 self
189 }
190
191 pub fn with_rules(mut self, rule_ids: Vec<&str>) -> Self {
192 self.rule_ids = rule_ids.into_iter().map(|s| s.to_string()).collect();
193 self
194 }
195
196 pub fn with_default_action(mut self, action: RuleAction) -> Self {
197 self.default_action = Some(action);
198 self
199 }
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct RuleMatchResult {
209 pub rule_id: String,
211 pub matched: bool,
213 pub evaluation_time_ms: u64,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct RuleExecutionResult {
220 pub rule_id: String,
222 pub success: bool,
224 pub result: serde_json::Value,
226 pub error: Option<String>,
228 pub execution_time_ms: u64,
230 pub variable_updates: HashMap<String, serde_json::Value>,
232 pub triggered_events: Vec<(String, serde_json::Value)>,
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct RuleGroupExecutionResult {
239 pub group_id: String,
241 pub match_results: Vec<RuleMatchResult>,
243 pub execution_results: Vec<RuleExecutionResult>,
245 pub final_result: Option<serde_json::Value>,
247 pub any_matched: bool,
249 pub used_default: bool,
251 pub total_time_ms: u64,
253}
254
255pub struct RuleEngine {
261 engine: Arc<RhaiScriptEngine>,
263 rules: Arc<RwLock<HashMap<String, RuleDefinition>>>,
265 groups: Arc<RwLock<HashMap<String, RuleGroupDefinition>>>,
267 event_handlers:
269 Arc<RwLock<HashMap<String, Vec<Box<dyn Fn(&str, &serde_json::Value) + Send + Sync>>>>>,
270}
271
272impl RuleEngine {
273 pub fn new(engine_config: ScriptEngineConfig) -> Result<Self> {
275 let engine = Arc::new(RhaiScriptEngine::new(engine_config)?);
276 Ok(Self {
277 engine,
278 rules: Arc::new(RwLock::new(HashMap::new())),
279 groups: Arc::new(RwLock::new(HashMap::new())),
280 event_handlers: Arc::new(RwLock::new(HashMap::new())),
281 })
282 }
283
284 pub fn with_engine(engine: Arc<RhaiScriptEngine>) -> Self {
286 Self {
287 engine,
288 rules: Arc::new(RwLock::new(HashMap::new())),
289 groups: Arc::new(RwLock::new(HashMap::new())),
290 event_handlers: Arc::new(RwLock::new(HashMap::new())),
291 }
292 }
293
294 pub async fn register_rule(&self, rule: RuleDefinition) -> Result<()> {
296 let mut rules = self.rules.write().await;
297 info!("Registered rule: {} ({})", rule.name, rule.id);
298 rules.insert(rule.id.clone(), rule);
299 Ok(())
300 }
301
302 pub async fn register_rules(&self, rules: Vec<RuleDefinition>) -> Result<()> {
304 for rule in rules {
305 self.register_rule(rule).await?;
306 }
307 Ok(())
308 }
309
310 pub async fn register_group(&self, group: RuleGroupDefinition) -> Result<()> {
312 let mut groups = self.groups.write().await;
313 info!("Registered rule group: {} ({})", group.name, group.id);
314 groups.insert(group.id.clone(), group);
315 Ok(())
316 }
317
318 pub async fn load_rules_from_yaml(&self, path: &str) -> Result<Vec<String>> {
320 let content = tokio::fs::read_to_string(path).await?;
321 let rules: Vec<RuleDefinition> = serde_yaml::from_str(&content)?;
322 let ids: Vec<String> = rules.iter().map(|r| r.id.clone()).collect();
323 self.register_rules(rules).await?;
324 Ok(ids)
325 }
326
327 pub async fn load_rules_from_json(&self, path: &str) -> Result<Vec<String>> {
329 let content = tokio::fs::read_to_string(path).await?;
330 let rules: Vec<RuleDefinition> = serde_json::from_str(&content)?;
331 let ids: Vec<String> = rules.iter().map(|r| r.id.clone()).collect();
332 self.register_rules(rules).await?;
333 Ok(ids)
334 }
335
336 pub async fn evaluate_condition(
338 &self,
339 rule: &RuleDefinition,
340 context: &ScriptContext,
341 ) -> Result<bool> {
342 if !rule.enabled {
343 return Ok(false);
344 }
345
346 let result = self.engine.execute(&rule.condition, context).await?;
347
348 if !result.success {
349 warn!(
350 "Rule {} condition evaluation failed: {:?}",
351 rule.id, result.error
352 );
353 return Ok(false);
354 }
355
356 Ok(match &result.value {
358 serde_json::Value::Bool(b) => *b,
359 serde_json::Value::Number(n) => n.as_i64().unwrap_or(0) != 0,
360 serde_json::Value::String(s) => !s.is_empty() && s != "false" && s != "0",
361 serde_json::Value::Array(arr) => !arr.is_empty(),
362 serde_json::Value::Object(obj) => !obj.is_empty(),
363 serde_json::Value::Null => false,
364 })
365 }
366
367 pub fn execute_action<'a>(
369 &'a self,
370 action: &'a RuleAction,
371 context: &'a mut ScriptContext,
372 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<RuleExecutionResult>> + Send + 'a>>
373 {
374 Box::pin(async move {
375 let start_time = std::time::Instant::now();
376 let mut variable_updates = HashMap::new();
377 let mut triggered_events = Vec::new();
378
379 let result = match action {
380 RuleAction::ReturnValue { value } => value.clone(),
381
382 RuleAction::ExecuteScript { script } => {
383 let result = self.engine.execute(script, context).await?;
384 if !result.success {
385 return Ok(RuleExecutionResult {
386 rule_id: String::new(),
387 success: false,
388 result: serde_json::Value::Null,
389 error: result.error,
390 execution_time_ms: start_time.elapsed().as_millis() as u64,
391 variable_updates,
392 triggered_events,
393 });
394 }
395 result.value
396 }
397
398 RuleAction::CallFunction { function, args } => {
399 let args_str = args
401 .iter()
402 .map(|a| a.to_string())
403 .collect::<Vec<_>>()
404 .join(", ");
405 let script = format!("{}({})", function, args_str);
406 let result = self.engine.execute(&script, context).await?;
407 if !result.success {
408 return Ok(RuleExecutionResult {
409 rule_id: String::new(),
410 success: false,
411 result: serde_json::Value::Null,
412 error: result.error,
413 execution_time_ms: start_time.elapsed().as_millis() as u64,
414 variable_updates,
415 triggered_events,
416 });
417 }
418 result.value
419 }
420
421 RuleAction::SetVariable { name, value } => {
422 context.set_variable(name, value.clone())?;
423 variable_updates.insert(name.clone(), value.clone());
424 serde_json::json!({ "set": name, "value": value })
425 }
426
427 RuleAction::TriggerEvent { event_type, data } => {
428 triggered_events.push((event_type.clone(), data.clone()));
429 let handlers = self.event_handlers.read().await;
431 if let Some(handlers) = handlers.get(event_type) {
432 for handler in handlers {
433 handler(event_type, data);
434 }
435 }
436 serde_json::json!({ "event": event_type, "data": data })
437 }
438
439 RuleAction::GotoRule { rule_id } => {
440 serde_json::json!({ "goto": rule_id })
442 }
443
444 RuleAction::Stop => {
445 serde_json::json!({ "stop": true })
446 }
447
448 RuleAction::Composite { actions } => {
449 let mut results = Vec::new();
451 for sub_action in actions {
452 let sub_result = self.execute_single_action(sub_action, context).await?;
454 if !sub_result.success {
455 return Ok(sub_result);
456 }
457 results.push(sub_result.result);
458 variable_updates.extend(sub_result.variable_updates);
459 triggered_events.extend(sub_result.triggered_events);
460 }
461 serde_json::json!(results)
462 }
463 };
464
465 Ok(RuleExecutionResult {
466 rule_id: String::new(),
467 success: true,
468 result,
469 error: None,
470 execution_time_ms: start_time.elapsed().as_millis() as u64,
471 variable_updates,
472 triggered_events,
473 })
474 })
475 }
476
477 async fn execute_single_action(
479 &self,
480 action: &RuleAction,
481 context: &mut ScriptContext,
482 ) -> Result<RuleExecutionResult> {
483 let start_time = std::time::Instant::now();
484 let mut variable_updates = HashMap::new();
485 let mut triggered_events = Vec::new();
486
487 let result = match action {
488 RuleAction::ReturnValue { value } => value.clone(),
489
490 RuleAction::ExecuteScript { script } => {
491 let result = self.engine.execute(script, context).await?;
492 if !result.success {
493 return Ok(RuleExecutionResult {
494 rule_id: String::new(),
495 success: false,
496 result: serde_json::Value::Null,
497 error: result.error,
498 execution_time_ms: start_time.elapsed().as_millis() as u64,
499 variable_updates,
500 triggered_events,
501 });
502 }
503 result.value
504 }
505
506 RuleAction::CallFunction { function, args } => {
507 let args_str = args
508 .iter()
509 .map(|a| a.to_string())
510 .collect::<Vec<_>>()
511 .join(", ");
512 let script = format!("{}({})", function, args_str);
513 let result = self.engine.execute(&script, context).await?;
514 if !result.success {
515 return Ok(RuleExecutionResult {
516 rule_id: String::new(),
517 success: false,
518 result: serde_json::Value::Null,
519 error: result.error,
520 execution_time_ms: start_time.elapsed().as_millis() as u64,
521 variable_updates,
522 triggered_events,
523 });
524 }
525 result.value
526 }
527
528 RuleAction::SetVariable { name, value } => {
529 context.set_variable(name, value.clone())?;
530 variable_updates.insert(name.clone(), value.clone());
531 serde_json::json!({ "set": name, "value": value })
532 }
533
534 RuleAction::TriggerEvent { event_type, data } => {
535 triggered_events.push((event_type.clone(), data.clone()));
536 let handlers = self.event_handlers.read().await;
537 if let Some(handlers) = handlers.get(event_type) {
538 for handler in handlers {
539 handler(event_type, data);
540 }
541 }
542 serde_json::json!({ "event": event_type, "data": data })
543 }
544
545 RuleAction::GotoRule { rule_id } => {
546 serde_json::json!({ "goto": rule_id })
547 }
548
549 RuleAction::Stop => {
550 serde_json::json!({ "stop": true })
551 }
552
553 RuleAction::Composite { .. } => {
554 return Err(anyhow!("Nested composite actions are not supported"));
556 }
557 };
558
559 Ok(RuleExecutionResult {
560 rule_id: String::new(),
561 success: true,
562 result,
563 error: None,
564 execution_time_ms: start_time.elapsed().as_millis() as u64,
565 variable_updates,
566 triggered_events,
567 })
568 }
569
570 pub async fn execute_rule(
572 &self,
573 rule_id: &str,
574 context: &mut ScriptContext,
575 ) -> Result<Option<RuleExecutionResult>> {
576 let rules = self.rules.read().await;
577 let rule = rules
578 .get(rule_id)
579 .ok_or_else(|| anyhow!("Rule not found: {}", rule_id))?
580 .clone();
581 drop(rules);
582
583 if !self.evaluate_condition(&rule, context).await? {
585 return Ok(None);
586 }
587
588 let mut result = self.execute_action(&rule.action, context).await?;
590 result.rule_id = rule_id.to_string();
591 Ok(Some(result))
592 }
593
594 pub async fn execute_group(
596 &self,
597 group_id: &str,
598 context: &mut ScriptContext,
599 ) -> Result<RuleGroupExecutionResult> {
600 let start_time = std::time::Instant::now();
601
602 let groups = self.groups.read().await;
603 let group = groups
604 .get(group_id)
605 .ok_or_else(|| anyhow!("Rule group not found: {}", group_id))?
606 .clone();
607 drop(groups);
608
609 if !group.enabled {
610 return Ok(RuleGroupExecutionResult {
611 group_id: group_id.to_string(),
612 match_results: Vec::new(),
613 execution_results: Vec::new(),
614 final_result: None,
615 any_matched: false,
616 used_default: false,
617 total_time_ms: start_time.elapsed().as_millis() as u64,
618 });
619 }
620
621 let rules = self.rules.read().await;
623 let mut group_rules: Vec<_> = group
624 .rule_ids
625 .iter()
626 .filter_map(|id| rules.get(id).cloned())
627 .collect();
628 drop(rules);
629
630 group_rules.sort_by(|a, b| b.priority.cmp(&a.priority));
632
633 let mut match_results = Vec::new();
634 let mut execution_results = Vec::new();
635 let mut any_matched = false;
636 let mut final_result = None;
637
638 for rule in group_rules {
639 let eval_start = std::time::Instant::now();
640 let matched = self.evaluate_condition(&rule, context).await?;
641
642 match_results.push(RuleMatchResult {
643 rule_id: rule.id.clone(),
644 matched,
645 evaluation_time_ms: eval_start.elapsed().as_millis() as u64,
646 });
647
648 if !matched {
649 continue;
650 }
651
652 any_matched = true;
653
654 let mut result = self.execute_action(&rule.action, context).await?;
656 result.rule_id = rule.id.clone();
657
658 let should_stop = if let Some(obj) = result.result.as_object() {
660 obj.contains_key("stop")
661 } else {
662 false
663 };
664
665 let goto_rule = if let Some(obj) = result.result.as_object() {
667 obj.get("goto")
668 .and_then(|v| v.as_str())
669 .map(|s| s.to_string())
670 } else {
671 None
672 };
673
674 final_result = Some(result.result.clone());
675 execution_results.push(result);
676
677 if should_stop {
678 break;
679 }
680
681 if let Some(target_rule_id) = goto_rule {
682 if let Some(goto_result) = self.execute_rule(&target_rule_id, context).await? {
684 final_result = Some(goto_result.result.clone());
685 execution_results.push(goto_result);
686 }
687 break;
688 }
689
690 match group.match_mode {
692 RuleMatchMode::FirstMatch | RuleMatchMode::FirstSuccess => break,
693 RuleMatchMode::AllMatch | RuleMatchMode::AllMatchOrdered => continue,
694 }
695 }
696
697 let used_default = !any_matched && group.default_action.is_some();
699 if let Some(ref default_action) = group.default_action
700 && !any_matched
701 {
702 let mut result = self.execute_action(default_action, context).await?;
703 result.rule_id = format!("{}_default", group_id);
704 final_result = Some(result.result.clone());
705 execution_results.push(result);
706 }
707
708 Ok(RuleGroupExecutionResult {
709 group_id: group_id.to_string(),
710 match_results,
711 execution_results,
712 final_result,
713 any_matched,
714 used_default,
715 total_time_ms: start_time.elapsed().as_millis() as u64,
716 })
717 }
718
719 pub async fn execute_all(
721 &self,
722 context: &mut ScriptContext,
723 ) -> Result<Vec<RuleExecutionResult>> {
724 let rules = self.rules.read().await;
725 let mut all_rules: Vec<_> = rules.values().cloned().collect();
726 drop(rules);
727
728 all_rules.sort_by(|a, b| b.priority.cmp(&a.priority));
730
731 let mut results = Vec::new();
732
733 for rule in all_rules {
734 if !rule.enabled {
735 continue;
736 }
737
738 if self.evaluate_condition(&rule, context).await? {
739 let mut result = self.execute_action(&rule.action, context).await?;
740 result.rule_id = rule.id.clone();
741 results.push(result);
742 }
743 }
744
745 Ok(results)
746 }
747
748 pub async fn get_rule(&self, rule_id: &str) -> Option<RuleDefinition> {
750 let rules = self.rules.read().await;
751 rules.get(rule_id).cloned()
752 }
753
754 pub async fn list_rules(&self) -> Vec<RuleDefinition> {
756 let rules = self.rules.read().await;
757 rules.values().cloned().collect()
758 }
759
760 pub async fn list_rules_by_tag(&self, tag: &str) -> Vec<RuleDefinition> {
762 let rules = self.rules.read().await;
763 rules
764 .values()
765 .filter(|r| r.tags.contains(&tag.to_string()))
766 .cloned()
767 .collect()
768 }
769
770 pub async fn unregister_rule(&self, rule_id: &str) -> bool {
772 let mut rules = self.rules.write().await;
773 rules.remove(rule_id).is_some()
774 }
775
776 pub async fn enable_rule(&self, rule_id: &str) -> Result<()> {
778 let mut rules = self.rules.write().await;
779 if let Some(rule) = rules.get_mut(rule_id) {
780 rule.enabled = true;
781 Ok(())
782 } else {
783 Err(anyhow!("Rule not found: {}", rule_id))
784 }
785 }
786
787 pub async fn disable_rule(&self, rule_id: &str) -> Result<()> {
789 let mut rules = self.rules.write().await;
790 if let Some(rule) = rules.get_mut(rule_id) {
791 rule.enabled = false;
792 Ok(())
793 } else {
794 Err(anyhow!("Rule not found: {}", rule_id))
795 }
796 }
797
798 pub async fn rule_count(&self) -> usize {
800 let rules = self.rules.read().await;
801 rules.len()
802 }
803
804 pub async fn clear(&self) {
806 let mut rules = self.rules.write().await;
807 let mut groups = self.groups.write().await;
808 rules.clear();
809 groups.clear();
810 }
811}
812
813pub struct RuleBuilder {
819 rule: RuleDefinition,
820}
821
822impl RuleBuilder {
823 pub fn new(id: &str, name: &str) -> Self {
824 Self {
825 rule: RuleDefinition {
826 id: id.to_string(),
827 name: name.to_string(),
828 description: String::new(),
829 priority: RulePriority::Normal,
830 enabled: true,
831 condition: "true".to_string(),
832 action: RuleAction::Stop,
833 tags: Vec::new(),
834 metadata: HashMap::new(),
835 },
836 }
837 }
838
839 pub fn description(mut self, desc: &str) -> Self {
840 self.rule.description = desc.to_string();
841 self
842 }
843
844 pub fn priority(mut self, priority: RulePriority) -> Self {
845 self.rule.priority = priority;
846 self
847 }
848
849 pub fn condition(mut self, condition: &str) -> Self {
850 self.rule.condition = condition.to_string();
851 self
852 }
853
854 pub fn when_true(mut self, condition: &str) -> Self {
855 self.rule.condition = condition.to_string();
856 self
857 }
858
859 pub fn then_return(mut self, value: serde_json::Value) -> Self {
860 self.rule.action = RuleAction::ReturnValue { value };
861 self
862 }
863
864 pub fn then_execute(mut self, script: &str) -> Self {
865 self.rule.action = RuleAction::ExecuteScript {
866 script: script.to_string(),
867 };
868 self
869 }
870
871 pub fn then_set(mut self, name: &str, value: serde_json::Value) -> Self {
872 self.rule.action = RuleAction::SetVariable {
873 name: name.to_string(),
874 value,
875 };
876 self
877 }
878
879 pub fn then_trigger(mut self, event_type: &str, data: serde_json::Value) -> Self {
880 self.rule.action = RuleAction::TriggerEvent {
881 event_type: event_type.to_string(),
882 data,
883 };
884 self
885 }
886
887 pub fn then_goto(mut self, rule_id: &str) -> Self {
888 self.rule.action = RuleAction::GotoRule {
889 rule_id: rule_id.to_string(),
890 };
891 self
892 }
893
894 pub fn then_stop(mut self) -> Self {
895 self.rule.action = RuleAction::Stop;
896 self
897 }
898
899 pub fn action(mut self, action: RuleAction) -> Self {
900 self.rule.action = action;
901 self
902 }
903
904 pub fn tag(mut self, tag: &str) -> Self {
905 self.rule.tags.push(tag.to_string());
906 self
907 }
908
909 pub fn disabled(mut self) -> Self {
910 self.rule.enabled = false;
911 self
912 }
913
914 pub fn build(self) -> RuleDefinition {
915 self.rule
916 }
917}
918
919#[cfg(test)]
924mod tests {
925 use super::*;
926
927 #[tokio::test]
928 async fn test_rule_registration() {
929 let engine = RuleEngine::new(ScriptEngineConfig::default()).unwrap();
930
931 let rule = RuleBuilder::new("test_rule", "Test Rule")
932 .condition("value > 10")
933 .then_return(serde_json::json!("high"))
934 .build();
935
936 engine.register_rule(rule).await.unwrap();
937
938 assert_eq!(engine.rule_count().await, 1);
939 }
940
941 #[tokio::test]
942 async fn test_rule_execution() {
943 let engine = RuleEngine::new(ScriptEngineConfig::default()).unwrap();
944
945 let rule = RuleBuilder::new("check_value", "Check Value")
946 .condition("value > 100")
947 .then_execute(r#"value * 2"#)
948 .build();
949
950 engine.register_rule(rule).await.unwrap();
951
952 let mut context = ScriptContext::new().with_variable("value", 150).unwrap();
953
954 let result = engine
955 .execute_rule("check_value", &mut context)
956 .await
957 .unwrap();
958
959 assert!(result.is_some());
960 let result = result.unwrap();
961 assert!(result.success);
962 assert_eq!(result.result, serde_json::json!(300));
963 }
964
965 #[tokio::test]
966 async fn test_rule_condition_not_met() {
967 let engine = RuleEngine::new(ScriptEngineConfig::default()).unwrap();
968
969 let rule = RuleBuilder::new("check_value", "Check Value")
970 .condition("value > 100")
971 .then_return(serde_json::json!("high"))
972 .build();
973
974 engine.register_rule(rule).await.unwrap();
975
976 let mut context = ScriptContext::new().with_variable("value", 50).unwrap();
977
978 let result = engine
979 .execute_rule("check_value", &mut context)
980 .await
981 .unwrap();
982
983 assert!(result.is_none());
984 }
985
986 #[tokio::test]
987 async fn test_rule_group_first_match() {
988 let engine = RuleEngine::new(ScriptEngineConfig::default()).unwrap();
989
990 let rules = vec![
992 RuleBuilder::new("rule_high", "High Value")
993 .priority(RulePriority::High)
994 .condition("value > 100")
995 .then_return(serde_json::json!("high"))
996 .build(),
997 RuleBuilder::new("rule_medium", "Medium Value")
998 .priority(RulePriority::Normal)
999 .condition("value > 50")
1000 .then_return(serde_json::json!("medium"))
1001 .build(),
1002 RuleBuilder::new("rule_low", "Low Value")
1003 .priority(RulePriority::Low)
1004 .condition("value > 0")
1005 .then_return(serde_json::json!("low"))
1006 .build(),
1007 ];
1008
1009 engine.register_rules(rules).await.unwrap();
1010
1011 let group = RuleGroupDefinition::new("value_checker", "Value Checker")
1013 .with_match_mode(RuleMatchMode::FirstMatch)
1014 .with_rules(vec!["rule_high", "rule_medium", "rule_low"]);
1015
1016 engine.register_group(group).await.unwrap();
1017
1018 let mut context = ScriptContext::new().with_variable("value", 150).unwrap();
1020 let result = engine
1021 .execute_group("value_checker", &mut context)
1022 .await
1023 .unwrap();
1024
1025 assert!(result.any_matched);
1026 assert_eq!(result.execution_results.len(), 1);
1027 assert_eq!(result.final_result, Some(serde_json::json!("high")));
1028
1029 let mut context = ScriptContext::new().with_variable("value", 75).unwrap();
1031 let result = engine
1032 .execute_group("value_checker", &mut context)
1033 .await
1034 .unwrap();
1035
1036 assert!(result.any_matched);
1037 assert_eq!(result.final_result, Some(serde_json::json!("medium")));
1038 }
1039
1040 #[tokio::test]
1041 async fn test_rule_with_default_action() {
1042 let engine = RuleEngine::new(ScriptEngineConfig::default()).unwrap();
1043
1044 let rule = RuleBuilder::new("positive_rule", "Positive Only")
1045 .condition("value > 0")
1046 .then_return(serde_json::json!("positive"))
1047 .build();
1048
1049 engine.register_rule(rule).await.unwrap();
1050
1051 let group = RuleGroupDefinition::new("number_group", "Number Group")
1052 .with_rules(vec!["positive_rule"])
1053 .with_default_action(RuleAction::ReturnValue {
1054 value: serde_json::json!("non_positive"),
1055 });
1056
1057 engine.register_group(group).await.unwrap();
1058
1059 let mut context = ScriptContext::new().with_variable("value", -10).unwrap();
1061 let result = engine
1062 .execute_group("number_group", &mut context)
1063 .await
1064 .unwrap();
1065
1066 assert!(!result.any_matched);
1067 assert!(result.used_default);
1068 assert_eq!(result.final_result, Some(serde_json::json!("non_positive")));
1069 }
1070
1071 #[tokio::test]
1072 async fn test_set_variable_action() {
1073 let engine = RuleEngine::new(ScriptEngineConfig::default()).unwrap();
1074
1075 let rule = RuleBuilder::new("set_status", "Set Status")
1076 .condition("true")
1077 .then_set("status", serde_json::json!("processed"))
1078 .build();
1079
1080 engine.register_rule(rule).await.unwrap();
1081
1082 let mut context = ScriptContext::new();
1083 let result = engine
1084 .execute_rule("set_status", &mut context)
1085 .await
1086 .unwrap()
1087 .unwrap();
1088
1089 assert!(result.success);
1090 assert!(result.variable_updates.contains_key("status"));
1091 assert_eq!(
1092 context.get_variable::<String>("status"),
1093 Some("processed".to_string())
1094 );
1095 }
1096
1097 #[test]
1098 fn test_rule_builder() {
1099 let rule = RuleBuilder::new("my_rule", "My Rule")
1100 .description("A test rule")
1101 .priority(RulePriority::High)
1102 .condition("x > 10")
1103 .then_return(serde_json::json!({"result": "success"}))
1104 .tag("test")
1105 .build();
1106
1107 assert_eq!(rule.id, "my_rule");
1108 assert_eq!(rule.priority, RulePriority::High);
1109 assert_eq!(rule.condition, "x > 10");
1110 assert!(rule.tags.contains(&"test".to_string()));
1111 }
1112}