1use async_trait::async_trait;
2use serde::{
3 Deserialize,
4 Serialize,
5};
6
7use crate::domain::{
8 provider::ProviderSummary,
9 Pipeline,
10};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(rename_all = "kebab-case")]
14pub enum CacheInvalidationReason {
15 Fetch,
16 ProviderChange,
17 ManualRefresh,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21#[serde(tag = "type", content = "payload")]
22pub enum CoreEvent {
23 ProvidersChanged,
24
25 PipelinesFetched {
26 provider_id: i64,
27 },
28
29 PipelinesFetchError {
30 error: String,
31 },
32
33 PipelinesUpdated {
34 pipelines: Vec<Pipeline>,
35 provider_id: Option<i64>,
36 timestamp: i64,
37 },
38
39 PipelineStatusChanged {
40 pipelines: Vec<Pipeline>,
41 },
42
43 RunTriggered {
44 workflow_id: String,
45 },
46
47 RunCancelled {
48 pipeline_id: String,
49 },
50
51 RefreshError {
52 error: String,
53 },
54
55 MetricsGenerated {
56 pipeline_id: String,
57 },
58
59 MetricsGlobalConfigChanged,
60
61 MetricsConfigChanged {
62 pipeline_id: String,
63 },
64
65 MetricsFlushed {
66 pipeline_id: Option<String>,
67 },
68
69 ProviderStatusUpdated {
70 provider_id: i64,
71 },
72
73 MigrationProgress {
74 step: String,
75 step_index: usize,
76 total_steps: usize,
77 message: String,
78 },
79
80 MigrationComplete {
81 success: bool,
82 summary: String,
83 },
84
85 ProviderAdded {
86 provider: ProviderSummary,
87 timestamp: i64,
88 },
89
90 ProviderUpdated {
91 provider: ProviderSummary,
92 timestamp: i64,
93 },
94
95 ProviderRemoved {
96 provider: ProviderSummary,
97 timestamp: i64,
98 },
99
100 PipelineCacheInvalidated {
101 provider_id: Option<i64>,
102 reason: CacheInvalidationReason,
103 },
104
105 RunHistoryCacheInvalidated {
106 pipeline_id: Option<String>,
107 },
108
109 VaultUnlocked,
110}
111
112impl CoreEvent {
113 pub fn event_name(&self) -> &'static str {
114 match self {
115 CoreEvent::ProvidersChanged => "providers-changed",
116 CoreEvent::PipelinesFetched { .. } => "pipelines-fetched",
117 CoreEvent::PipelinesFetchError { .. } => "pipelines-fetch-error",
118 CoreEvent::PipelinesUpdated { .. } => "pipelines-updated",
119 CoreEvent::PipelineStatusChanged { .. } => "pipeline-status-changed",
120 CoreEvent::RunTriggered { .. } => "run-triggered",
121 CoreEvent::RunCancelled { .. } => "run-cancelled",
122 CoreEvent::RefreshError { .. } => "refresh-error",
123 CoreEvent::MetricsGenerated { .. } => "metrics-generated",
124 CoreEvent::MetricsGlobalConfigChanged => "metrics-global-config-changed",
125 CoreEvent::MetricsConfigChanged { .. } => "metrics-config-changed",
126 CoreEvent::MetricsFlushed { .. } => "metrics-flushed",
127 CoreEvent::ProviderStatusUpdated { .. } => "provider-status-updated",
128 CoreEvent::MigrationProgress { .. } => "migration-progress",
129 CoreEvent::MigrationComplete { .. } => "migration-complete",
130 CoreEvent::ProviderAdded { .. } => "provider-added",
131 CoreEvent::ProviderUpdated { .. } => "provider-updated",
132 CoreEvent::ProviderRemoved { .. } => "provider-removed",
133 CoreEvent::PipelineCacheInvalidated { .. } => "pipeline-cache-invalidated",
134 CoreEvent::RunHistoryCacheInvalidated { .. } => "run-history-cache-invalidated",
135 CoreEvent::VaultUnlocked => "vault-unlocked",
136 }
137 }
138
139 pub fn to_json_payload(&self) -> serde_json::Value {
140 match self {
141 CoreEvent::ProvidersChanged => serde_json::json!({}),
142 CoreEvent::PipelinesFetched { provider_id } => serde_json::json!(provider_id),
143 CoreEvent::PipelinesFetchError { error } => serde_json::json!(error),
144 CoreEvent::PipelinesUpdated {
145 pipelines,
146 provider_id,
147 timestamp,
148 } => {
149 let mut json = serde_json::json!({
150 "pipelines": pipelines,
151 "timestamp": timestamp,
152 });
153 if let Some(pid) = provider_id {
154 json["providerId"] = serde_json::json!(pid);
155 }
156 json
157 }
158 CoreEvent::PipelineStatusChanged { pipelines } => {
159 serde_json::to_value(pipelines).unwrap_or_default()
160 }
161 CoreEvent::RunTriggered { workflow_id } => serde_json::json!(workflow_id),
162 CoreEvent::RunCancelled { pipeline_id } => serde_json::json!(pipeline_id),
163 CoreEvent::RefreshError { error } => serde_json::json!(error),
164 CoreEvent::MetricsGenerated { pipeline_id } => serde_json::json!(pipeline_id),
165 CoreEvent::MetricsGlobalConfigChanged => serde_json::json!({}),
166 CoreEvent::MetricsConfigChanged { pipeline_id } => serde_json::json!(pipeline_id),
167 CoreEvent::MetricsFlushed { pipeline_id } => {
168 let mut json = serde_json::json!({});
169 if let Some(pid) = pipeline_id {
170 json["pipeline_id"] = serde_json::json!(pid);
171 }
172 json
173 }
174 CoreEvent::ProviderStatusUpdated { provider_id } => serde_json::json!(provider_id),
175 CoreEvent::MigrationProgress {
176 step,
177 step_index,
178 total_steps,
179 message,
180 } => serde_json::json!({
181 "step": step,
182 "step_index": step_index,
183 "total_steps": total_steps,
184 "message": message,
185 }),
186 CoreEvent::MigrationComplete { success, summary } => serde_json::json!({
187 "success": success,
188 "summary": summary,
189 }),
190 CoreEvent::ProviderAdded {
191 provider,
192 timestamp,
193 } => serde_json::json!({
194 "provider": provider,
195 "action": "added",
196 "timestamp": timestamp,
197 }),
198 CoreEvent::ProviderUpdated {
199 provider,
200 timestamp,
201 } => serde_json::json!({
202 "provider": provider,
203 "action": "updated",
204 "timestamp": timestamp,
205 }),
206 CoreEvent::ProviderRemoved {
207 provider,
208 timestamp,
209 } => serde_json::json!({
210 "provider": provider,
211 "action": "removed",
212 "timestamp": timestamp,
213 }),
214 CoreEvent::PipelineCacheInvalidated {
215 provider_id,
216 reason,
217 } => {
218 let mut json = serde_json::json!({
219 "reason": reason,
220 });
221 if let Some(pid) = provider_id {
222 json["providerId"] = serde_json::json!(pid);
223 }
224 json
225 }
226 CoreEvent::RunHistoryCacheInvalidated { pipeline_id } => {
227 let mut json = serde_json::json!({});
228 if let Some(pid) = pipeline_id {
229 json["pipeline_id"] = serde_json::json!(pid);
230 }
231 json
232 }
233 CoreEvent::VaultUnlocked => serde_json::json!({}),
234 }
235 }
236}
237
238#[async_trait]
239pub trait EventBus: Send + Sync {
240 async fn emit(&self, event: CoreEvent);
241
242 async fn emit_to(&self, target: &str, event: CoreEvent);
243}
244
245pub struct NoOpEventBus;
246
247#[async_trait]
248impl EventBus for NoOpEventBus {
249 async fn emit(&self, _event: CoreEvent) {}
250
251 async fn emit_to(&self, _target: &str, _event: CoreEvent) {}
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_event_names() {
260 assert_eq!(
261 CoreEvent::ProvidersChanged.event_name(),
262 "providers-changed"
263 );
264 assert_eq!(
265 CoreEvent::PipelinesFetched { provider_id: 1 }.event_name(),
266 "pipelines-fetched"
267 );
268 }
269
270 #[test]
271 fn test_event_serialization() {
272 let event = CoreEvent::PipelinesFetched { provider_id: 42 };
273 let json = serde_json::to_string(&event).unwrap();
274 assert!(json.contains("PipelinesFetched"));
275 assert!(json.contains("42"));
276 }
277}