1use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::collections::BTreeMap;
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13pub struct TaskError {
14 pub code: String,
15 pub message: String,
16 #[serde(default = "yes")]
17 pub retryable: bool,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub details: Option<Value>,
20}
21
22fn yes() -> bool {
23 true
24}
25
26impl TaskError {
27 pub fn new(code: &str, message: impl Into<String>, retryable: bool) -> Self {
28 TaskError {
29 code: code.into(),
30 message: message.into(),
31 retryable,
32 details: None,
33 }
34 }
35}
36
37pub mod codes {
39 pub const TIMEOUT: &str = "TIMEOUT";
40 pub const QUEUE_TIMEOUT: &str = "QUEUE_TIMEOUT";
41 pub const EXPRESSION_ERROR: &str = "EXPRESSION_ERROR";
42 pub const INPUT_CONTRACT_VIOLATION: &str = "INPUT_CONTRACT_VIOLATION";
43 pub const OUTPUT_CONTRACT_VIOLATION: &str = "OUTPUT_CONTRACT_VIOLATION";
44 pub const CANCELLED: &str = "CANCELLED";
45 pub const WORKFLOW_TIMEOUT: &str = "WORKFLOW_TIMEOUT";
46 pub const ADAPTER_DISABLED: &str = "ADAPTER_DISABLED";
47 pub const HTTP_ERROR: &str = "HTTP_ERROR";
48 pub const SIGNAL_TIMEOUT: &str = "SIGNAL_TIMEOUT";
49 pub const LIMIT_EXCEEDED: &str = "LIMIT_EXCEEDED";
50}
51
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53#[serde(tag = "type")]
54pub enum Event {
55 WorkflowCreated {
56 workflow: String,
57 version: String,
58 definition_hash: String,
59 input: Value,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 parent: Option<ParentRef>,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 schedule_id: Option<String>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
69 run: Option<u32>,
70 },
71 StepFanOut {
73 step: String,
74 items: Vec<Value>,
75 },
76 WorkflowStarted {},
77 StepScheduled {
79 step: String,
80 attempt: u32,
81 input: Value,
82 idempotency_key: String,
83 redelivery: bool,
84 },
85 StepSkipped {
86 step: String,
87 reason: String,
88 },
89 StepDispatched {
90 step: String,
91 attempt: u32,
92 token: String,
93 worker_id: String,
94 worker_session: String,
95 deadline_ms: Option<u64>,
96 },
97 StepSucceeded {
98 step: String,
99 attempt: u32,
100 output: Value,
101 },
102 StepAttemptFailed {
103 step: String,
104 attempt: u32,
105 error: TaskError,
106 },
107 StepAttemptUncertain {
110 step: String,
111 attempt: u32,
112 reason: String,
113 },
114 RetryScheduled {
115 step: String,
116 next_attempt: u32,
117 delay_ms: u64,
118 fire_at_ms: u64,
119 },
120 StepFailed {
121 step: String,
122 error: TaskError,
123 },
124 StepCancelled {
126 step: String,
127 },
128 TimerStarted {
129 step: String,
130 fire_at_ms: u64,
131 },
132 TimerFired {
133 step: String,
134 },
135 StaleResultIgnored {
137 step: String,
138 token: String,
139 outcome: String,
140 },
141 CancelRequested {
142 reason: String,
143 },
144 WorkflowTimeoutReached {},
145 CompensationStarted {
146 reason: String,
147 steps: Vec<String>,
148 },
149 CompensationCompleted {},
150 CompensationFailed {
151 step: String,
152 error: TaskError,
153 },
154 WorkflowCompleted {
155 output: Value,
156 },
157 WorkflowFailed {
158 error: TaskError,
159 compensation: CompensationOutcome,
160 },
161 WorkflowCancelled {
162 compensation: CompensationOutcome,
163 },
164 WorkflowTimedOut {
165 compensation: CompensationOutcome,
166 },
167 SignalReceived {
170 name: String,
171 payload: Value,
172 },
173 SignalAwaitStarted {
175 step: String,
176 signal: String,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
178 deadline_ms: Option<u64>,
179 },
180 LimitExceeded {
183 limit: String,
184 message: String,
185 },
186 HandlersStarted {
189 kind: String,
190 compensation: CompensationOutcome,
191 },
192 SignalDelivered {
195 step: String,
196 signal_seq: u64,
197 },
198 WorkflowContinuedAsNew {
203 next_run: u32,
204 input: Value,
205 },
206}
207
208impl Event {
209 pub fn name(&self) -> &'static str {
210 match self {
211 Event::WorkflowCreated { .. } => "WorkflowCreated",
212 Event::WorkflowStarted { .. } => "WorkflowStarted",
213 Event::StepFanOut { .. } => "StepFanOut",
214 Event::StepScheduled { .. } => "StepScheduled",
215 Event::StepSkipped { .. } => "StepSkipped",
216 Event::StepDispatched { .. } => "StepDispatched",
217 Event::StepSucceeded { .. } => "StepSucceeded",
218 Event::StepAttemptFailed { .. } => "StepAttemptFailed",
219 Event::StepAttemptUncertain { .. } => "StepAttemptUncertain",
220 Event::RetryScheduled { .. } => "RetryScheduled",
221 Event::StepFailed { .. } => "StepFailed",
222 Event::StepCancelled { .. } => "StepCancelled",
223 Event::TimerStarted { .. } => "TimerStarted",
224 Event::TimerFired { .. } => "TimerFired",
225 Event::StaleResultIgnored { .. } => "StaleResultIgnored",
226 Event::CancelRequested { .. } => "CancelRequested",
227 Event::WorkflowTimeoutReached { .. } => "WorkflowTimeoutReached",
228 Event::CompensationStarted { .. } => "CompensationStarted",
229 Event::CompensationCompleted { .. } => "CompensationCompleted",
230 Event::CompensationFailed { .. } => "CompensationFailed",
231 Event::WorkflowCompleted { .. } => "WorkflowCompleted",
232 Event::WorkflowFailed { .. } => "WorkflowFailed",
233 Event::WorkflowCancelled { .. } => "WorkflowCancelled",
234 Event::WorkflowTimedOut { .. } => "WorkflowTimedOut",
235 Event::SignalReceived { .. } => "SignalReceived",
236 Event::HandlersStarted { .. } => "HandlersStarted",
237 Event::LimitExceeded { .. } => "LimitExceeded",
238 Event::SignalAwaitStarted { .. } => "SignalAwaitStarted",
239 Event::SignalDelivered { .. } => "SignalDelivered",
240 Event::WorkflowContinuedAsNew { .. } => "WorkflowContinuedAsNew",
241 }
242 }
243
244 pub fn step(&self) -> Option<&str> {
245 match self {
246 Event::StepScheduled { step, .. }
247 | Event::StepFanOut { step, .. }
248 | Event::StepSkipped { step, .. }
249 | Event::StepDispatched { step, .. }
250 | Event::StepSucceeded { step, .. }
251 | Event::StepAttemptFailed { step, .. }
252 | Event::StepAttemptUncertain { step, .. }
253 | Event::RetryScheduled { step, .. }
254 | Event::StepFailed { step, .. }
255 | Event::StepCancelled { step }
256 | Event::TimerStarted { step, .. }
257 | Event::TimerFired { step }
258 | Event::StaleResultIgnored { step, .. }
259 | Event::SignalAwaitStarted { step, .. }
260 | Event::SignalDelivered { step, .. }
261 | Event::CompensationFailed { step, .. } => Some(step),
262 _ => None,
263 }
264 }
265}
266
267#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
268#[serde(rename_all = "snake_case")]
269pub enum CompensationOutcome {
270 None,
272 Completed,
273 Failed,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct HistoryEvent {
279 pub seq: u64,
280 pub timestamp_ms: u64,
281 #[serde(flatten)]
282 pub event: Event,
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
286#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
287pub enum WorkflowStatus {
288 Running,
289 Compensating,
290 Completed,
291 Failed,
292 Cancelled,
293 TimedOut,
294 ContinuedAsNew,
297}
298
299impl WorkflowStatus {
300 pub fn is_terminal(self) -> bool {
301 matches!(
302 self,
303 WorkflowStatus::Completed
304 | WorkflowStatus::Failed
305 | WorkflowStatus::Cancelled
306 | WorkflowStatus::TimedOut
307 | WorkflowStatus::ContinuedAsNew
308 )
309 }
310
311 pub fn as_str(self) -> &'static str {
312 match self {
313 WorkflowStatus::Running => "RUNNING",
314 WorkflowStatus::Compensating => "COMPENSATING",
315 WorkflowStatus::Completed => "COMPLETED",
316 WorkflowStatus::Failed => "FAILED",
317 WorkflowStatus::Cancelled => "CANCELLED",
318 WorkflowStatus::TimedOut => "TIMED_OUT",
319 WorkflowStatus::ContinuedAsNew => "CONTINUED_AS_NEW",
320 }
321 }
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
325#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
326pub enum UnitStatus {
327 Pending,
328 Running,
330 Scheduled,
332 Dispatched,
333 AttemptFailed,
335 Uncertain,
337 RetryWait,
338 Waiting,
340 Succeeded,
341 Skipped,
342 Failed,
343 Cancelled,
344}
345
346impl UnitStatus {
347 pub fn is_terminal(self) -> bool {
348 matches!(
349 self,
350 UnitStatus::Succeeded
351 | UnitStatus::Skipped
352 | UnitStatus::Failed
353 | UnitStatus::Cancelled
354 )
355 }
356}
357
358#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
360#[serde(tag = "kind", rename_all = "snake_case")]
361pub enum AbortReason {
362 Failed { error: TaskError },
363 Cancelled { reason: String },
364 TimedOut,
365}
366
367#[derive(Debug, Clone, Default, Serialize, Deserialize)]
370pub struct UnitState {
371 pub status: Option<UnitStatus>,
372 pub attempt: u32,
373 #[serde(skip_serializing_if = "Option::is_none")]
374 pub input: Option<Value>,
375 #[serde(skip_serializing_if = "Option::is_none")]
376 pub idempotency_key: Option<String>,
377 #[serde(skip_serializing_if = "Option::is_none")]
378 pub output: Option<Value>,
379 #[serde(skip_serializing_if = "Option::is_none")]
380 pub error: Option<TaskError>,
381 #[serde(skip_serializing_if = "Option::is_none")]
382 pub last_error: Option<TaskError>,
383 pub redelivery: bool,
384 #[serde(skip_serializing_if = "Option::is_none")]
385 pub scheduled_at_ms: Option<u64>,
386 #[serde(skip_serializing_if = "Option::is_none")]
387 pub dispatched_at_ms: Option<u64>,
388 #[serde(skip_serializing_if = "Option::is_none")]
389 pub deadline_ms: Option<u64>,
390 #[serde(skip_serializing_if = "Option::is_none")]
391 pub wake_at_ms: Option<u64>,
392 #[serde(skip_serializing_if = "Option::is_none")]
393 pub finished_at_ms: Option<u64>,
394 #[serde(skip_serializing_if = "Option::is_none")]
395 pub worker_id: Option<String>,
396 #[serde(skip_serializing_if = "Option::is_none")]
397 pub worker_session: Option<String>,
398 #[serde(skip_serializing_if = "Option::is_none")]
400 pub token: Option<String>,
401 #[serde(default, skip_serializing_if = "Vec::is_empty")]
404 pub tokens: Vec<(String, u32)>,
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub succeeded_seq: Option<u64>,
408 pub dispatch_count: u32,
409}
410
411impl UnitState {
412 pub fn status(&self) -> UnitStatus {
413 self.status.unwrap_or(UnitStatus::Pending)
414 }
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize)]
418pub struct ExecutionState {
419 pub id: String,
420 #[serde(default = "first_run", skip_serializing_if = "is_first_run")]
423 pub run: u32,
424 #[serde(default, skip_serializing_if = "Option::is_none")]
425 pub parent: Option<ParentRef>,
426 #[serde(default, skip_serializing_if = "Option::is_none")]
428 pub schedule_id: Option<String>,
429 pub workflow: String,
430 pub version: String,
431 pub definition_hash: String,
432 pub input: Value,
433 pub status: WorkflowStatus,
434 pub created_at_ms: u64,
435 pub updated_at_ms: u64,
436 #[serde(skip_serializing_if = "Option::is_none")]
437 pub finished_at_ms: Option<u64>,
438 pub units: BTreeMap<String, UnitState>,
439 #[serde(skip_serializing_if = "Option::is_none")]
440 pub abort: Option<AbortReason>,
441 #[serde(default, skip_serializing_if = "Vec::is_empty")]
442 pub compensation_order: Vec<String>,
443 #[serde(skip_serializing_if = "Option::is_none")]
444 pub compensation: Option<CompensationOutcome>,
445 #[serde(skip_serializing_if = "Option::is_none")]
446 pub output: Option<Value>,
447 #[serde(skip_serializing_if = "Option::is_none")]
448 pub error: Option<TaskError>,
449 #[serde(default, skip_serializing_if = "Vec::is_empty")]
451 pub signals: Vec<ReceivedSignal>,
452 #[serde(default, skip_serializing_if = "Option::is_none")]
454 pub handlers: Option<HandlerPhase>,
455 pub last_seq: u64,
456}
457
458fn first_run() -> u32 {
459 1
460}
461
462fn is_first_run(run: &u32) -> bool {
463 *run == 1
464}
465
466#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
468pub struct HandlerPhase {
469 pub kind: String,
470 pub compensation: CompensationOutcome,
471}
472
473pub fn is_cleanup_key(key: &str) -> bool {
476 key.starts_with("compensate:") || orbit_ir::parse_handler_key(key).is_some()
477}
478
479#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
481pub struct ReceivedSignal {
482 pub seq: u64,
484 pub name: String,
485 pub payload: Value,
486 pub received_at_ms: u64,
487 #[serde(default, skip_serializing_if = "Option::is_none")]
489 pub delivered_to: Option<String>,
490}
491
492#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
494pub struct ParentRef {
495 pub execution_id: String,
496 pub step: String,
497}
498
499pub fn element_key(step: &str, i: usize) -> String {
501 format!("{step}[{i}]")
502}
503
504#[derive(Debug, Clone, Copy, PartialEq, Eq)]
506pub struct UnitKey<'a> {
507 pub compensation: bool,
508 pub step: &'a str,
509 pub index: Option<usize>,
510}
511
512pub fn parse_key(key: &str) -> UnitKey<'_> {
513 let (compensation, rest) = match key.strip_prefix("compensate:") {
514 Some(r) => (true, r),
515 None => (false, key),
516 };
517 if let Some(open) = rest.find('[') {
518 if let Some(index) = rest[open + 1..]
519 .strip_suffix(']')
520 .and_then(|n| n.parse().ok())
521 {
522 return UnitKey {
523 compensation,
524 step: &rest[..open],
525 index: Some(index),
526 };
527 }
528 }
529 UnitKey {
530 compensation,
531 step: rest,
532 index: None,
533 }
534}
535
536pub fn compensation_key(step: &str) -> String {
537 format!("compensate:{step}")
538}
539
540impl ExecutionState {
541 pub fn new(id: &str) -> Self {
542 ExecutionState {
543 id: id.into(),
544 run: 1,
545 parent: None,
546 schedule_id: None,
547 workflow: String::new(),
548 version: String::new(),
549 definition_hash: String::new(),
550 input: Value::Null,
551 status: WorkflowStatus::Running,
552 created_at_ms: 0,
553 updated_at_ms: 0,
554 finished_at_ms: None,
555 units: BTreeMap::new(),
556 abort: None,
557 compensation_order: vec![],
558 compensation: None,
559 output: None,
560 error: None,
561 signals: vec![],
562 handlers: None,
563 last_seq: 0,
564 }
565 }
566
567 pub fn replay(id: &str, history: &[HistoryEvent]) -> Self {
569 let mut s = ExecutionState::new(id);
570 for h in history {
571 apply(&mut s, h);
572 }
573 s
574 }
575
576 pub fn unit(&self, key: &str) -> Option<&UnitState> {
577 self.units.get(key)
578 }
579}
580
581pub fn apply(s: &mut ExecutionState, h: &HistoryEvent) {
584 let ts = h.timestamp_ms;
585 s.last_seq = h.seq;
586 s.updated_at_ms = ts;
587 match &h.event {
588 Event::WorkflowCreated {
589 workflow,
590 version,
591 definition_hash,
592 input,
593 parent,
594 schedule_id,
595 run,
596 } => {
597 s.parent = parent.clone();
598 s.schedule_id = schedule_id.clone();
599 s.run = run.unwrap_or(1);
600 s.workflow = workflow.clone();
601 s.version = version.clone();
602 s.definition_hash = definition_hash.clone();
603 s.input = input.clone();
604 s.created_at_ms = ts;
605 s.status = WorkflowStatus::Running;
606 }
607 Event::WorkflowStarted {} => {}
608 Event::StepScheduled {
609 step,
610 attempt,
611 input,
612 idempotency_key,
613 redelivery,
614 } => {
615 let u = s.units.entry(step.clone()).or_default();
616 u.status = Some(UnitStatus::Scheduled);
617 u.attempt = *attempt;
618 u.input = Some(input.clone());
619 u.idempotency_key = Some(idempotency_key.clone());
620 u.redelivery = *redelivery;
621 u.scheduled_at_ms = Some(ts);
622 u.token = None;
623 u.deadline_ms = None;
624 u.wake_at_ms = None;
625 }
626 Event::StepFanOut { step, items } => {
627 let u = s.units.entry(step.clone()).or_default();
628 u.status = Some(UnitStatus::Running);
629 u.input = Some(Value::Array(items.clone()));
630 u.scheduled_at_ms = Some(ts);
631 u.attempt = 1;
632 for i in 0..items.len() {
633 s.units.entry(element_key(step, i)).or_default();
634 }
635 }
636 Event::StepSkipped { step, .. } => {
637 let u = s.units.entry(step.clone()).or_default();
638 u.status = Some(UnitStatus::Skipped);
639 u.output = Some(Value::Null);
640 u.finished_at_ms = Some(ts);
641 }
642 Event::StepDispatched {
643 step,
644 attempt,
645 token,
646 worker_id,
647 worker_session,
648 deadline_ms,
649 } => {
650 let u = s.units.entry(step.clone()).or_default();
651 u.status = Some(UnitStatus::Dispatched);
652 u.attempt = *attempt;
653 u.token = Some(token.clone());
654 u.tokens.push((token.clone(), *attempt));
655 u.worker_id = Some(worker_id.clone());
656 u.worker_session = Some(worker_session.clone());
657 u.dispatched_at_ms = Some(ts);
658 u.deadline_ms = *deadline_ms;
659 u.dispatch_count += 1;
660 }
661 Event::StepSucceeded {
662 step,
663 attempt,
664 output,
665 } => {
666 let u = s.units.entry(step.clone()).or_default();
667 u.status = Some(UnitStatus::Succeeded);
668 u.attempt = *attempt;
669 u.output = Some(output.clone());
670 u.finished_at_ms = Some(ts);
671 u.succeeded_seq = Some(h.seq);
672 u.token = None;
673 u.wake_at_ms = None;
674 }
675 Event::StepAttemptFailed { step, error, .. } => {
676 let u = s.units.entry(step.clone()).or_default();
677 u.status = Some(UnitStatus::AttemptFailed);
678 u.last_error = Some(error.clone());
679 u.token = None;
680 }
681 Event::StepAttemptUncertain { step, .. } => {
682 let u = s.units.entry(step.clone()).or_default();
683 u.status = Some(UnitStatus::Uncertain);
684 u.token = None;
685 }
686 Event::RetryScheduled {
687 step,
688 next_attempt,
689 fire_at_ms,
690 ..
691 } => {
692 let u = s.units.entry(step.clone()).or_default();
693 u.status = Some(UnitStatus::RetryWait);
694 u.attempt = *next_attempt;
695 u.wake_at_ms = Some(*fire_at_ms);
696 }
697 Event::StepFailed { step, error } => {
698 let u = s.units.entry(step.clone()).or_default();
699 u.status = Some(UnitStatus::Failed);
700 u.error = Some(error.clone());
701 u.finished_at_ms = Some(ts);
702 u.token = None;
703 if s.abort.is_none() && !step.starts_with("compensate:") {
704 s.abort = Some(AbortReason::Failed {
705 error: TaskError {
706 message: format!("step `{step}` failed: {}", error.message),
707 ..error.clone()
708 },
709 });
710 }
711 }
712 Event::StepCancelled { step } => {
713 let u = s.units.entry(step.clone()).or_default();
714 u.status = Some(UnitStatus::Cancelled);
715 u.finished_at_ms = Some(ts);
716 u.token = None;
717 }
718 Event::TimerStarted { step, fire_at_ms } => {
719 let u = s.units.entry(step.clone()).or_default();
720 u.status = Some(UnitStatus::Waiting);
721 u.wake_at_ms = Some(*fire_at_ms);
722 u.scheduled_at_ms = Some(ts);
723 u.attempt = 1;
724 }
725 Event::TimerFired { step } => {
726 let u = s.units.entry(step.clone()).or_default();
727 u.status = Some(UnitStatus::Succeeded);
728 u.output = Some(Value::Object(Default::default()));
729 u.finished_at_ms = Some(ts);
730 u.succeeded_seq = Some(h.seq);
731 u.wake_at_ms = None;
732 }
733 Event::StaleResultIgnored { .. } => {}
734 Event::CancelRequested { reason } => {
735 if s.abort.is_none() {
736 s.abort = Some(AbortReason::Cancelled {
737 reason: reason.clone(),
738 });
739 }
740 }
741 Event::WorkflowTimeoutReached {} => {
742 if s.abort.is_none() {
743 s.abort = Some(AbortReason::TimedOut);
744 }
745 }
746 Event::CompensationStarted { steps, .. } => {
747 s.status = WorkflowStatus::Compensating;
748 s.compensation_order = steps.clone();
749 for st in steps {
750 s.units.entry(compensation_key(st)).or_default();
751 }
752 }
753 Event::CompensationCompleted {} => s.compensation = Some(CompensationOutcome::Completed),
754 Event::CompensationFailed { .. } => s.compensation = Some(CompensationOutcome::Failed),
755 Event::WorkflowCompleted { output } => {
756 s.status = WorkflowStatus::Completed;
757 s.output = Some(output.clone());
758 s.finished_at_ms = Some(ts);
759 }
760 Event::WorkflowFailed {
761 error,
762 compensation,
763 } => {
764 s.status = WorkflowStatus::Failed;
765 s.error = Some(error.clone());
766 s.compensation = Some(*compensation);
767 s.finished_at_ms = Some(ts);
768 }
769 Event::WorkflowCancelled { compensation } => {
770 s.status = WorkflowStatus::Cancelled;
771 s.compensation = Some(*compensation);
772 s.finished_at_ms = Some(ts);
773 }
774 Event::WorkflowTimedOut { compensation } => {
775 s.status = WorkflowStatus::TimedOut;
776 s.compensation = Some(*compensation);
777 s.finished_at_ms = Some(ts);
778 }
779 Event::LimitExceeded { message, .. } => {
780 if s.abort.is_none() {
781 s.abort = Some(AbortReason::Failed {
782 error: TaskError::new(codes::LIMIT_EXCEEDED, message.clone(), false),
783 });
784 }
785 }
786 Event::HandlersStarted { kind, compensation } => {
787 s.handlers = Some(HandlerPhase {
788 kind: kind.clone(),
789 compensation: *compensation,
790 });
791 }
792 Event::SignalReceived { name, payload } => s.signals.push(ReceivedSignal {
793 seq: h.seq,
794 name: name.clone(),
795 payload: payload.clone(),
796 received_at_ms: ts,
797 delivered_to: None,
798 }),
799 Event::SignalAwaitStarted {
800 step, deadline_ms, ..
801 } => {
802 let u = s.units.entry(step.clone()).or_default();
803 u.status = Some(UnitStatus::Waiting);
804 u.wake_at_ms = *deadline_ms;
805 u.scheduled_at_ms = Some(ts);
806 u.attempt = 1;
807 }
808 Event::WorkflowContinuedAsNew { .. } => {
809 s.status = WorkflowStatus::ContinuedAsNew;
810 s.finished_at_ms = Some(ts);
811 }
812 Event::SignalDelivered { step, signal_seq } => {
813 let payload = match s.signals.iter_mut().find(|r| r.seq == *signal_seq) {
814 Some(r) => {
815 r.delivered_to = Some(step.clone());
816 r.payload.clone()
817 }
818 None => Value::Null,
819 };
820 let u = s.units.entry(step.clone()).or_default();
821 u.status = Some(UnitStatus::Succeeded);
822 u.output = Some(payload);
823 u.finished_at_ms = Some(ts);
824 u.succeeded_seq = Some(h.seq);
825 u.wake_at_ms = None;
826 }
827 }
828}