Skip to main content

wfe_core/primitives/
if_step.rs

1use async_trait::async_trait;
2use serde_json::json;
3
4use crate::models::ExecutionResult;
5use crate::traits::step::{StepBody, StepExecutionContext};
6
7/// A conditional step that branches execution based on a boolean condition.
8#[derive(Default)]
9pub struct IfStep {
10    pub condition: bool,
11}
12
13#[async_trait]
14impl StepBody for IfStep {
15    async fn run(&mut self, context: &StepExecutionContext<'_>) -> crate::Result<ExecutionResult> {
16        let children_active = context
17            .persistence_data
18            .and_then(|d| d.get("children_active"))
19            .and_then(|v| v.as_bool())
20            .unwrap_or(false);
21
22        if children_active {
23            // Subsequent run: check if branch is complete.
24            let mut scope = context.execution_pointer.scope.clone();
25            scope.push(context.execution_pointer.id.clone());
26
27            if context.workflow.is_branch_complete(&scope) {
28                Ok(ExecutionResult::next())
29            } else {
30                Ok(ExecutionResult::persist(json!({"children_active": true})))
31            }
32        } else {
33            // First run.
34            if self.condition {
35                Ok(ExecutionResult::branch(
36                    vec![json!(null)],
37                    Some(json!({"children_active": true})),
38                ))
39            } else {
40                Ok(ExecutionResult::next())
41            }
42        }
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use crate::models::{ExecutionPointer, PointerStatus};
50    use crate::primitives::test_helpers::*;
51
52    #[tokio::test]
53    async fn condition_true_first_run_branches() {
54        let mut step = IfStep { condition: true };
55        let pointer = ExecutionPointer::new(0);
56        let wf_step = default_step();
57        let workflow = default_workflow();
58        let ctx = make_context(&pointer, &wf_step, &workflow);
59
60        let result = step.run(&ctx).await.unwrap();
61        assert!(!result.proceed);
62        assert_eq!(result.branch_values, Some(vec![json!(null)]));
63        assert_eq!(result.persistence_data, Some(json!({"children_active": true})));
64    }
65
66    #[tokio::test]
67    async fn condition_false_first_run_proceeds() {
68        let mut step = IfStep { condition: false };
69        let pointer = ExecutionPointer::new(0);
70        let wf_step = default_step();
71        let workflow = default_workflow();
72        let ctx = make_context(&pointer, &wf_step, &workflow);
73
74        let result = step.run(&ctx).await.unwrap();
75        assert!(result.proceed);
76        assert!(result.branch_values.is_none());
77    }
78
79    #[tokio::test]
80    async fn children_active_and_complete_proceeds() {
81        let mut step = IfStep { condition: true };
82        let mut pointer = ExecutionPointer::new(0);
83        pointer.persistence_data = Some(json!({"children_active": true}));
84
85        let wf_step = default_step();
86
87        // Create a workflow with child pointers that are all complete.
88        let mut workflow = default_workflow();
89        let mut child = ExecutionPointer::new(1);
90        child.scope = vec![pointer.id.clone()];
91        child.status = PointerStatus::Complete;
92        workflow.execution_pointers.push(child);
93
94        let ctx = make_context(&pointer, &wf_step, &workflow);
95
96        let result = step.run(&ctx).await.unwrap();
97        assert!(result.proceed);
98    }
99
100    #[tokio::test]
101    async fn children_active_and_incomplete_persists() {
102        let mut step = IfStep { condition: true };
103        let mut pointer = ExecutionPointer::new(0);
104        pointer.persistence_data = Some(json!({"children_active": true}));
105
106        let wf_step = default_step();
107
108        // Create a workflow with a child pointer that is still running.
109        let mut workflow = default_workflow();
110        let mut child = ExecutionPointer::new(1);
111        child.scope = vec![pointer.id.clone()];
112        child.status = PointerStatus::Running;
113        workflow.execution_pointers.push(child);
114
115        let ctx = make_context(&pointer, &wf_step, &workflow);
116
117        let result = step.run(&ctx).await.unwrap();
118        assert!(!result.proceed);
119        assert_eq!(result.persistence_data, Some(json!({"children_active": true})));
120    }
121}