1use std::collections::BTreeSet;
2
3use uuid::Uuid;
4
5use crate::error::{FlowError, Result};
6use crate::model::{project_run, validate_run_id, FlowEvent, WorkflowRunStatus, WorkflowSpec};
7
8use super::validation::{ensure_same_start, is_event_conflict};
9use super::FlowEngine;
10
11impl FlowEngine {
12 pub async fn start(&self, spec: WorkflowSpec, input: serde_json::Value) -> Result<String> {
14 let run_id = Uuid::new_v4().to_string();
15 self.start_with_id(run_id, spec, input).await
16 }
17
18 pub async fn start_with_id(
25 &self,
26 run_id: impl Into<String>,
27 spec: WorkflowSpec,
28 input: serde_json::Value,
29 ) -> Result<String> {
30 let run_id = run_id.into();
31 self.ensure_run_started(&run_id, &spec, &input).await?;
32 self.drive(&run_id).await?;
33 Ok(run_id)
34 }
35
36 pub(super) async fn terminate_run(&self, run_id: &str, event: FlowEvent) -> Result<()> {
37 self.terminate_run_with_context(run_id, event, 0, &BTreeSet::new())
38 .await
39 }
40
41 pub(super) async fn terminate_run_with_context(
42 &self,
43 run_id: &str,
44 event: FlowEvent,
45 child_depth: usize,
46 ancestry: &BTreeSet<String>,
47 ) -> Result<()> {
48 for _ in 0..self.max_replay_iterations {
49 let snapshot = self.ensure_continuation_leaf(run_id).await?;
50 if ancestry.contains(&snapshot.run_id) {
51 return Err(FlowError::ChildWorkflowCycle(snapshot.run_id));
52 }
53 if snapshot.status.is_terminal() {
54 return Ok(());
55 }
56 let mut active_ancestry = ancestry.clone();
57 active_ancestry.insert(snapshot.run_id.clone());
58 if matches!(
59 self.terminate_child_workflows(
60 &snapshot,
61 terminal_reason(&event),
62 child_depth,
63 &active_ancestry,
64 )
65 .await?,
66 super::child_workflows::ChildReconciliation::Replay
67 ) {
68 continue;
69 }
70 match self
71 .record_event_at(&snapshot.run_id, snapshot.last_sequence, event.clone())
72 .await
73 {
74 Ok(_) => return Ok(()),
75 Err(error) if is_event_conflict(&error) => continue,
76 Err(error) => return Err(error),
77 }
78 }
79 Err(FlowError::ReplayLimitExceeded(self.max_replay_iterations))
80 }
81
82 pub(super) async fn ensure_run_started(
83 &self,
84 run_id: &str,
85 spec: &WorkflowSpec,
86 input: &serde_json::Value,
87 ) -> Result<()> {
88 self.ensure_run_started_with_admission(run_id, spec, input, true)
89 .await
90 }
91
92 pub(super) async fn ensure_run_started_with_admission(
93 &self,
94 run_id: &str,
95 spec: &WorkflowSpec,
96 input: &serde_json::Value,
97 require_runtime_build: bool,
98 ) -> Result<()> {
99 spec.validate()?;
100 validate_run_id(run_id)?;
101
102 for _ in 0..self.max_replay_iterations {
103 match self.store.list(run_id).await {
104 Ok(history) => {
105 let snapshot = project_run(run_id, &history)?;
106 ensure_same_start(run_id, &snapshot, spec, input)?;
107 if snapshot.status != WorkflowRunStatus::Pending {
108 return Ok(());
109 }
110 if require_runtime_build {
113 self.ensure_runtime_build_available(run_id, spec)?;
114 }
115 match self
116 .record_event_at(run_id, snapshot.last_sequence, FlowEvent::RunStarted)
117 .await
118 {
119 Ok(_) => return Ok(()),
120 Err(error) if is_event_conflict(&error) => continue,
121 Err(error) => return Err(error),
122 }
123 }
124 Err(FlowError::RunNotFound(_)) => {
125 if require_runtime_build {
128 self.ensure_runtime_build_available(run_id, spec)?;
129 }
130 match self
131 .record_event_at(
132 run_id,
133 0,
134 FlowEvent::RunCreated {
135 spec: spec.clone(),
136 input: input.clone(),
137 },
138 )
139 .await
140 {
141 Ok(_) => continue,
142 Err(error) if is_event_conflict(&error) => continue,
143 Err(error) => return Err(error),
144 }
145 }
146 Err(error) => return Err(error),
147 }
148 }
149
150 Err(FlowError::ReplayLimitExceeded(self.max_replay_iterations))
151 }
152}
153
154fn terminal_reason(event: &FlowEvent) -> Option<String> {
155 match event {
156 FlowEvent::RunCancelled { reason }
157 | FlowEvent::RunTimedOut { reason, .. }
158 | FlowEvent::RunHostShutdown { reason } => reason.clone(),
159 FlowEvent::RunFailed { error } | FlowEvent::RunRetryExhausted { error, .. } => {
160 Some(error.clone())
161 }
162 _ => None,
163 }
164}