1use std::str::FromStr;
5
6use anyhow::Result;
7
8use crate::ctx::{inject_engine_version, timestamp_now, WorkflowCtx};
9use crate::store::WorkflowStore;
10use crate::types::*;
11
12impl<S: WorkflowStore> WorkflowCtx<S> {
13 pub async fn start_workflow(
14 &self,
15 namespace: &str,
16 workflow_type: &str,
17 workflow_id: &str,
18 input: Option<&str>,
19 task_queue: &str,
20 search_attributes: Option<&str>,
21 ) -> Result<WorkflowRecord> {
22 let now = timestamp_now();
23 let run_id = format!("run-{workflow_id}-{}", now as u64);
24
25 let stamped_attrs = inject_engine_version(search_attributes);
34
35 let wf = WorkflowRecord {
36 id: workflow_id.to_string(),
37 namespace: namespace.to_string(),
38 run_id,
39 workflow_type: workflow_type.to_string(),
40 task_queue: task_queue.to_string(),
41 status: "PENDING".to_string(),
42 input: input.map(String::from),
43 result: None,
44 error: None,
45 parent_id: None,
46 claimed_by: None,
47 search_attributes: stamped_attrs,
48 archived_at: None,
49 archive_uri: None,
50 created_at: now,
51 updated_at: now,
52 completed_at: None,
53 };
54
55 self.store.create_workflow(&wf).await?;
56
57 self.store
58 .append_event(&WorkflowEvent {
59 id: None,
60 workflow_id: workflow_id.to_string(),
61 seq: 1,
62 event_type: "WorkflowStarted".to_string(),
63 payload: input.map(String::from),
64 timestamp: now,
65 })
66 .await?;
67
68 self.store.mark_workflow_dispatchable(workflow_id).await?;
71
72 self.broadcast("workflow_started", workflow_id, namespace);
74
75 Ok(wf)
76 }
77
78 pub async fn get_workflow(&self, id: &str) -> Result<Option<WorkflowRecord>> {
79 self.store.get_workflow(id).await
80 }
81
82 pub async fn list_workflows(
83 &self,
84 namespace: &str,
85 status: Option<WorkflowStatus>,
86 workflow_type: Option<&str>,
87 search_attrs_filter: Option<&str>,
88 limit: i64,
89 offset: i64,
90 ) -> Result<Vec<WorkflowRecord>> {
91 self.store
92 .list_workflows(
93 namespace,
94 status,
95 workflow_type,
96 search_attrs_filter,
97 limit,
98 offset,
99 )
100 .await
101 }
102
103 pub async fn upsert_search_attributes(
104 &self,
105 workflow_id: &str,
106 patch_json: &str,
107 ) -> Result<()> {
108 self.store
109 .upsert_search_attributes(workflow_id, patch_json)
110 .await
111 }
112
113 pub async fn cancel_workflow(&self, id: &str, reason: Option<&str>) -> Result<bool> {
114 let wf = self.store.get_workflow(id).await?;
115 match wf {
116 None => Ok(false),
117 Some(wf) => {
118 let status = WorkflowStatus::from_str(&wf.status)
119 .map_err(|e| anyhow::anyhow!(e))?;
120 if status.is_terminal() {
121 return Ok(false);
122 }
123
124 self.store.cancel_pending_activities(id).await?;
136 self.store.cancel_pending_timers(id).await?;
137
138 let payload = reason.map(|r| {
143 serde_json::json!({ "reason": r }).to_string()
144 });
145
146 let seq = self.store.get_event_count(id).await? as i32 + 1;
147 self.store
148 .append_event(&WorkflowEvent {
149 id: None,
150 workflow_id: id.to_string(),
151 seq,
152 event_type: "WorkflowCancelRequested".to_string(),
153 payload,
154 timestamp: timestamp_now(),
155 })
156 .await?;
157
158 self.store.mark_workflow_dispatchable(id).await?;
159
160 let children = self.store.list_child_workflows(id).await?;
164 for child in children {
165 Box::pin(self.cancel_workflow(&child.id, reason)).await?;
166 }
167
168 if matches!(status, WorkflowStatus::Pending) {
174 self.finalise_cancellation(id).await?;
175 }
176
177 Ok(true)
178 }
179 }
180 }
181
182 pub async fn terminate_workflow(&self, id: &str, reason: Option<&str>) -> Result<bool> {
183 let wf = self.store.get_workflow(id).await?;
184 match wf {
185 None => Ok(false),
186 Some(wf) => {
187 let status = WorkflowStatus::from_str(&wf.status)
188 .map_err(|e| anyhow::anyhow!(e))?;
189 if status.is_terminal() {
190 return Ok(false);
191 }
192
193 self.store
194 .update_workflow_status(
195 id,
196 WorkflowStatus::Failed,
197 None,
198 Some(reason.unwrap_or("terminated")),
199 )
200 .await?;
201
202 self.broadcast("workflow_terminated", id, &wf.namespace);
205
206 Ok(true)
207 }
208 }
209 }
210
211 pub async fn finalise_cancellation(&self, workflow_id: &str) -> Result<()> {
216 if let Some(wf) = self.store.get_workflow(workflow_id).await?
218 && wf.status == "CANCELLED"
219 {
220 return Ok(());
221 }
222 self.store
223 .update_workflow_status(workflow_id, WorkflowStatus::Cancelled, None, None)
224 .await?;
225 let event_seq = self.store.get_event_count(workflow_id).await? as i32 + 1;
226 self.store
227 .append_event(&WorkflowEvent {
228 id: None,
229 workflow_id: workflow_id.to_string(),
230 seq: event_seq,
231 event_type: "WorkflowCancelled".to_string(),
232 payload: None,
233 timestamp: timestamp_now(),
234 })
235 .await?;
236 let ns = self
237 .store
238 .get_workflow(workflow_id)
239 .await?
240 .map(|w| w.namespace)
241 .unwrap_or_default();
242 self.broadcast("workflow_cancelled", workflow_id, &ns);
243 Ok(())
244 }
245
246 pub async fn complete_workflow(&self, workflow_id: &str, result: Option<&str>) -> Result<()> {
251 self.store
252 .update_workflow_status(workflow_id, WorkflowStatus::Completed, result, None)
253 .await?;
254 let event_seq = self.store.get_event_count(workflow_id).await? as i32 + 1;
255 self.store
256 .append_event(&WorkflowEvent {
257 id: None,
258 workflow_id: workflow_id.to_string(),
259 seq: event_seq,
260 event_type: "WorkflowCompleted".to_string(),
261 payload: result.map(String::from),
262 timestamp: timestamp_now(),
263 })
264 .await?;
265 self.notify_parent_of_child_outcome(
266 workflow_id,
267 "ChildWorkflowCompleted",
268 serde_json::json!({
269 "child_workflow_id": workflow_id,
270 "result": result.and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok()),
271 }),
272 )
273 .await?;
274 let ns = self
275 .store
276 .get_workflow(workflow_id)
277 .await?
278 .map(|w| w.namespace)
279 .unwrap_or_default();
280 self.broadcast("workflow_completed", workflow_id, &ns);
281 Ok(())
282 }
283
284 pub async fn fail_workflow(&self, workflow_id: &str, error: &str) -> Result<()> {
287 self.store
288 .update_workflow_status(workflow_id, WorkflowStatus::Failed, None, Some(error))
289 .await?;
290 let event_seq = self.store.get_event_count(workflow_id).await? as i32 + 1;
291 self.store
292 .append_event(&WorkflowEvent {
293 id: None,
294 workflow_id: workflow_id.to_string(),
295 seq: event_seq,
296 event_type: "WorkflowFailed".to_string(),
297 payload: Some(serde_json::json!({"error": error}).to_string()),
298 timestamp: timestamp_now(),
299 })
300 .await?;
301 self.notify_parent_of_child_outcome(
302 workflow_id,
303 "ChildWorkflowFailed",
304 serde_json::json!({
305 "child_workflow_id": workflow_id,
306 "error": error,
307 }),
308 )
309 .await?;
310 let ns = self
311 .store
312 .get_workflow(workflow_id)
313 .await?
314 .map(|w| w.namespace)
315 .unwrap_or_default();
316 self.broadcast("workflow_failed", workflow_id, &ns);
317 Ok(())
318 }
319
320 async fn notify_parent_of_child_outcome(
324 &self,
325 child_workflow_id: &str,
326 event_type: &str,
327 payload: serde_json::Value,
328 ) -> Result<()> {
329 let Some(child) = self.store.get_workflow(child_workflow_id).await? else {
330 return Ok(());
331 };
332 let Some(parent_id) = child.parent_id else {
333 return Ok(());
334 };
335 let event_seq = self.store.get_event_count(&parent_id).await? as i32 + 1;
336 self.store
337 .append_event(&WorkflowEvent {
338 id: None,
339 workflow_id: parent_id.clone(),
340 seq: event_seq,
341 event_type: event_type.to_string(),
342 payload: Some(payload.to_string()),
343 timestamp: timestamp_now(),
344 })
345 .await?;
346 self.store.mark_workflow_dispatchable(&parent_id).await?;
347 Ok(())
348 }
349}