1use crate::decision::Decision;
4use crate::error::EngineError;
5use crue_dsl::ast::ActionNode;
6use crue_dsl::compiler::{
7 ActionDecision as DslActionDecision, ActionInstruction as DslActionInstruction,
8};
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub enum Operator {
14 Eq,
15 Ne,
16 Gt,
17 Lt,
18 Gte,
19 Lte,
20}
21
22impl Operator {
23 pub fn parse(op: &str) -> Result<Self, EngineError> {
24 match op {
25 "==" => Ok(Self::Eq),
26 "!=" => Ok(Self::Ne),
27 ">" => Ok(Self::Gt),
28 "<" => Ok(Self::Lt),
29 ">=" => Ok(Self::Gte),
30 "<=" => Ok(Self::Lte),
31 _ => Err(EngineError::InvalidOperator(op.to_string())),
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38pub enum ActionKind {
39 Block,
40 Warn,
41 RequireApproval,
42 Log,
43}
44
45impl ActionKind {
46 pub fn parse(action: &str) -> Result<Self, EngineError> {
47 match action {
48 "BLOCK" => Ok(Self::Block),
49 "WARN" => Ok(Self::Warn),
50 "REQUIRE_APPROVAL" => Ok(Self::RequireApproval),
51 "LOG" => Ok(Self::Log),
52 _ => Err(EngineError::InvalidAction(action.to_string())),
53 }
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub enum RuleEffect {
60 Block {
61 code: String,
62 message: Option<String>,
63 },
64 Warn {
65 code: String,
66 },
67 RequireApproval {
68 code: String,
69 timeout_minutes: u32,
70 },
71 Log,
72 AlertSoc,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub enum ActionInstruction {
78 SetDecision(Decision),
79 SetErrorCode(String),
80 SetMessage(String),
81 SetApprovalTimeout(u32),
82 SetAlertSoc(bool),
83 Halt,
84}
85
86impl TryFrom<ActionNode> for RuleEffect {
87 type Error = EngineError;
88
89 fn try_from(value: ActionNode) -> Result<Self, Self::Error> {
90 Ok(match value {
91 ActionNode::Block { code, message } => Self::Block { code, message },
92 ActionNode::Warn { code } => Self::Warn { code },
93 ActionNode::RequireApproval {
94 code,
95 timeout_minutes,
96 } => Self::RequireApproval {
97 code,
98 timeout_minutes,
99 },
100 ActionNode::Log => Self::Log,
101 ActionNode::AlertSoc => Self::AlertSoc,
102 })
103 }
104}
105
106impl TryFrom<DslActionInstruction> for ActionInstruction {
107 type Error = EngineError;
108
109 fn try_from(value: DslActionInstruction) -> Result<Self, Self::Error> {
110 Ok(match value {
111 DslActionInstruction::SetDecision(d) => Self::SetDecision(match d {
112 DslActionDecision::Allow => Decision::Allow,
113 DslActionDecision::Block => Decision::Block,
114 DslActionDecision::Warn => Decision::Warn,
115 DslActionDecision::ApprovalRequired => Decision::ApprovalRequired,
116 }),
117 DslActionInstruction::SetErrorCode(code) => Self::SetErrorCode(code),
118 DslActionInstruction::SetMessage(msg) => Self::SetMessage(msg),
119 DslActionInstruction::SetApprovalTimeout(timeout) => Self::SetApprovalTimeout(timeout),
120 DslActionInstruction::SetAlertSoc(v) => Self::SetAlertSoc(v),
121 DslActionInstruction::Halt => Self::Halt,
122 })
123 }
124}
125
126impl RuleEffect {
127 pub fn is_alert_only(&self) -> bool {
128 matches!(self, Self::AlertSoc)
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_operator_parse() {
138 assert_eq!(Operator::parse(">=").unwrap(), Operator::Gte);
139 assert!(Operator::parse("contains").is_err());
140 }
141
142 #[test]
143 fn test_action_node_to_rule_effect() {
144 let effect = RuleEffect::try_from(ActionNode::RequireApproval {
145 code: "APPROVAL".to_string(),
146 timeout_minutes: 15,
147 })
148 .unwrap();
149
150 assert_eq!(
151 effect,
152 RuleEffect::RequireApproval {
153 code: "APPROVAL".to_string(),
154 timeout_minutes: 15,
155 }
156 );
157 }
158
159 #[test]
160 fn test_action_kind_parse() {
161 assert_eq!(ActionKind::parse("BLOCK").unwrap(), ActionKind::Block);
162 assert!(ActionKind::parse("DROP_TABLE").is_err());
163 }
164
165 #[test]
166 fn test_action_instruction_roundtrip_serde() {
167 let insn = ActionInstruction::SetDecision(Decision::Block);
168 let json = serde_json::to_string(&insn).unwrap();
169 let decoded: ActionInstruction = serde_json::from_str(&json).unwrap();
170 assert_eq!(decoded, insn);
171 }
172
173 #[test]
174 fn test_dsl_action_instruction_to_engine_action_instruction() {
175 let dsl = DslActionInstruction::SetDecision(DslActionDecision::Block);
176 let engine = ActionInstruction::try_from(dsl).unwrap();
177 assert_eq!(engine, ActionInstruction::SetDecision(Decision::Block));
178 }
179}