miyabi_workflow/
condition.rs1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub enum Condition {
9 Always,
11
12 FieldEquals { field: String, value: Value },
14
15 FieldGreaterThan { field: String, value: f64 },
17
18 FieldLessThan { field: String, value: f64 },
20
21 FieldExists { field: String },
23
24 And(Vec<Condition>),
26
27 Or(Vec<Condition>),
29
30 Not(Box<Condition>),
32}
33
34impl Condition {
35 pub fn evaluate(&self, context: &Value) -> bool {
37 match self {
38 Condition::Always => true,
39
40 Condition::FieldEquals { field, value } => {
41 Self::get_field(context, field).map(|v| v == value).unwrap_or(false)
42 },
43
44 Condition::FieldGreaterThan { field, value } => Self::get_field(context, field)
45 .and_then(|v| v.as_f64())
46 .map(|v| v > *value)
47 .unwrap_or(false),
48
49 Condition::FieldLessThan { field, value } => Self::get_field(context, field)
50 .and_then(|v| v.as_f64())
51 .map(|v| v < *value)
52 .unwrap_or(false),
53
54 Condition::FieldExists { field } => Self::get_field(context, field).is_some(),
55
56 Condition::And(conditions) => conditions.iter().all(|c| c.evaluate(context)),
57
58 Condition::Or(conditions) => conditions.iter().any(|c| c.evaluate(context)),
59
60 Condition::Not(condition) => !condition.evaluate(context),
61 }
62 }
63
64 fn get_field<'a>(context: &'a Value, field: &str) -> Option<&'a Value> {
66 if field.contains('.') {
67 let parts: Vec<&str> = field.split('.').collect();
69 let mut current = context;
70
71 for part in parts {
72 current = current.get(part)?;
73 }
74
75 Some(current)
76 } else {
77 context.get(field)
78 }
79 }
80
81 pub fn success(field: &str) -> Self {
83 Condition::FieldEquals {
84 field: field.to_string(),
85 value: Value::Bool(true),
86 }
87 }
88
89 pub fn failure(field: &str) -> Self {
91 Condition::FieldEquals {
92 field: field.to_string(),
93 value: Value::Bool(false),
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use serde_json::json;
102
103 #[test]
104 fn test_always_condition() {
105 let cond = Condition::Always;
106 let context = json!({});
107
108 assert!(cond.evaluate(&context));
109 }
110
111 #[test]
112 fn test_field_equals() {
113 let cond = Condition::FieldEquals {
114 field: "status".to_string(),
115 value: json!("passed"),
116 };
117
118 let context_pass = json!({ "status": "passed" });
119 let context_fail = json!({ "status": "failed" });
120
121 assert!(cond.evaluate(&context_pass));
122 assert!(!cond.evaluate(&context_fail));
123 }
124
125 #[test]
126 fn test_field_greater_than() {
127 let cond = Condition::FieldGreaterThan {
128 field: "score".to_string(),
129 value: 0.8,
130 };
131
132 let context_high = json!({ "score": 0.95 });
133 let context_low = json!({ "score": 0.5 });
134
135 assert!(cond.evaluate(&context_high));
136 assert!(!cond.evaluate(&context_low));
137 }
138
139 #[test]
140 fn test_field_less_than() {
141 let cond = Condition::FieldLessThan {
142 field: "errors".to_string(),
143 value: 5.0,
144 };
145
146 let context_few = json!({ "errors": 2.0 });
147 let context_many = json!({ "errors": 10.0 });
148
149 assert!(cond.evaluate(&context_few));
150 assert!(!cond.evaluate(&context_many));
151 }
152
153 #[test]
154 fn test_field_exists() {
155 let cond = Condition::FieldExists {
156 field: "result".to_string(),
157 };
158
159 let context_exists = json!({ "result": "data" });
160 let context_missing = json!({ "other": "data" });
161
162 assert!(cond.evaluate(&context_exists));
163 assert!(!cond.evaluate(&context_missing));
164 }
165
166 #[test]
167 fn test_nested_field() {
168 let cond = Condition::FieldEquals {
169 field: "result.status".to_string(),
170 value: json!("success"),
171 };
172
173 let context = json!({
174 "result": {
175 "status": "success",
176 "code": 200
177 }
178 });
179
180 assert!(cond.evaluate(&context));
181 }
182
183 #[test]
184 fn test_and_condition() {
185 let cond = Condition::And(vec![
186 Condition::FieldEquals {
187 field: "status".to_string(),
188 value: json!("passed"),
189 },
190 Condition::FieldGreaterThan {
191 field: "score".to_string(),
192 value: 0.8,
193 },
194 ]);
195
196 let context_both = json!({ "status": "passed", "score": 0.9 });
197 let context_one = json!({ "status": "passed", "score": 0.5 });
198
199 assert!(cond.evaluate(&context_both));
200 assert!(!cond.evaluate(&context_one));
201 }
202
203 #[test]
204 fn test_or_condition() {
205 let cond = Condition::Or(vec![
206 Condition::FieldEquals {
207 field: "status".to_string(),
208 value: json!("passed"),
209 },
210 Condition::FieldEquals {
211 field: "status".to_string(),
212 value: json!("warning"),
213 },
214 ]);
215
216 let context_pass = json!({ "status": "passed" });
217 let context_warn = json!({ "status": "warning" });
218 let context_fail = json!({ "status": "failed" });
219
220 assert!(cond.evaluate(&context_pass));
221 assert!(cond.evaluate(&context_warn));
222 assert!(!cond.evaluate(&context_fail));
223 }
224
225 #[test]
226 fn test_not_condition() {
227 let cond = Condition::Not(Box::new(Condition::FieldEquals {
228 field: "failed".to_string(),
229 value: json!(true),
230 }));
231
232 let context_ok = json!({ "failed": false });
233 let context_failed = json!({ "failed": true });
234
235 assert!(cond.evaluate(&context_ok));
236 assert!(!cond.evaluate(&context_failed));
237 }
238
239 #[test]
240 fn test_success_helper() {
241 let cond = Condition::success("passed");
242 let context = json!({ "passed": true });
243
244 assert!(cond.evaluate(&context));
245 }
246
247 #[test]
248 fn test_failure_helper() {
249 let cond = Condition::failure("passed");
250 let context = json!({ "passed": false });
251
252 assert!(cond.evaluate(&context));
253 }
254}