forge_core/workflow/
suspend.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub enum SuspendReason {
8 Sleep { wake_at: DateTime<Utc> },
10 WaitingEvent {
12 event_name: String,
13 timeout: Option<DateTime<Utc>>,
14 },
15}
16
17impl SuspendReason {
18 pub fn wake_at(&self) -> Option<DateTime<Utc>> {
20 match self {
21 Self::Sleep { wake_at } => Some(*wake_at),
22 Self::WaitingEvent { timeout, .. } => *timeout,
23 }
24 }
25
26 pub fn is_sleep(&self) -> bool {
28 matches!(self, Self::Sleep { .. })
29 }
30
31 pub fn is_event_wait(&self) -> bool {
33 matches!(self, Self::WaitingEvent { .. })
34 }
35
36 pub fn event_name(&self) -> Option<&str> {
38 match self {
39 Self::WaitingEvent { event_name, .. } => Some(event_name),
40 _ => None,
41 }
42 }
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct WorkflowEvent {
48 pub id: Uuid,
50 pub event_name: String,
52 pub correlation_id: String,
54 pub payload: Option<serde_json::Value>,
56 pub created_at: DateTime<Utc>,
58}
59
60impl WorkflowEvent {
61 pub fn new(
63 event_name: impl Into<String>,
64 correlation_id: impl Into<String>,
65 payload: Option<serde_json::Value>,
66 ) -> Self {
67 Self {
68 id: Uuid::new_v4(),
69 event_name: event_name.into(),
70 correlation_id: correlation_id.into(),
71 payload,
72 created_at: Utc::now(),
73 }
74 }
75
76 pub fn payload_as<T: serde::de::DeserializeOwned>(&self) -> Option<T> {
78 self.payload
79 .as_ref()
80 .and_then(|p| serde_json::from_value(p.clone()).ok())
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn test_suspend_reason_sleep() {
90 let wake_at = Utc::now() + chrono::Duration::hours(1);
91 let reason = SuspendReason::Sleep { wake_at };
92
93 assert!(reason.is_sleep());
94 assert!(!reason.is_event_wait());
95 assert_eq!(reason.wake_at(), Some(wake_at));
96 assert!(reason.event_name().is_none());
97 }
98
99 #[test]
100 fn test_suspend_reason_event() {
101 let timeout = Utc::now() + chrono::Duration::days(7);
102 let reason = SuspendReason::WaitingEvent {
103 event_name: "payment_confirmed".to_string(),
104 timeout: Some(timeout),
105 };
106
107 assert!(!reason.is_sleep());
108 assert!(reason.is_event_wait());
109 assert_eq!(reason.wake_at(), Some(timeout));
110 assert_eq!(reason.event_name(), Some("payment_confirmed"));
111 }
112
113 #[test]
114 fn test_workflow_event_creation() {
115 let event = WorkflowEvent::new(
116 "order_completed",
117 "workflow-123",
118 Some(serde_json::json!({"order_id": "ABC123"})),
119 );
120
121 assert_eq!(event.event_name, "order_completed");
122 assert_eq!(event.correlation_id, "workflow-123");
123 assert!(event.payload.is_some());
124 }
125
126 #[test]
127 fn test_workflow_event_payload_typed() {
128 #[derive(Debug, Deserialize, PartialEq)]
129 struct OrderData {
130 order_id: String,
131 }
132
133 let event = WorkflowEvent::new(
134 "order_completed",
135 "workflow-123",
136 Some(serde_json::json!({"order_id": "ABC123"})),
137 );
138
139 let data: Option<OrderData> = event.payload_as();
140 assert!(data.is_some());
141 assert_eq!(
142 data.unwrap(),
143 OrderData {
144 order_id: "ABC123".to_string()
145 }
146 );
147 }
148}