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