1use chrono::{DateTime, Utc};
2use serde::de::DeserializeOwned;
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6use crate::error::{FlowError, Result};
7use crate::runtime_build::RuntimeBuildId;
8
9use super::{
10 CancellationRequestSnapshot, ChildOperationReference, ChildWorkflowSnapshot, JsonValue,
11 RetryPolicy, SignalWaitSnapshot, SignalWaitStatus, WorkflowContinuation, WorkflowProgress,
12 WorkflowSignalSnapshot, WorkflowSpec, WorkflowTerminalOutcome,
13};
14
15#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
17#[non_exhaustive]
18#[serde(rename_all = "snake_case")]
19pub enum WorkflowRunStatus {
20 Pending,
22 Running,
24 Suspended,
26 Cancelling,
28 Completed,
30 Failed,
32 Cancelled,
34 ContinuedAsNew,
36}
37
38impl WorkflowRunStatus {
39 pub fn is_terminal(self) -> bool {
41 matches!(
42 self,
43 Self::Completed | Self::Failed | Self::Cancelled | Self::ContinuedAsNew
44 )
45 }
46}
47
48#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
50#[non_exhaustive]
51#[serde(rename_all = "snake_case")]
52pub enum StepStatus {
53 Pending,
55 Running,
57 Completed,
59 Failed,
61 Cancelled,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
67#[non_exhaustive]
68pub struct StepSnapshot {
69 pub step_id: String,
71 pub step_name: String,
73 pub status: StepStatus,
75 pub input: JsonValue,
77 pub retry: RetryPolicy,
79 pub output: Option<JsonValue>,
81 pub error: Option<String>,
83 pub attempt: u32,
85 pub retry_after: Option<DateTime<Utc>>,
87}
88
89impl StepSnapshot {
90 pub fn output_as<T>(&self) -> Result<Option<T>>
92 where
93 T: DeserializeOwned,
94 {
95 self.output
96 .clone()
97 .map(serde_json::from_value)
98 .transpose()
99 .map_err(FlowError::from)
100 }
101}
102
103#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
105#[non_exhaustive]
106#[serde(rename_all = "snake_case")]
107pub enum WaitStatus {
108 Waiting,
110 Completed,
112 Cancelled,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
118#[non_exhaustive]
119pub struct WaitSnapshot {
120 pub wait_id: String,
122 pub status: WaitStatus,
124 pub resume_at: DateTime<Utc>,
126}
127
128#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
130#[non_exhaustive]
131#[serde(rename_all = "snake_case")]
132pub enum HookStatus {
133 Active,
135 Received,
137 Disposed,
139 Cancelled,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
145#[non_exhaustive]
146pub struct HookSnapshot {
147 pub hook_id: String,
149 pub token: String,
151 pub status: HookStatus,
153 pub metadata: JsonValue,
155 pub payload: Option<JsonValue>,
157}
158
159impl HookSnapshot {
160 pub fn new(
162 hook_id: impl Into<String>,
163 token: impl Into<String>,
164 status: HookStatus,
165 metadata: JsonValue,
166 payload: Option<JsonValue>,
167 ) -> Self {
168 Self {
169 hook_id: hook_id.into(),
170 token: token.into(),
171 status,
172 metadata,
173 payload,
174 }
175 }
176
177 pub fn metadata_as<T>(&self) -> Result<T>
179 where
180 T: DeserializeOwned,
181 {
182 serde_json::from_value(self.metadata.clone()).map_err(FlowError::from)
183 }
184
185 pub fn payload_as<T>(&self) -> Result<Option<T>>
187 where
188 T: DeserializeOwned,
189 {
190 self.payload
191 .clone()
192 .map(serde_json::from_value)
193 .transpose()
194 .map_err(FlowError::from)
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
200#[non_exhaustive]
201pub struct ActiveHookSnapshot {
202 pub run_id: String,
204 pub hook: HookSnapshot,
206}
207
208impl ActiveHookSnapshot {
209 pub fn new(run_id: impl Into<String>, hook: HookSnapshot) -> Self {
211 Self {
212 run_id: run_id.into(),
213 hook,
214 }
215 }
216
217 pub fn metadata_as<T>(&self) -> Result<T>
219 where
220 T: DeserializeOwned,
221 {
222 self.hook.metadata_as()
223 }
224}
225
226#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
228#[non_exhaustive]
229#[serde(rename_all = "snake_case")]
230pub enum ScheduledWakeupKind {
231 Wait,
233 Retry,
235}
236
237impl ScheduledWakeupKind {
238 #[cfg(any(feature = "postgres", feature = "sqlite"))]
239 pub(crate) fn from_database_code(code: i64) -> Result<Self> {
240 match code {
241 0 => Ok(Self::Wait),
242 2 => Ok(Self::Retry),
243 _ => Err(FlowError::Store(format!(
244 "invalid scheduled wakeup kind code {code}"
245 ))),
246 }
247 }
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
252#[non_exhaustive]
253pub struct ScheduledWakeup {
254 pub run_id: String,
256 pub kind: ScheduledWakeupKind,
258 pub subject_id: String,
260 pub scheduled_at: DateTime<Utc>,
262 #[serde(default, skip_serializing_if = "Option::is_none")]
264 pub runtime_build_id: Option<RuntimeBuildId>,
265}
266
267impl ScheduledWakeup {
268 pub fn new(
270 run_id: impl Into<String>,
271 kind: ScheduledWakeupKind,
272 subject_id: impl Into<String>,
273 scheduled_at: DateTime<Utc>,
274 runtime_build_id: Option<RuntimeBuildId>,
275 ) -> Self {
276 Self {
277 run_id: run_id.into(),
278 kind,
279 subject_id: subject_id.into(),
280 scheduled_at,
281 runtime_build_id,
282 }
283 }
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
288#[non_exhaustive]
289pub struct WorkflowRunSnapshot {
290 pub run_id: String,
292 pub spec: WorkflowSpec,
294 pub input: JsonValue,
296 pub status: WorkflowRunStatus,
298 pub steps: BTreeMap<String, StepSnapshot>,
300 pub waits: BTreeMap<String, WaitSnapshot>,
302 pub hooks: BTreeMap<String, HookSnapshot>,
304 #[serde(default)]
306 pub cancellation: Option<CancellationRequestSnapshot>,
307 #[serde(default)]
309 pub progress: Vec<WorkflowProgress>,
310 #[serde(default)]
312 pub child_operations: BTreeMap<String, ChildOperationReference>,
313 #[serde(default)]
315 pub child_workflows: BTreeMap<String, ChildWorkflowSnapshot>,
316 #[serde(default)]
318 pub signals: Vec<WorkflowSignalSnapshot>,
319 #[serde(default)]
321 pub signal_waits: BTreeMap<String, SignalWaitSnapshot>,
322 pub output: Option<JsonValue>,
324 pub error: Option<String>,
326 #[serde(default)]
328 pub terminal_outcome: Option<WorkflowTerminalOutcome>,
329 #[serde(default, skip_serializing_if = "Option::is_none")]
331 pub continuation: Option<WorkflowContinuation>,
332 pub last_sequence: u64,
334}
335
336impl WorkflowRunSnapshot {
337 pub fn new(run_id: impl Into<String>, spec: WorkflowSpec, input: JsonValue) -> Self {
343 Self {
344 run_id: run_id.into(),
345 spec,
346 input,
347 status: WorkflowRunStatus::Pending,
348 steps: BTreeMap::new(),
349 waits: BTreeMap::new(),
350 hooks: BTreeMap::new(),
351 cancellation: None,
352 progress: Vec::new(),
353 child_operations: BTreeMap::new(),
354 child_workflows: BTreeMap::new(),
355 signals: Vec::new(),
356 signal_waits: BTreeMap::new(),
357 output: None,
358 error: None,
359 terminal_outcome: None,
360 continuation: None,
361 last_sequence: 0,
362 }
363 }
364
365 pub fn input_as<T>(&self) -> Result<T>
367 where
368 T: DeserializeOwned,
369 {
370 serde_json::from_value(self.input.clone()).map_err(FlowError::from)
371 }
372
373 pub fn output_as<T>(&self) -> Result<Option<T>>
375 where
376 T: DeserializeOwned,
377 {
378 self.output
379 .clone()
380 .map(serde_json::from_value)
381 .transpose()
382 .map_err(FlowError::from)
383 }
384
385 pub fn step_output(&self, step_id: &str) -> Option<&JsonValue> {
387 self.steps
388 .get(step_id)
389 .and_then(|step| step.output.as_ref())
390 }
391
392 pub fn step_output_as<T>(&self, step_id: &str) -> Result<Option<T>>
394 where
395 T: DeserializeOwned,
396 {
397 match self.steps.get(step_id) {
398 Some(step) => step.output_as(),
399 None => Ok(None),
400 }
401 }
402
403 pub fn hook_payload(&self, hook_id: &str) -> Option<&JsonValue> {
405 self.hooks
406 .get(hook_id)
407 .and_then(|hook| hook.payload.as_ref())
408 }
409
410 pub fn progress(&self, progress_id: &str) -> Option<&WorkflowProgress> {
412 self.progress
413 .iter()
414 .find(|progress| progress.progress_id == progress_id)
415 }
416
417 pub fn latest_progress(&self) -> Option<&WorkflowProgress> {
419 self.progress.last()
420 }
421
422 pub fn child_operation(&self, reference_id: &str) -> Option<&ChildOperationReference> {
424 self.child_operations.get(reference_id)
425 }
426
427 pub fn child_workflow(&self, child_id: &str) -> Option<&ChildWorkflowSnapshot> {
429 self.child_workflows.get(child_id)
430 }
431
432 pub fn signal(&self, signal_id: &str) -> Option<&WorkflowSignalSnapshot> {
434 self.signals
435 .iter()
436 .find(|signal| signal.signal_id == signal_id)
437 }
438
439 pub fn signal_wait_payload(&self, wait_id: &str) -> Option<&JsonValue> {
441 let signal_id = self.signal_waits.get(wait_id)?.signal_id.as_deref()?;
442 self.signal(signal_id).map(|signal| &signal.payload)
443 }
444
445 pub fn signal_wait_payload_as<T>(&self, wait_id: &str) -> Result<Option<T>>
447 where
448 T: DeserializeOwned,
449 {
450 self.signal_wait_payload(wait_id)
451 .cloned()
452 .map(serde_json::from_value)
453 .transpose()
454 .map_err(FlowError::from)
455 }
456
457 pub fn hook_metadata_as<T>(&self, hook_id: &str) -> Result<Option<T>>
459 where
460 T: DeserializeOwned,
461 {
462 match self.hooks.get(hook_id) {
463 Some(hook) => hook.metadata_as().map(Some),
464 None => Ok(None),
465 }
466 }
467
468 pub fn hook_payload_as<T>(&self, hook_id: &str) -> Result<Option<T>>
470 where
471 T: DeserializeOwned,
472 {
473 match self.hooks.get(hook_id) {
474 Some(hook) => hook.payload_as(),
475 None => Ok(None),
476 }
477 }
478
479 pub fn has_open_suspension(&self) -> bool {
481 self.waits
482 .values()
483 .any(|wait| wait.status == WaitStatus::Waiting)
484 || self
485 .hooks
486 .values()
487 .any(|hook| hook.status == HookStatus::Active)
488 || self.steps.values().any(|step| step.retry_after.is_some())
489 || self
490 .child_workflows
491 .values()
492 .any(ChildWorkflowSnapshot::is_open)
493 || self
494 .signal_waits
495 .values()
496 .any(|wait| wait.status == SignalWaitStatus::Waiting)
497 }
498
499 pub fn due_retries(&self, now: DateTime<Utc>) -> Vec<(String, DateTime<Utc>)> {
501 self.steps
502 .values()
503 .filter_map(|step| match step.retry_after {
504 Some(retry_after) if step.status == StepStatus::Pending && retry_after <= now => {
505 Some((step.step_id.clone(), retry_after))
506 }
507 _ => None,
508 })
509 .collect()
510 }
511
512 pub fn has_future_retry(&self, now: DateTime<Utc>) -> bool {
514 self.steps.values().any(|step| {
515 step.status == StepStatus::Pending
516 && step
517 .retry_after
518 .map(|retry_after| retry_after > now)
519 .unwrap_or(false)
520 })
521 }
522}