1use chrono::{DateTime, Utc};
2use serde::de::DeserializeOwned;
3
4use crate::error::{FlowError, Result};
5use crate::model::{
6 CancellationRequest, ChildOperationReference, FlowEvent, FlowEventEnvelope, HookMetadata,
7 JsonValue, RetryPolicy, RuntimeCommand, StepCommand, WorkflowProgress,
8};
9use crate::runtime::WorkflowInvocation;
10
11pub struct WorkflowContext<'a> {
17 invocation: &'a WorkflowInvocation,
18}
19
20impl<'a> WorkflowContext<'a> {
21 pub fn new(invocation: &'a WorkflowInvocation) -> Self {
22 Self { invocation }
23 }
24
25 pub fn run_id(&self) -> &str {
26 &self.invocation.run_id
27 }
28
29 pub fn input(&self) -> &JsonValue {
30 &self.invocation.input
31 }
32
33 pub fn input_as<T>(&self) -> Result<T>
35 where
36 T: DeserializeOwned,
37 {
38 self.invocation.input_as()
39 }
40
41 pub fn history(&self) -> &[FlowEventEnvelope] {
42 &self.invocation.history
43 }
44
45 pub fn cancellation_request(&self) -> Option<&CancellationRequest> {
47 self.history()
48 .iter()
49 .find_map(|envelope| match &envelope.event {
50 FlowEvent::RunCancellationRequested { request } => Some(request),
51 _ => None,
52 })
53 }
54
55 pub fn progress(&self, progress_id: &str) -> Option<&WorkflowProgress> {
57 self.history()
58 .iter()
59 .find_map(|envelope| match &envelope.event {
60 FlowEvent::RunProgressRecorded { progress }
61 if progress.progress_id == progress_id =>
62 {
63 Some(progress)
64 }
65 _ => None,
66 })
67 }
68
69 pub fn child_operation(&self, reference_id: &str) -> Option<&ChildOperationReference> {
71 self.history()
72 .iter()
73 .find_map(|envelope| match &envelope.event {
74 FlowEvent::ChildOperationLinked { child } if child.reference_id == reference_id => {
75 Some(child)
76 }
77 _ => None,
78 })
79 }
80
81 pub fn step_output(&self, step_id: &str) -> Option<&JsonValue> {
82 self.history()
83 .iter()
84 .find_map(|envelope| match &envelope.event {
85 FlowEvent::StepCompleted {
86 step_id: id,
87 output,
88 } if id == step_id => Some(output),
89 _ => None,
90 })
91 }
92
93 pub fn step_output_as<T>(&self, step_id: &str) -> Result<Option<T>>
94 where
95 T: DeserializeOwned,
96 {
97 self.step_output(step_id)
98 .cloned()
99 .map(serde_json::from_value)
100 .transpose()
101 .map_err(FlowError::from)
102 }
103
104 pub fn step_completed(&self, step_id: &str) -> bool {
105 self.step_output(step_id).is_some()
106 }
107
108 pub fn step_failed(&self, step_id: &str) -> Option<&str> {
109 self.history()
110 .iter()
111 .rev()
112 .find_map(|envelope| match &envelope.event {
113 FlowEvent::StepFailed {
114 step_id: id, error, ..
115 } if id == step_id => Some(error.as_str()),
116 _ => None,
117 })
118 }
119
120 pub fn wait_completed(&self, wait_id: &str) -> bool {
121 self.history().iter().any(|envelope| {
122 matches!(
123 &envelope.event,
124 FlowEvent::WaitCompleted { wait_id: id } if id == wait_id
125 )
126 })
127 }
128
129 pub fn hook_payload(&self, hook_id: &str) -> Option<&JsonValue> {
130 self.history()
131 .iter()
132 .find_map(|envelope| match &envelope.event {
133 FlowEvent::HookReceived {
134 hook_id: id,
135 payload,
136 } if id == hook_id => Some(payload),
137 _ => None,
138 })
139 }
140
141 pub fn hook_payload_as<T>(&self, hook_id: &str) -> Result<Option<T>>
142 where
143 T: DeserializeOwned,
144 {
145 self.hook_payload(hook_id)
146 .cloned()
147 .map(serde_json::from_value)
148 .transpose()
149 .map_err(FlowError::from)
150 }
151
152 pub fn hook_disposed(&self, hook_id: &str) -> bool {
153 self.history().iter().any(|envelope| {
154 matches!(
155 &envelope.event,
156 FlowEvent::HookDisposed { hook_id: id } if id == hook_id
157 )
158 })
159 }
160
161 pub fn complete(&self, output: JsonValue) -> RuntimeCommand {
162 RuntimeCommand::Complete { output }
163 }
164
165 pub fn fail(&self, error: impl Into<String>) -> RuntimeCommand {
166 RuntimeCommand::Fail {
167 error: error.into(),
168 }
169 }
170
171 pub fn cancel(&self) -> RuntimeCommand {
173 RuntimeCommand::Cancel
174 }
175
176 pub fn timeout(&self, deadline: DateTime<Utc>, reason: Option<String>) -> RuntimeCommand {
178 RuntimeCommand::Timeout { deadline, reason }
179 }
180
181 pub fn record_progress(&self, progress: WorkflowProgress) -> RuntimeCommand {
183 RuntimeCommand::RecordProgress { progress }
184 }
185
186 pub fn link_child_operation(&self, child: ChildOperationReference) -> RuntimeCommand {
188 RuntimeCommand::LinkChildOperation { child }
189 }
190
191 pub fn schedule_step(
192 &self,
193 step_id: impl Into<String>,
194 step_name: impl Into<String>,
195 input: JsonValue,
196 ) -> RuntimeCommand {
197 RuntimeCommand::schedule_step(step_id, step_name, input)
198 }
199
200 pub fn schedule_step_with_retry(
201 &self,
202 step_id: impl Into<String>,
203 step_name: impl Into<String>,
204 input: JsonValue,
205 retry: RetryPolicy,
206 ) -> RuntimeCommand {
207 RuntimeCommand::ScheduleStep {
208 step_id: step_id.into(),
209 step_name: step_name.into(),
210 input,
211 retry,
212 }
213 }
214
215 pub fn step(
216 &self,
217 step_id: impl Into<String>,
218 step_name: impl Into<String>,
219 input: JsonValue,
220 ) -> StepCommand {
221 StepCommand::new(step_id, step_name, input)
222 }
223
224 pub fn step_with_retry(
225 &self,
226 step_id: impl Into<String>,
227 step_name: impl Into<String>,
228 input: JsonValue,
229 retry: RetryPolicy,
230 ) -> StepCommand {
231 StepCommand::new(step_id, step_name, input).with_retry(retry)
232 }
233
234 pub fn schedule_steps(&self, steps: Vec<StepCommand>) -> RuntimeCommand {
235 RuntimeCommand::schedule_steps(steps)
236 }
237
238 pub fn wait_until(
239 &self,
240 wait_id: impl Into<String>,
241 resume_at: DateTime<Utc>,
242 ) -> RuntimeCommand {
243 RuntimeCommand::WaitUntil {
244 wait_id: wait_id.into(),
245 resume_at,
246 }
247 }
248
249 pub fn create_hook(
250 &self,
251 hook_id: impl Into<String>,
252 token: impl Into<String>,
253 metadata: JsonValue,
254 ) -> RuntimeCommand {
255 RuntimeCommand::CreateHook {
256 hook_id: hook_id.into(),
257 token: token.into(),
258 metadata,
259 }
260 }
261
262 pub fn create_hook_with_metadata(
263 &self,
264 hook_id: impl Into<String>,
265 token: impl Into<String>,
266 metadata: HookMetadata,
267 ) -> Result<RuntimeCommand> {
268 Ok(self.create_hook(hook_id, token, metadata.into_json()?))
269 }
270}