1use crate::events::{EventBus, EventHandle, EventState};
2use crate::{Result, StackState};
3use alien_error::{AlienError, AlienErrorData};
4use serde::{Deserialize, Serialize};
5#[cfg(feature = "openapi")]
6use utoipa::ToSchema;
7
8#[derive(Serialize, Deserialize, Debug, Clone)]
10#[cfg_attr(feature = "openapi", derive(ToSchema))]
11#[serde(rename_all = "camelCase")]
12pub struct PushProgress {
13 pub operation: String,
15 pub layers_uploaded: usize,
17 pub total_layers: usize,
19 pub bytes_uploaded: u64,
21 pub total_bytes: u64,
23}
24
25#[derive(Serialize, Deserialize, Debug, Clone)]
27#[cfg_attr(feature = "openapi", derive(ToSchema))]
28#[serde(tag = "type")]
29pub enum AlienEvent {
30 #[serde(rename_all = "camelCase")]
35 LoadingConfiguration,
36
37 #[serde(rename_all = "camelCase")]
39 Finished,
40
41 #[serde(rename_all = "camelCase")]
46 BuildingStack {
47 stack: String,
49 },
50
51 #[serde(rename_all = "camelCase")]
53 RunningPreflights {
54 stack: String,
56 platform: String,
58 },
59
60 #[serde(rename_all = "camelCase")]
62 DownloadingAlienRuntime {
63 target_triple: String,
65 url: String,
67 },
68
69 #[serde(rename_all = "camelCase")]
71 BuildingResource {
72 resource_name: String,
74 resource_type: String,
76 #[serde(default, skip_serializing_if = "Vec::is_empty")]
78 related_resources: Vec<String>,
79 },
80
81 #[serde(rename_all = "camelCase")]
83 BuildingImage {
84 image: String,
86 },
87
88 #[serde(rename_all = "camelCase")]
90 PushingImage {
91 image: String,
93 progress: Option<PushProgress>,
95 },
96
97 #[serde(rename_all = "camelCase")]
99 PushingStack {
100 stack: String,
102 platform: String,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
106 destination: Option<String>,
107 },
108
109 #[serde(rename_all = "camelCase")]
111 PushingResource {
112 resource_name: String,
114 resource_type: String,
116 },
117
118 #[serde(rename_all = "camelCase")]
120 CreatingRelease {
121 project: String,
123 },
124
125 #[serde(rename_all = "camelCase")]
127 CompilingCode {
128 language: String,
130 progress: Option<String>,
132 },
133
134 #[serde(rename_all = "camelCase")]
138 StackStep {
139 next_state: StackState,
141 suggested_delay_ms: Option<u64>,
143 },
144
145 #[serde(rename_all = "camelCase")]
147 GeneratingCloudFormationTemplate,
148
149 #[serde(rename_all = "camelCase")]
151 GeneratingTemplate {
152 platform: String,
154 },
155
156 #[serde(rename_all = "camelCase")]
161 ProvisioningAgent {
162 agent_id: String,
164 release_id: String,
166 },
167
168 #[serde(rename_all = "camelCase")]
170 UpdatingAgent {
171 agent_id: String,
173 release_id: String,
175 },
176
177 #[serde(rename_all = "camelCase")]
179 DeletingAgent {
180 agent_id: String,
182 release_id: String,
184 },
185
186 #[serde(rename_all = "camelCase")]
188 DebuggingAgent {
189 agent_id: String,
191 debug_session_id: String,
193 },
194
195 #[serde(rename_all = "camelCase")]
200 PreparingEnvironment {
201 strategy_name: String,
203 },
204
205 #[serde(rename_all = "camelCase")]
207 DeployingStack {
208 stack_name: String,
210 },
211
212 #[serde(rename_all = "camelCase")]
214 RunningTestWorker {
215 stack_name: String,
217 },
218
219 #[serde(rename_all = "camelCase")]
221 CleaningUpStack {
222 stack_name: String,
224 strategy_name: String,
226 },
227
228 #[serde(rename_all = "camelCase")]
230 CleaningUpEnvironment {
231 stack_name: String,
233 strategy_name: String,
235 },
236
237 #[serde(rename_all = "camelCase")]
239 SettingUpPlatformContext {
240 platform_name: String,
242 },
243
244 #[serde(rename_all = "camelCase")]
249 EnsuringDockerRepository {
250 repository_name: String,
252 },
253
254 #[serde(rename_all = "camelCase")]
256 DeployingCloudFormationStack {
257 cfn_stack_name: String,
259 current_status: String,
261 },
262
263 #[serde(rename_all = "camelCase")]
265 AssumingRole {
266 role_arn: String,
268 },
269
270 #[serde(rename_all = "camelCase")]
272 ImportingStackStateFromCloudFormation {
273 cfn_stack_name: String,
275 },
276
277 #[serde(rename_all = "camelCase")]
279 DeletingCloudFormationStack {
280 cfn_stack_name: String,
282 current_status: String,
284 },
285
286 #[serde(rename_all = "camelCase")]
288 EmptyingBuckets {
289 bucket_names: Vec<String>,
291 },
292
293 #[cfg(test)]
297 #[serde(rename_all = "camelCase")]
298 TestBuildingStack { stack: String },
299
300 #[cfg(test)]
301 #[serde(rename_all = "camelCase")]
302 TestBuildingImage { image: String },
303
304 #[cfg(test)]
305 #[serde(rename_all = "camelCase")]
306 TestBuildImage { image: String, stage: String },
307
308 #[cfg(test)]
309 #[serde(rename_all = "camelCase")]
310 TestPushImage { image: String },
311
312 #[cfg(test)]
313 #[serde(rename_all = "camelCase")]
314 TestCreatingResource {
315 resource_type: String,
316 resource_name: String,
317 details: Option<String>,
318 },
319
320 #[cfg(test)]
321 #[serde(rename_all = "camelCase")]
322 TestDeployingStack { stack: String },
323
324 #[cfg(test)]
325 #[serde(rename_all = "camelCase")]
326 TestPerformingHealthCheck { target: String, check_type: String },
327}
328
329impl AlienEvent {
330 pub async fn emit(self) -> Result<EventHandle> {
332 EventBus::emit(self, None, EventState::None).await
333 }
334
335 pub async fn emit_with_state(self, state: EventState) -> Result<EventHandle> {
337 EventBus::emit(self, None, state).await
338 }
339
340 pub async fn emit_with_parent(self, parent_id: &str) -> Result<EventHandle> {
342 EventBus::emit(self, Some(parent_id.to_string()), EventState::None).await
343 }
344
345 pub async fn in_scope<F, Fut, T, E>(self, f: F) -> std::result::Result<T, AlienError<E>>
348 where
349 F: FnOnce(EventHandle) -> Fut,
350 Fut: std::future::Future<Output = std::result::Result<T, AlienError<E>>>,
351 E: AlienErrorData + Clone + std::fmt::Debug + Serialize + Send + Sync + 'static,
352 {
353 let handle = match EventBus::emit(self, None, EventState::Started).await {
354 Ok(handle) => handle,
355 Err(e) => {
356 eprintln!("Failed to emit event, continuing with no-op handle: {}", e);
360 EventHandle::noop()
361 }
362 };
363
364 let result = handle.as_parent(|_| f(handle.clone())).await;
366
367 match result {
368 Ok(result) => {
369 let _ = handle.complete().await; Ok(result)
371 }
372 Err(err) => {
373 let _ = handle.fail(err.clone()).await; Err(err)
377 }
378 }
379 }
380}
381
382#[cfg(test)]
383mod tests {
384 use super::*;
385
386 #[test]
387 fn test_event_serialization() {
388 let event = AlienEvent::BuildingStack {
389 stack: "test-stack".to_string(),
390 };
391
392 let json = serde_json::to_string(&event).unwrap();
393 assert!(json.contains("\"type\":\"BuildingStack\""));
394 assert!(json.contains("\"stack\":\"test-stack\""));
395
396 let deserialized: AlienEvent = serde_json::from_str(&json).unwrap();
397 match deserialized {
398 AlienEvent::BuildingStack { stack } => assert_eq!(stack, "test-stack"),
399 _ => panic!("Wrong event type"),
400 }
401
402 let event_with_snake_case = AlienEvent::DownloadingAlienRuntime {
404 target_triple: "x86_64-unknown-linux-gnu".to_string(),
405 url: "https://example.com".to_string(),
406 };
407
408 let json = serde_json::to_string(&event_with_snake_case).unwrap();
409 assert!(json.contains("\"type\":\"DownloadingAlienRuntime\""));
410 assert!(json.contains("\"targetTriple\":\"x86_64-unknown-linux-gnu\""));
411 assert!(json.contains("\"url\":\"https://example.com\""));
412
413 let deserialized: AlienEvent = serde_json::from_str(&json).unwrap();
414 match deserialized {
415 AlienEvent::DownloadingAlienRuntime { target_triple, url } => {
416 assert_eq!(target_triple, "x86_64-unknown-linux-gnu");
417 assert_eq!(url, "https://example.com");
418 }
419 _ => panic!("Wrong event type"),
420 }
421 }
422}