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