Skip to main content

mockforge_intelligence/behavioral_economics/
rules.rs

1//! Behavior rule definitions
2//!
3//! Defines the structure of behavior rules that can be either declarative
4//! (simple YAML/JSON config) or scriptable (JavaScript/WASM) for complex logic.
5
6use 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/// Type of behavior rule
13#[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 rule - simple if-then logic defined in YAML/JSON
18    Declarative,
19    /// Scriptable rule - complex logic defined in JavaScript or WASM
20    Scriptable,
21}
22
23/// Behavior rule definition
24///
25/// A rule consists of a condition and an action. When the condition evaluates
26/// to true, the action is executed. Rules can be declarative (simple) or
27/// scriptable (complex).
28#[derive(Debug, Clone, Serialize, Deserialize)]
29#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
30pub struct BehaviorRule {
31    /// Rule name (unique identifier)
32    pub name: String,
33
34    /// Rule type (declarative or scriptable)
35    pub rule_type: RuleType,
36
37    /// Condition to evaluate
38    pub condition: BehaviorCondition,
39
40    /// Action to execute when condition is true
41    pub action: BehaviorAction,
42
43    /// Priority (higher = evaluated first)
44    pub priority: u32,
45
46    /// Optional script for scriptable rules (JavaScript or WASM)
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub script: Option<String>,
49
50    /// Optional script language (e.g., "javascript", "wasm")
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub script_language: Option<String>,
53
54    /// Additional metadata
55    #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
56    pub metadata: std::collections::HashMap<String, Value>,
57}
58
59impl BehaviorRule {
60    /// Create a new declarative rule
61    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    /// Create a new scriptable rule
80    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    /// Validate the rule
101    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}