1use chrono::{DateTime, Utc};
2use serde::de::DeserializeOwned;
3
4use crate::error::{FlowError, Result};
5use crate::model::{
6 CancellationRequest, ChildOperationReference, ChildWorkflowCancellationPolicy,
7 ChildWorkflowCommand, FlowEvent, FlowEventEnvelope, HookMetadata, JsonValue, RetryPolicy,
8 RuntimeCommand, StepCommand, WorkflowProgress, WorkflowSignal, WorkflowSpec,
9 WorkflowTerminalOutcome,
10};
11use crate::runtime::WorkflowInvocation;
12
13pub struct WorkflowContext<'a> {
19 invocation: &'a WorkflowInvocation,
20}
21
22impl<'a> WorkflowContext<'a> {
23 pub fn new(invocation: &'a WorkflowInvocation) -> Self {
25 Self { invocation }
26 }
27
28 pub fn run_id(&self) -> &str {
30 &self.invocation.run_id
31 }
32
33 pub fn input(&self) -> &JsonValue {
35 &self.invocation.input
36 }
37
38 pub fn spec(&self) -> &WorkflowSpec {
40 &self.invocation.spec
41 }
42
43 pub fn has_patch_marker(&self, patch_id: &str) -> bool {
49 self.spec().has_patch_marker(patch_id)
50 }
51
52 pub fn input_as<T>(&self) -> Result<T>
54 where
55 T: DeserializeOwned,
56 {
57 self.invocation.input_as()
58 }
59
60 pub fn history(&self) -> &[FlowEventEnvelope] {
62 &self.invocation.history
63 }
64
65 pub fn cancellation_request(&self) -> Option<&CancellationRequest> {
67 self.history()
68 .iter()
69 .find_map(|envelope| match &envelope.event {
70 FlowEvent::RunCancellationRequested { request } => Some(request),
71 _ => None,
72 })
73 }
74
75 pub fn progress(&self, progress_id: &str) -> Option<&WorkflowProgress> {
77 self.history()
78 .iter()
79 .find_map(|envelope| match &envelope.event {
80 FlowEvent::RunProgressRecorded { progress }
81 if progress.progress_id == progress_id =>
82 {
83 Some(progress)
84 }
85 _ => None,
86 })
87 }
88
89 pub fn child_operation(&self, reference_id: &str) -> Option<&ChildOperationReference> {
91 self.history()
92 .iter()
93 .find_map(|envelope| match &envelope.event {
94 FlowEvent::ChildOperationLinked { child } if child.reference_id == reference_id => {
95 Some(child)
96 }
97 _ => None,
98 })
99 }
100
101 pub fn child_workflow_run_id(&self, child_id: &str) -> Option<&str> {
103 self.history()
104 .iter()
105 .find_map(|envelope| match &envelope.event {
106 FlowEvent::ChildWorkflowRequested {
107 child_id: id,
108 child_run_id,
109 ..
110 } if id == child_id => Some(child_run_id.as_str()),
111 _ => None,
112 })
113 }
114
115 pub fn child_workflow_outcome(&self, child_id: &str) -> Option<&WorkflowTerminalOutcome> {
117 self.history()
118 .iter()
119 .rev()
120 .find_map(|envelope| match &envelope.event {
121 FlowEvent::ChildWorkflowResolved {
122 child_id: id,
123 outcome,
124 } if id == child_id => Some(outcome),
125 _ => None,
126 })
127 }
128
129 pub fn signal(&self, signal_id: &str) -> Option<&WorkflowSignal> {
131 self.history()
132 .iter()
133 .find_map(|envelope| match &envelope.event {
134 FlowEvent::SignalReceived { signal } if signal.signal_id == signal_id => {
135 Some(signal)
136 }
137 _ => None,
138 })
139 }
140
141 pub fn signal_payload(&self, wait_id: &str) -> Option<&JsonValue> {
143 let signal_id = self
144 .history()
145 .iter()
146 .find_map(|envelope| match &envelope.event {
147 FlowEvent::SignalWaitCompleted {
148 wait_id: completed_wait_id,
149 signal_id,
150 } if completed_wait_id == wait_id => Some(signal_id.as_str()),
151 _ => None,
152 })?;
153 self.signal(signal_id).map(|signal| &signal.payload)
154 }
155
156 pub fn signal_payload_as<T>(&self, wait_id: &str) -> Result<Option<T>>
158 where
159 T: DeserializeOwned,
160 {
161 self.signal_payload(wait_id)
162 .cloned()
163 .map(serde_json::from_value)
164 .transpose()
165 .map_err(FlowError::from)
166 }
167
168 pub fn step_output(&self, step_id: &str) -> Option<&JsonValue> {
170 self.history()
171 .iter()
172 .find_map(|envelope| match &envelope.event {
173 FlowEvent::StepCompleted {
174 step_id: id,
175 output,
176 } if id == step_id => Some(output),
177 _ => None,
178 })
179 }
180
181 pub fn step_output_as<T>(&self, step_id: &str) -> Result<Option<T>>
183 where
184 T: DeserializeOwned,
185 {
186 self.step_output(step_id)
187 .cloned()
188 .map(serde_json::from_value)
189 .transpose()
190 .map_err(FlowError::from)
191 }
192
193 pub fn step_completed(&self, step_id: &str) -> bool {
195 self.step_output(step_id).is_some()
196 }
197
198 pub fn step_failed(&self, step_id: &str) -> Option<&str> {
200 self.history()
201 .iter()
202 .rev()
203 .find_map(|envelope| match &envelope.event {
204 FlowEvent::StepFailed {
205 step_id: id, error, ..
206 } if id == step_id => Some(error.as_str()),
207 _ => None,
208 })
209 }
210
211 pub fn wait_completed(&self, wait_id: &str) -> bool {
213 self.history().iter().any(|envelope| {
214 matches!(
215 &envelope.event,
216 FlowEvent::WaitCompleted { wait_id: id } if id == wait_id
217 )
218 })
219 }
220
221 pub fn hook_payload(&self, hook_id: &str) -> Option<&JsonValue> {
223 self.history()
224 .iter()
225 .find_map(|envelope| match &envelope.event {
226 FlowEvent::HookReceived {
227 hook_id: id,
228 payload,
229 } if id == hook_id => Some(payload),
230 _ => None,
231 })
232 }
233
234 pub fn hook_payload_as<T>(&self, hook_id: &str) -> Result<Option<T>>
236 where
237 T: DeserializeOwned,
238 {
239 self.hook_payload(hook_id)
240 .cloned()
241 .map(serde_json::from_value)
242 .transpose()
243 .map_err(FlowError::from)
244 }
245
246 pub fn hook_disposed(&self, hook_id: &str) -> bool {
248 self.history().iter().any(|envelope| {
249 matches!(
250 &envelope.event,
251 FlowEvent::HookDisposed { hook_id: id } if id == hook_id
252 )
253 })
254 }
255
256 pub fn complete(&self, output: JsonValue) -> RuntimeCommand {
258 RuntimeCommand::Complete { output }
259 }
260
261 pub fn fail(&self, error: impl Into<String>) -> RuntimeCommand {
263 RuntimeCommand::Fail {
264 error: error.into(),
265 }
266 }
267
268 pub fn cancel(&self) -> RuntimeCommand {
270 RuntimeCommand::Cancel
271 }
272
273 pub fn timeout(&self, deadline: DateTime<Utc>, reason: Option<String>) -> RuntimeCommand {
275 RuntimeCommand::Timeout { deadline, reason }
276 }
277
278 pub fn continue_as_new(&self, input: JsonValue) -> RuntimeCommand {
283 RuntimeCommand::ContinueAsNew { input }
284 }
285
286 pub fn record_progress(&self, progress: WorkflowProgress) -> RuntimeCommand {
288 RuntimeCommand::RecordProgress { progress }
289 }
290
291 pub fn link_child_operation(&self, child: ChildOperationReference) -> RuntimeCommand {
293 RuntimeCommand::LinkChildOperation { child }
294 }
295
296 pub fn start_child_workflow(
302 &self,
303 child_id: impl Into<String>,
304 spec: WorkflowSpec,
305 input: JsonValue,
306 ) -> RuntimeCommand {
307 self.start_child_workflow_with_policy(
308 child_id,
309 spec,
310 input,
311 ChildWorkflowCancellationPolicy::default(),
312 )
313 }
314
315 pub fn start_child_workflow_with_policy(
317 &self,
318 child_id: impl Into<String>,
319 spec: WorkflowSpec,
320 input: JsonValue,
321 cancellation_policy: ChildWorkflowCancellationPolicy,
322 ) -> RuntimeCommand {
323 RuntimeCommand::StartChildWorkflow {
324 child_id: child_id.into(),
325 spec,
326 input,
327 cancellation_policy,
328 }
329 }
330
331 pub fn child_workflow(
333 &self,
334 child_id: impl Into<String>,
335 spec: WorkflowSpec,
336 input: JsonValue,
337 ) -> ChildWorkflowCommand {
338 ChildWorkflowCommand::new(child_id, spec, input)
339 }
340
341 pub fn child_workflow_with_policy(
343 &self,
344 child_id: impl Into<String>,
345 spec: WorkflowSpec,
346 input: JsonValue,
347 cancellation_policy: ChildWorkflowCancellationPolicy,
348 ) -> ChildWorkflowCommand {
349 self.child_workflow(child_id, spec, input)
350 .with_cancellation_policy(cancellation_policy)
351 }
352
353 pub fn start_child_workflows(&self, children: Vec<ChildWorkflowCommand>) -> RuntimeCommand {
355 RuntimeCommand::start_child_workflows(children)
356 }
357
358 pub fn schedule_step(
360 &self,
361 step_id: impl Into<String>,
362 step_name: impl Into<String>,
363 input: JsonValue,
364 ) -> RuntimeCommand {
365 RuntimeCommand::schedule_step(step_id, step_name, input)
366 }
367
368 pub fn schedule_step_with_retry(
370 &self,
371 step_id: impl Into<String>,
372 step_name: impl Into<String>,
373 input: JsonValue,
374 retry: RetryPolicy,
375 ) -> RuntimeCommand {
376 RuntimeCommand::ScheduleStep {
377 step_id: step_id.into(),
378 step_name: step_name.into(),
379 input,
380 retry,
381 }
382 }
383
384 pub fn step(
386 &self,
387 step_id: impl Into<String>,
388 step_name: impl Into<String>,
389 input: JsonValue,
390 ) -> StepCommand {
391 StepCommand::new(step_id, step_name, input)
392 }
393
394 pub fn step_with_retry(
396 &self,
397 step_id: impl Into<String>,
398 step_name: impl Into<String>,
399 input: JsonValue,
400 retry: RetryPolicy,
401 ) -> StepCommand {
402 StepCommand::new(step_id, step_name, input).with_retry(retry)
403 }
404
405 pub fn schedule_steps(&self, steps: Vec<StepCommand>) -> RuntimeCommand {
407 RuntimeCommand::schedule_steps(steps)
408 }
409
410 pub fn wait_until(
412 &self,
413 wait_id: impl Into<String>,
414 resume_at: DateTime<Utc>,
415 ) -> RuntimeCommand {
416 RuntimeCommand::WaitUntil {
417 wait_id: wait_id.into(),
418 resume_at,
419 }
420 }
421
422 pub fn create_hook(
424 &self,
425 hook_id: impl Into<String>,
426 token: impl Into<String>,
427 metadata: JsonValue,
428 ) -> RuntimeCommand {
429 RuntimeCommand::CreateHook {
430 hook_id: hook_id.into(),
431 token: token.into(),
432 metadata,
433 }
434 }
435
436 pub fn create_hook_with_metadata(
438 &self,
439 hook_id: impl Into<String>,
440 token: impl Into<String>,
441 metadata: HookMetadata,
442 ) -> Result<RuntimeCommand> {
443 Ok(self.create_hook(hook_id, token, metadata.into_json()?))
444 }
445
446 pub fn wait_for_signal(
449 &self,
450 wait_id: impl Into<String>,
451 signal_name: impl Into<String>,
452 ) -> RuntimeCommand {
453 RuntimeCommand::WaitForSignal {
454 wait_id: wait_id.into(),
455 signal_name: signal_name.into(),
456 }
457 }
458}