Skip to main content

wfe_core/primitives/
while_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 looping step that repeats its children while a condition is true.
8#[derive(Default)]
9pub struct WhileStep {
10    pub condition: bool,
11}
12
13#[async_trait]
14impl StepBody for WhileStep {
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 the current iteration 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                // Iteration complete. Re-evaluate condition.
29                if self.condition {
30                    // Start a new iteration.
31                    Ok(ExecutionResult::branch(
32                        vec![json!(null)],
33                        Some(json!({"children_active": true})),
34                    ))
35                } else {
36                    Ok(ExecutionResult::next())
37                }
38            } else {
39                Ok(ExecutionResult::persist(json!({"children_active": true})))
40            }
41        } else {
42            // First run.
43            if self.condition {
44                Ok(ExecutionResult::branch(
45                    vec![json!(null)],
46                    Some(json!({"children_active": true})),
47                ))
48            } else {
49                Ok(ExecutionResult::next())
50            }
51        }
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use crate::models::{ExecutionPointer, PointerStatus};
59    use crate::primitives::test_helpers::*;
60
61    #[tokio::test]
62    async fn condition_true_first_run_branches() {
63        let mut step = WhileStep { condition: true };
64        let pointer = ExecutionPointer::new(0);
65        let wf_step = default_step();
66        let workflow = default_workflow();
67        let ctx = make_context(&pointer, &wf_step, &workflow);
68
69        let result = step.run(&ctx).await.unwrap();
70        assert!(!result.proceed);
71        assert_eq!(result.branch_values, Some(vec![json!(null)]));
72        assert_eq!(result.persistence_data, Some(json!({"children_active": true})));
73    }
74
75    #[tokio::test]
76    async fn condition_false_first_run_proceeds() {
77        let mut step = WhileStep { condition: false };
78        let pointer = ExecutionPointer::new(0);
79        let wf_step = default_step();
80        let workflow = default_workflow();
81        let ctx = make_context(&pointer, &wf_step, &workflow);
82
83        let result = step.run(&ctx).await.unwrap();
84        assert!(result.proceed);
85    }
86
87    #[tokio::test]
88    async fn children_complete_and_condition_true_re_branches() {
89        let mut step = WhileStep { condition: true };
90        let mut pointer = ExecutionPointer::new(0);
91        pointer.persistence_data = Some(json!({"children_active": true}));
92
93        let wf_step = default_step();
94        let mut workflow = default_workflow();
95        let mut child = ExecutionPointer::new(1);
96        child.scope = vec![pointer.id.clone()];
97        child.status = PointerStatus::Complete;
98        workflow.execution_pointers.push(child);
99
100        let ctx = make_context(&pointer, &wf_step, &workflow);
101
102        let result = step.run(&ctx).await.unwrap();
103        assert!(!result.proceed);
104        assert_eq!(result.branch_values, Some(vec![json!(null)]));
105    }
106
107    #[tokio::test]
108    async fn children_complete_and_condition_false_proceeds() {
109        let mut step = WhileStep { condition: false };
110        let mut pointer = ExecutionPointer::new(0);
111        pointer.persistence_data = Some(json!({"children_active": true}));
112
113        let wf_step = default_step();
114        let mut workflow = default_workflow();
115        let mut child = ExecutionPointer::new(1);
116        child.scope = vec![pointer.id.clone()];
117        child.status = PointerStatus::Complete;
118        workflow.execution_pointers.push(child);
119
120        let ctx = make_context(&pointer, &wf_step, &workflow);
121
122        let result = step.run(&ctx).await.unwrap();
123        assert!(result.proceed);
124    }
125
126    #[tokio::test]
127    async fn children_incomplete_persists() {
128        let mut step = WhileStep { condition: true };
129        let mut pointer = ExecutionPointer::new(0);
130        pointer.persistence_data = Some(json!({"children_active": true}));
131
132        let wf_step = default_step();
133        let mut workflow = default_workflow();
134        let mut child = ExecutionPointer::new(1);
135        child.scope = vec![pointer.id.clone()];
136        child.status = PointerStatus::Running;
137        workflow.execution_pointers.push(child);
138
139        let ctx = make_context(&pointer, &wf_step, &workflow);
140
141        let result = step.run(&ctx).await.unwrap();
142        assert!(!result.proceed);
143        assert!(result.branch_values.is_none());
144        assert_eq!(result.persistence_data, Some(json!({"children_active": true})));
145    }
146}