oxigdal_workflow/conditional/
branching.rs1use crate::conditional::expressions::{Expression, ExpressionContext};
4use crate::error::{Result, WorkflowError};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ConditionalBranch {
10 pub condition: Expression,
12 pub then_tasks: Vec<String>,
14 pub else_tasks: Option<Vec<String>>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct SwitchCase {
21 pub variable: String,
23 pub cases: Vec<Case>,
25 pub default: Option<Vec<String>>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct Case {
32 pub value: serde_json::Value,
34 pub tasks: Vec<String>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct LoopCondition {
41 pub condition: Expression,
43 pub body_tasks: Vec<String>,
45 pub max_iterations: usize,
47}
48
49impl ConditionalBranch {
50 pub fn new(condition: Expression, then_tasks: Vec<String>) -> Self {
52 Self {
53 condition,
54 then_tasks,
55 else_tasks: None,
56 }
57 }
58
59 pub fn with_else(mut self, else_tasks: Vec<String>) -> Self {
61 self.else_tasks = Some(else_tasks);
62 self
63 }
64
65 pub fn evaluate(&self, context: &ExpressionContext) -> Result<Vec<String>> {
67 let condition_result = self.condition.evaluate(context)?;
68
69 let is_true = condition_result
70 .as_bool()
71 .ok_or_else(|| WorkflowError::conditional("Condition must evaluate to boolean"))?;
72
73 if is_true {
74 Ok(self.then_tasks.clone())
75 } else if let Some(ref else_tasks) = self.else_tasks {
76 Ok(else_tasks.clone())
77 } else {
78 Ok(Vec::new())
79 }
80 }
81}
82
83impl SwitchCase {
84 pub fn new(variable: String, cases: Vec<Case>) -> Self {
86 Self {
87 variable,
88 cases,
89 default: None,
90 }
91 }
92
93 pub fn with_default(mut self, default_tasks: Vec<String>) -> Self {
95 self.default = Some(default_tasks);
96 self
97 }
98
99 pub fn evaluate(&self, context: &ExpressionContext) -> Result<Vec<String>> {
101 let value = context.get(&self.variable).ok_or_else(|| {
102 WorkflowError::conditional(format!("Variable '{}' not found", self.variable))
103 })?;
104
105 for case in &self.cases {
106 if &case.value == value {
107 return Ok(case.tasks.clone());
108 }
109 }
110
111 Ok(self.default.clone().unwrap_or_default())
113 }
114}
115
116impl LoopCondition {
117 pub fn new(condition: Expression, body_tasks: Vec<String>, max_iterations: usize) -> Self {
119 Self {
120 condition,
121 body_tasks,
122 max_iterations,
123 }
124 }
125
126 pub fn should_continue(&self, context: &ExpressionContext) -> Result<bool> {
128 let condition_result = self.condition.evaluate(context)?;
129
130 condition_result
131 .as_bool()
132 .ok_or_else(|| WorkflowError::conditional("Loop condition must evaluate to boolean"))
133 }
134
135 pub fn get_body_tasks(&self) -> Vec<String> {
137 self.body_tasks.clone()
138 }
139}
140
141#[derive(Debug, Clone)]
143pub enum ExecutionDecision {
144 Execute(Vec<String>),
146 Skip,
148 Repeat(Vec<String>),
150}
151
152pub struct ConditionalEvaluator {
154 branches: Vec<ConditionalBranch>,
156 switches: Vec<SwitchCase>,
158 loops: Vec<LoopCondition>,
160}
161
162impl ConditionalEvaluator {
163 pub fn new() -> Self {
165 Self {
166 branches: Vec::new(),
167 switches: Vec::new(),
168 loops: Vec::new(),
169 }
170 }
171
172 pub fn add_branch(&mut self, branch: ConditionalBranch) {
174 self.branches.push(branch);
175 }
176
177 pub fn add_switch(&mut self, switch: SwitchCase) {
179 self.switches.push(switch);
180 }
181
182 pub fn add_loop(&mut self, loop_cond: LoopCondition) {
184 self.loops.push(loop_cond);
185 }
186
187 pub fn evaluate(&self, context: &ExpressionContext) -> Result<Vec<String>> {
189 let mut tasks_to_execute = Vec::new();
190
191 for branch in &self.branches {
193 tasks_to_execute.extend(branch.evaluate(context)?);
194 }
195
196 for switch in &self.switches {
198 tasks_to_execute.extend(switch.evaluate(context)?);
199 }
200
201 Ok(tasks_to_execute)
202 }
203}
204
205impl Default for ConditionalEvaluator {
206 fn default() -> Self {
207 Self::new()
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use crate::conditional::expressions::Expression;
215 use serde_json::Value;
216 use std::collections::HashMap;
217
218 #[test]
219 fn test_conditional_branch() {
220 let branch = ConditionalBranch::new(
221 Expression::eq(
222 Expression::variable("status"),
223 Expression::literal(Value::String("success".to_string())),
224 ),
225 vec!["task1".to_string(), "task2".to_string()],
226 )
227 .with_else(vec!["task3".to_string()]);
228
229 let mut ctx = HashMap::new();
230 ctx.insert("status".to_string(), Value::String("success".to_string()));
231
232 let tasks = branch.evaluate(&ctx).expect("Failed to evaluate");
233 assert_eq!(tasks, vec!["task1".to_string(), "task2".to_string()]);
234 }
235
236 #[test]
237 fn test_switch_case() {
238 let switch = SwitchCase::new(
239 "env".to_string(),
240 vec![
241 Case {
242 value: Value::String("dev".to_string()),
243 tasks: vec!["dev_task".to_string()],
244 },
245 Case {
246 value: Value::String("prod".to_string()),
247 tasks: vec!["prod_task".to_string()],
248 },
249 ],
250 )
251 .with_default(vec!["default_task".to_string()]);
252
253 let mut ctx = HashMap::new();
254 ctx.insert("env".to_string(), Value::String("prod".to_string()));
255
256 let tasks = switch.evaluate(&ctx).expect("Failed to evaluate");
257 assert_eq!(tasks, vec!["prod_task".to_string()]);
258 }
259
260 #[test]
261 fn test_loop_condition() {
262 let loop_cond = LoopCondition::new(
263 Expression::binary(
264 Expression::variable("count"),
265 crate::conditional::expressions::BinaryOperator::Lt,
266 Expression::literal(Value::Number(5.into())),
267 ),
268 vec!["increment".to_string()],
269 10,
270 );
271
272 let mut ctx = HashMap::new();
273 ctx.insert("count".to_string(), Value::Number(3.into()));
274
275 let should_continue = loop_cond.should_continue(&ctx).expect("Failed to evaluate");
276 assert!(should_continue);
277 }
278}