mockforge_intelligence/behavioral_economics/
rules.rs1use crate::behavioral_economics::actions::BehaviorAction;
7use crate::behavioral_economics::conditions::BehaviorCondition;
8use mockforge_foundation::{Error, Result};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
14#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
15#[serde(rename_all = "snake_case")]
16pub enum RuleType {
17 Declarative,
19 Scriptable,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
29#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
30pub struct BehaviorRule {
31 pub name: String,
33
34 pub rule_type: RuleType,
36
37 pub condition: BehaviorCondition,
39
40 pub action: BehaviorAction,
42
43 pub priority: u32,
45
46 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub script: Option<String>,
49
50 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub script_language: Option<String>,
53
54 #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
56 pub metadata: std::collections::HashMap<String, Value>,
57}
58
59impl BehaviorRule {
60 pub fn declarative(
62 name: String,
63 condition: BehaviorCondition,
64 action: BehaviorAction,
65 priority: u32,
66 ) -> Self {
67 Self {
68 name,
69 rule_type: RuleType::Declarative,
70 condition,
71 action,
72 priority,
73 script: None,
74 script_language: None,
75 metadata: std::collections::HashMap::new(),
76 }
77 }
78
79 pub fn scriptable(
81 name: String,
82 condition: BehaviorCondition,
83 action: BehaviorAction,
84 priority: u32,
85 script: String,
86 script_language: String,
87 ) -> Self {
88 Self {
89 name,
90 rule_type: RuleType::Scriptable,
91 condition,
92 action,
93 priority,
94 script: Some(script),
95 script_language: Some(script_language),
96 metadata: std::collections::HashMap::new(),
97 }
98 }
99
100 pub fn validate(&self) -> Result<()> {
102 if self.name.trim().is_empty() {
103 return Err(Error::internal("Rule name cannot be empty"));
104 }
105
106 if matches!(self.rule_type, RuleType::Scriptable) {
107 if self.script.is_none() {
108 return Err(Error::internal("Scriptable rules must have a script"));
109 }
110 if self.script_language.is_none() {
111 return Err(Error::internal("Scriptable rules must have a script_language"));
112 }
113 }
114
115 Ok(())
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::behavioral_economics::actions::BehaviorAction;
123 use crate::behavioral_economics::conditions::BehaviorCondition;
124
125 #[test]
126 fn test_declarative_rule_creation() {
127 let rule = BehaviorRule::declarative(
128 "test-rule".to_string(),
129 BehaviorCondition::Always,
130 BehaviorAction::NoOp,
131 100,
132 );
133 assert_eq!(rule.rule_type, RuleType::Declarative);
134 assert!(rule.script.is_none());
135 }
136
137 #[test]
138 fn test_scriptable_rule_creation() {
139 let rule = BehaviorRule::scriptable(
140 "test-rule".to_string(),
141 BehaviorCondition::Always,
142 BehaviorAction::NoOp,
143 100,
144 "console.log('test')".to_string(),
145 "javascript".to_string(),
146 );
147 assert_eq!(rule.rule_type, RuleType::Scriptable);
148 assert!(rule.script.is_some());
149 }
150
151 #[test]
152 fn test_rule_validation() {
153 let mut rule = BehaviorRule::declarative(
154 "test-rule".to_string(),
155 BehaviorCondition::Always,
156 BehaviorAction::NoOp,
157 100,
158 );
159 assert!(rule.validate().is_ok());
160
161 rule.name = "".to_string();
162 assert!(rule.validate().is_err());
163 }
164}