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 },
105
106 #[serde(rename_all = "camelCase")]
108 PushingResource {
109 resource_name: String,
111 resource_type: String,
113 },
114
115 #[serde(rename_all = "camelCase")]
117 CreatingRelease {
118 project: String,
120 },
121
122 #[serde(rename_all = "camelCase")]
124 CompilingCode {
125 language: String,
127 progress: Option<String>,
129 },
130
131 #[serde(rename_all = "camelCase")]
135 StackStep {
136 next_state: StackState,
138 suggested_delay_ms: Option<u64>,
140 },
141
142 #[serde(rename_all = "camelCase")]
144 GeneratingCloudFormationTemplate,
145
146 #[serde(rename_all = "camelCase")]
148 GeneratingTemplate {
149 platform: String,
151 },
152
153 #[serde(rename_all = "camelCase")]
158 ProvisioningAgent {
159 agent_id: String,
161 release_id: String,
163 },
164
165 #[serde(rename_all = "camelCase")]
167 UpdatingAgent {
168 agent_id: String,
170 release_id: String,
172 },
173
174 #[serde(rename_all = "camelCase")]
176 DeletingAgent {
177 agent_id: String,
179 release_id: String,
181 },
182
183 #[serde(rename_all = "camelCase")]
185 DebuggingAgent {
186 agent_id: String,
188 debug_session_id: String,
190 },
191
192 #[serde(rename_all = "camelCase")]
197 PreparingEnvironment {
198 strategy_name: String,
200 },
201
202 #[serde(rename_all = "camelCase")]
204 DeployingStack {
205 stack_name: String,
207 },
208
209 #[serde(rename_all = "camelCase")]
211 RunningTestFunction {
212 stack_name: String,
214 },
215
216 #[serde(rename_all = "camelCase")]
218 CleaningUpStack {
219 stack_name: String,
221 strategy_name: String,
223 },
224
225 #[serde(rename_all = "camelCase")]
227 CleaningUpEnvironment {
228 stack_name: String,
230 strategy_name: String,
232 },
233
234 #[serde(rename_all = "camelCase")]
236 SettingUpPlatformContext {
237 platform_name: String,
239 },
240
241 #[serde(rename_all = "camelCase")]
246 EnsuringDockerRepository {
247 repository_name: String,
249 },
250
251 #[serde(rename_all = "camelCase")]
253 DeployingCloudFormationStack {
254 cfn_stack_name: String,
256 current_status: String,
258 },
259
260 #[serde(rename_all = "camelCase")]
262 AssumingRole {
263 role_arn: String,
265 },
266
267 #[serde(rename_all = "camelCase")]
269 ImportingStackStateFromCloudFormation {
270 cfn_stack_name: String,
272 },
273
274 #[serde(rename_all = "camelCase")]
276 DeletingCloudFormationStack {
277 cfn_stack_name: String,
279 current_status: String,
281 },
282
283 #[serde(rename_all = "camelCase")]
285 EmptyingBuckets {
286 bucket_names: Vec<String>,
288 },
289
290 #[cfg(test)]
294 #[serde(rename_all = "camelCase")]
295 TestBuildingStack { stack: String },
296
297 #[cfg(test)]
298 #[serde(rename_all = "camelCase")]
299 TestBuildingImage { image: String },
300
301 #[cfg(test)]
302 #[serde(rename_all = "camelCase")]
303 TestBuildImage { image: String, stage: String },
304
305 #[cfg(test)]
306 #[serde(rename_all = "camelCase")]
307 TestPushImage { image: String },
308
309 #[cfg(test)]
310 #[serde(rename_all = "camelCase")]
311 TestCreatingResource {
312 resource_type: String,
313 resource_name: String,
314 details: Option<String>,
315 },
316
317 #[cfg(test)]
318 #[serde(rename_all = "camelCase")]
319 TestDeployingStack { stack: String },
320
321 #[cfg(test)]
322 #[serde(rename_all = "camelCase")]
323 TestPerformingHealthCheck { target: String, check_type: String },
324}
325
326impl AlienEvent {
327 pub async fn emit(self) -> Result<EventHandle> {
329 EventBus::emit(self, None, EventState::None).await
330 }
331
332 pub async fn emit_with_state(self, state: EventState) -> Result<EventHandle> {
334 EventBus::emit(self, None, state).await
335 }
336
337 pub async fn emit_with_parent(self, parent_id: &str) -> Result<EventHandle> {
339 EventBus::emit(self, Some(parent_id.to_string()), EventState::None).await
340 }
341
342 pub async fn in_scope<F, Fut, T, E>(self, f: F) -> std::result::Result<T, AlienError<E>>
345 where
346 F: FnOnce(EventHandle) -> Fut,
347 Fut: std::future::Future<Output = std::result::Result<T, AlienError<E>>>,
348 E: AlienErrorData + Clone + std::fmt::Debug + Serialize + Send + Sync + 'static,
349 {
350 let handle = match EventBus::emit(self, None, EventState::Started).await {
351 Ok(handle) => handle,
352 Err(e) => {
353 eprintln!("Failed to emit event, continuing with no-op handle: {}", e);
357 EventHandle::noop()
358 }
359 };
360
361 let result = handle.as_parent(|_| f(handle.clone())).await;
363
364 match result {
365 Ok(result) => {
366 let _ = handle.complete().await; Ok(result)
368 }
369 Err(err) => {
370 let _ = handle.fail(err.clone()).await; Err(err)
374 }
375 }
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[test]
384 fn test_event_serialization() {
385 let event = AlienEvent::BuildingStack {
386 stack: "test-stack".to_string(),
387 };
388
389 let json = serde_json::to_string(&event).unwrap();
390 assert!(json.contains("\"type\":\"BuildingStack\""));
391 assert!(json.contains("\"stack\":\"test-stack\""));
392
393 let deserialized: AlienEvent = serde_json::from_str(&json).unwrap();
394 match deserialized {
395 AlienEvent::BuildingStack { stack } => assert_eq!(stack, "test-stack"),
396 _ => panic!("Wrong event type"),
397 }
398
399 let event_with_snake_case = AlienEvent::DownloadingAlienRuntime {
401 target_triple: "x86_64-unknown-linux-gnu".to_string(),
402 url: "https://example.com".to_string(),
403 };
404
405 let json = serde_json::to_string(&event_with_snake_case).unwrap();
406 assert!(json.contains("\"type\":\"DownloadingAlienRuntime\""));
407 assert!(json.contains("\"targetTriple\":\"x86_64-unknown-linux-gnu\""));
408 assert!(json.contains("\"url\":\"https://example.com\""));
409
410 let deserialized: AlienEvent = serde_json::from_str(&json).unwrap();
411 match deserialized {
412 AlienEvent::DownloadingAlienRuntime { target_triple, url } => {
413 assert_eq!(target_triple, "x86_64-unknown-linux-gnu");
414 assert_eq!(url, "https://example.com");
415 }
416 _ => panic!("Wrong event type"),
417 }
418 }
419}