Skip to main content

wfe_core/primitives/
sequence.rs

1use async_trait::async_trait;
2use serde_json::json;
3
4use crate::models::ExecutionResult;
5use crate::traits::step::{StepBody, StepExecutionContext};
6
7/// A container step that executes its children sequentially.
8/// Completes when all children have finished.
9#[derive(Default)]
10pub struct SequenceStep;
11
12#[async_trait]
13impl StepBody for SequenceStep {
14    async fn run(&mut self, context: &StepExecutionContext<'_>) -> crate::Result<ExecutionResult> {
15        let mut scope = context.execution_pointer.scope.clone();
16        scope.push(context.execution_pointer.id.clone());
17
18        // If this container has declared children and they haven't been
19        // spawned yet, branch to create execution pointers for all of them.
20        let has_children = !context.step.children.is_empty();
21        let children_spawned = context
22            .workflow
23            .execution_pointers
24            .iter()
25            .any(|p| p.scope == scope);
26
27        if has_children && !children_spawned {
28            Ok(ExecutionResult::branch(vec![json!(null)], None))
29        } else if context.workflow.is_branch_complete(&scope) {
30            Ok(ExecutionResult::next())
31        } else {
32            Ok(ExecutionResult::persist(json!({"children_active": true})))
33        }
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40    use crate::models::{ExecutionPointer, PointerStatus};
41    use crate::primitives::test_helpers::*;
42
43    #[tokio::test]
44    async fn children_complete_proceeds() {
45        let mut step = SequenceStep;
46        let pointer = ExecutionPointer::new(0);
47        let wf_step = default_step();
48
49        let mut workflow = default_workflow();
50        let mut child = ExecutionPointer::new(1);
51        child.scope = vec![pointer.id.clone()];
52        child.status = PointerStatus::Complete;
53        workflow.execution_pointers.push(child);
54
55        let ctx = make_context(&pointer, &wf_step, &workflow);
56        let result = step.run(&ctx).await.unwrap();
57        assert!(result.proceed);
58    }
59
60    #[tokio::test]
61    async fn children_incomplete_persists() {
62        let mut step = SequenceStep;
63        let pointer = ExecutionPointer::new(0);
64        let wf_step = default_step();
65
66        let mut workflow = default_workflow();
67        let mut child = ExecutionPointer::new(1);
68        child.scope = vec![pointer.id.clone()];
69        child.status = PointerStatus::Running;
70        workflow.execution_pointers.push(child);
71
72        let ctx = make_context(&pointer, &wf_step, &workflow);
73        let result = step.run(&ctx).await.unwrap();
74        assert!(!result.proceed);
75        assert_eq!(
76            result.persistence_data,
77            Some(json!({"children_active": true}))
78        );
79    }
80
81    #[tokio::test]
82    async fn no_children_in_scope_proceeds() {
83        let mut step = SequenceStep;
84        let pointer = ExecutionPointer::new(0);
85        let wf_step = default_step();
86        let workflow = default_workflow();
87
88        let ctx = make_context(&pointer, &wf_step, &workflow);
89        let result = step.run(&ctx).await.unwrap();
90        // No children in scope means is_branch_complete returns true (vacuously).
91        assert!(result.proceed);
92    }
93
94    #[tokio::test]
95    async fn spawns_children_when_defined() {
96        let mut step = SequenceStep;
97        let pointer = ExecutionPointer::new(0);
98        let mut wf_step = default_step();
99        wf_step.children = vec![1, 2];
100
101        let workflow = default_workflow();
102
103        let ctx = make_context(&pointer, &wf_step, &workflow);
104        let result = step.run(&ctx).await.unwrap();
105        assert!(!result.proceed);
106        assert!(result.branch_values.is_some());
107    }
108}