1use std::sync::Arc;
8
9use anyhow::Result;
10use serde::{Deserialize, Serialize};
11
12use assay_domain::events::{
13 CursorGoneError, EngineEventBus, Event, EventFilter, NewEvent, Subsystem,
14};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
21#[serde(tag = "kind", rename_all = "snake_case")]
22pub enum WorkflowBusEvent {
23 WorkflowCreated {
24 workflow_id: String,
25 workflow_type: String,
26 task_queue: String,
27 status: String,
28 },
29 WorkflowStatusChanged {
30 workflow_id: String,
31 old_status: String,
32 new_status: String,
33 task_queue: String,
34 },
35 WorkflowNeedsDispatch {
36 workflow_id: String,
37 task_queue: String,
38 },
39 WorkflowStarted {
40 workflow_id: String,
41 },
42 WorkflowRunning {
43 workflow_id: String,
44 },
45 WorkflowCompleted {
46 workflow_id: String,
47 },
48 WorkflowFailed {
49 workflow_id: String,
50 },
51 WorkflowCancelled {
52 workflow_id: String,
53 },
54 WorkflowTerminated {
55 workflow_id: String,
56 },
57 ActivityInserted {
58 activity_id: i64,
59 workflow_id: String,
60 task_queue: String,
61 name: String,
62 },
63 ActivityStatusChanged {
64 activity_id: i64,
65 workflow_id: String,
66 old_status: String,
67 new_status: String,
68 },
69 SignalReceived {
70 workflow_id: String,
71 signal_name: String,
72 },
73 TimerFired {
74 workflow_id: String,
75 seq: i32,
76 },
77}
78
79impl WorkflowBusEvent {
80 pub fn kind(&self) -> &'static str {
83 match self {
84 WorkflowBusEvent::WorkflowCreated { .. } => "workflow_created",
85 WorkflowBusEvent::WorkflowStatusChanged { .. } => "workflow_status_changed",
86 WorkflowBusEvent::WorkflowNeedsDispatch { .. } => "workflow_needs_dispatch",
87 WorkflowBusEvent::WorkflowStarted { .. } => "workflow_started",
88 WorkflowBusEvent::WorkflowRunning { .. } => "workflow_running",
89 WorkflowBusEvent::WorkflowCompleted { .. } => "workflow_completed",
90 WorkflowBusEvent::WorkflowFailed { .. } => "workflow_failed",
91 WorkflowBusEvent::WorkflowCancelled { .. } => "workflow_cancelled",
92 WorkflowBusEvent::WorkflowTerminated { .. } => "workflow_terminated",
93 WorkflowBusEvent::ActivityInserted { .. } => "activity_inserted",
94 WorkflowBusEvent::ActivityStatusChanged { .. } => "activity_status_changed",
95 WorkflowBusEvent::SignalReceived { .. } => "signal_received",
96 WorkflowBusEvent::TimerFired { .. } => "timer_fired",
97 }
98 }
99
100 pub fn payload(&self) -> serde_json::Value {
104 let v = serde_json::to_value(self).expect("WorkflowBusEvent serialisable");
105 match v {
106 serde_json::Value::Object(mut m) => {
107 m.remove("kind");
108 serde_json::Value::Object(m)
109 }
110 other => other,
111 }
112 }
113}
114
115#[derive(Clone)]
119pub struct WorkflowEventBus {
120 inner: Arc<dyn EngineEventBus>,
121}
122
123impl WorkflowEventBus {
124 pub fn new(inner: Arc<dyn EngineEventBus>) -> Self {
125 Self { inner }
126 }
127
128 pub async fn publish(&self, namespace: &str, ev: WorkflowBusEvent) -> Result<i64> {
131 let kind = ev.kind();
132 let payload = ev.payload();
133 self.inner
134 .publish_committed(NewEvent {
135 namespace,
136 subsystem: Subsystem::Workflow,
137 kind,
138 payload,
139 })
140 .await
141 }
142
143 pub(crate) async fn publish_retry_requested(
144 &self,
145 namespace: &str,
146 workflow_id: &str,
147 activity_id: i64,
148 activity_seq: i32,
149 ) -> Result<i64> {
150 self.inner
151 .publish_committed(NewEvent {
152 namespace,
153 subsystem: Subsystem::Workflow,
154 kind: "workflow_retry_requested",
155 payload: serde_json::json!({
156 "workflow_id": workflow_id,
157 "activity_id": activity_id,
158 "activity_seq": activity_seq,
159 }),
160 })
161 .await
162 }
163
164 pub async fn read_since(
167 &self,
168 namespace: &str,
169 after: Option<i64>,
170 filter: &EventFilter,
171 limit: u32,
172 ) -> std::result::Result<Vec<Event>, CursorGoneError> {
173 self.inner.read_since(namespace, after, filter, limit).await
174 }
175
176 pub fn inner(&self) -> Arc<dyn EngineEventBus> {
179 Arc::clone(&self.inner)
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn kind_tag_stripped_from_payload() {
189 let e = WorkflowBusEvent::WorkflowCreated {
190 workflow_id: "wf-1".into(),
191 workflow_type: "greet".into(),
192 task_queue: "main".into(),
193 status: "PENDING".into(),
194 };
195 assert_eq!(e.kind(), "workflow_created");
196 let payload = e.payload();
197 assert_eq!(payload["workflow_id"], "wf-1");
198 assert_eq!(payload["workflow_type"], "greet");
199 assert!(payload.get("kind").is_none(), "kind tag must be stripped");
200 }
201
202 #[test]
203 fn activity_inserted_payload() {
204 let e = WorkflowBusEvent::ActivityInserted {
205 activity_id: 42,
206 workflow_id: "wf-1".into(),
207 task_queue: "main".into(),
208 name: "send_email".into(),
209 };
210 let p = e.payload();
211 assert_eq!(p["activity_id"], 42);
212 assert_eq!(p["workflow_id"], "wf-1");
213 }
214}