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