1use crate::types::{ExecutionResult, ExecutionStatus};
7use async_trait::async_trait;
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(tag = "event_type", rename_all = "snake_case")]
15pub enum ExecutionEvent {
16 Started {
18 execution_id: Uuid,
19 command: String,
20 timestamp: DateTime<Utc>,
21 },
22
23 Stdout {
25 execution_id: Uuid,
26 line: String,
27 timestamp: DateTime<Utc>,
28 },
29
30 Stderr {
32 execution_id: Uuid,
33 line: String,
34 timestamp: DateTime<Utc>,
35 },
36
37 Completed {
39 execution_id: Uuid,
40 result: ExecutionResult,
41 timestamp: DateTime<Utc>,
42 },
43
44 Failed {
46 execution_id: Uuid,
47 error: String,
48 timestamp: DateTime<Utc>,
49 },
50
51 Cancelled {
53 execution_id: Uuid,
54 timestamp: DateTime<Utc>,
55 },
56
57 Timeout {
59 execution_id: Uuid,
60 timeout_ms: u64,
61 timestamp: DateTime<Utc>,
62 },
63
64 Progress {
66 plan_id: Uuid,
67 completed: usize,
68 total: usize,
69 current_command: Option<String>,
70 timestamp: DateTime<Utc>,
71 },
72
73 StatusChanged {
75 execution_id: Uuid,
76 old_status: ExecutionStatus,
77 new_status: ExecutionStatus,
78 timestamp: DateTime<Utc>,
79 },
80}
81
82impl ExecutionEvent {
83 #[must_use]
85 pub fn execution_id(&self) -> Option<Uuid> {
86 match self {
87 ExecutionEvent::Started { execution_id, .. }
88 | ExecutionEvent::Stdout { execution_id, .. }
89 | ExecutionEvent::Stderr { execution_id, .. }
90 | ExecutionEvent::Completed { execution_id, .. }
91 | ExecutionEvent::Failed { execution_id, .. }
92 | ExecutionEvent::Cancelled { execution_id, .. }
93 | ExecutionEvent::Timeout { execution_id, .. }
94 | ExecutionEvent::StatusChanged { execution_id, .. } => Some(*execution_id),
95 ExecutionEvent::Progress { .. } => None,
96 }
97 }
98
99 #[must_use]
101 pub fn plan_id(&self) -> Option<Uuid> {
102 match self {
103 ExecutionEvent::Progress { plan_id, .. } => Some(*plan_id),
104 _ => None,
105 }
106 }
107
108 #[must_use]
110 pub fn timestamp(&self) -> DateTime<Utc> {
111 match self {
112 ExecutionEvent::Started { timestamp, .. }
113 | ExecutionEvent::Stdout { timestamp, .. }
114 | ExecutionEvent::Stderr { timestamp, .. }
115 | ExecutionEvent::Completed { timestamp, .. }
116 | ExecutionEvent::Failed { timestamp, .. }
117 | ExecutionEvent::Cancelled { timestamp, .. }
118 | ExecutionEvent::Timeout { timestamp, .. }
119 | ExecutionEvent::Progress { timestamp, .. }
120 | ExecutionEvent::StatusChanged { timestamp, .. } => *timestamp,
121 }
122 }
123
124 #[must_use]
126 pub fn is_terminal(&self) -> bool {
127 matches!(
128 self,
129 ExecutionEvent::Completed { .. }
130 | ExecutionEvent::Failed { .. }
131 | ExecutionEvent::Cancelled { .. }
132 | ExecutionEvent::Timeout { .. }
133 )
134 }
135
136 #[must_use]
138 pub fn event_type_name(&self) -> &str {
139 match self {
140 ExecutionEvent::Started { .. } => "started",
141 ExecutionEvent::Stdout { .. } => "stdout",
142 ExecutionEvent::Stderr { .. } => "stderr",
143 ExecutionEvent::Completed { .. } => "completed",
144 ExecutionEvent::Failed { .. } => "failed",
145 ExecutionEvent::Cancelled { .. } => "cancelled",
146 ExecutionEvent::Timeout { .. } => "timeout",
147 ExecutionEvent::Progress { .. } => "progress",
148 ExecutionEvent::StatusChanged { .. } => "status_changed",
149 }
150 }
151}
152
153#[async_trait]
157pub trait EventHandler: Send + Sync {
158 async fn handle_event(&self, event: ExecutionEvent);
160
161 async fn handle_error(&self, error: String) {
163 eprintln!("Event handler error: {error}");
164 }
165}
166
167pub struct NoopEventHandler;
169
170#[async_trait]
171impl EventHandler for NoopEventHandler {
172 async fn handle_event(&self, _event: ExecutionEvent) {
173 }
175}
176
177pub struct LoggingEventHandler {
179 pub verbose: bool,
180}
181
182impl LoggingEventHandler {
183 #[must_use]
184 pub fn new(verbose: bool) -> Self {
185 Self { verbose }
186 }
187}
188
189#[async_trait]
190impl EventHandler for LoggingEventHandler {
191 async fn handle_event(&self, event: ExecutionEvent) {
192 if self.verbose {
193 eprintln!(
194 "[{}] Event: {} - {:?}",
195 event.timestamp().format("%Y-%m-%d %H:%M:%S"),
196 event.event_type_name(),
197 event
198 );
199 } else {
200 match event {
201 ExecutionEvent::Started { command, .. } => {
202 eprintln!("Started: {command}");
203 }
204 ExecutionEvent::Completed { execution_id, .. } => {
205 eprintln!("Completed: {execution_id}");
206 }
207 ExecutionEvent::Failed { error, .. } => {
208 eprintln!("Failed: {error}");
209 }
210 _ => {}
211 }
212 }
213 }
214}
215
216pub struct MultiEventHandler {
218 handlers: Vec<Box<dyn EventHandler>>,
219}
220
221impl MultiEventHandler {
222 #[must_use]
223 pub fn new() -> Self {
224 Self {
225 handlers: Vec::new(),
226 }
227 }
228
229 pub fn add_handler(&mut self, handler: Box<dyn EventHandler>) {
230 self.handlers.push(handler);
231 }
232}
233
234impl Default for MultiEventHandler {
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240#[async_trait]
241impl EventHandler for MultiEventHandler {
242 async fn handle_event(&self, event: ExecutionEvent) {
243 for handler in &self.handlers {
244 handler.handle_event(event.clone()).await;
245 }
246 }
247
248 async fn handle_error(&self, error: String) {
249 for handler in &self.handlers {
250 handler.handle_error(error.clone()).await;
251 }
252 }
253}
254
255#[cfg(test)]
260mod tests {
261 use super::*;
262 use crate::types::ExecutionResult;
263 use std::time::Duration;
264
265 #[test]
266 fn test_execution_event_execution_id() {
267 let id = Uuid::new_v4();
268 let event = ExecutionEvent::Started {
269 execution_id: id,
270 command: "test".to_string(),
271 timestamp: Utc::now(),
272 };
273 assert_eq!(event.execution_id(), Some(id));
274
275 let plan_id = Uuid::new_v4();
276 let event = ExecutionEvent::Progress {
277 plan_id,
278 completed: 1,
279 total: 5,
280 current_command: None,
281 timestamp: Utc::now(),
282 };
283 assert_eq!(event.execution_id(), None);
284 assert_eq!(event.plan_id(), Some(plan_id));
285 }
286
287 #[test]
288 fn test_execution_event_is_terminal() {
289 let id = Uuid::new_v4();
290
291 let event = ExecutionEvent::Started {
292 execution_id: id,
293 command: "test".to_string(),
294 timestamp: Utc::now(),
295 };
296 assert!(!event.is_terminal());
297
298 let event = ExecutionEvent::Stdout {
299 execution_id: id,
300 line: "output".to_string(),
301 timestamp: Utc::now(),
302 };
303 assert!(!event.is_terminal());
304
305 let result = ExecutionResult {
306 id,
307 status: ExecutionStatus::Completed,
308 success: true,
309 exit_code: 0,
310 stdout: "".to_string(),
311 stderr: "".to_string(),
312 duration: Duration::from_secs(1),
313 started_at: Utc::now(),
314 completed_at: Some(Utc::now()),
315 error: None,
316 };
317
318 let event = ExecutionEvent::Completed {
319 execution_id: id,
320 result,
321 timestamp: Utc::now(),
322 };
323 assert!(event.is_terminal());
324
325 let event = ExecutionEvent::Failed {
326 execution_id: id,
327 error: "error".to_string(),
328 timestamp: Utc::now(),
329 };
330 assert!(event.is_terminal());
331
332 let event = ExecutionEvent::Cancelled {
333 execution_id: id,
334 timestamp: Utc::now(),
335 };
336 assert!(event.is_terminal());
337
338 let event = ExecutionEvent::Timeout {
339 execution_id: id,
340 timeout_ms: 5000,
341 timestamp: Utc::now(),
342 };
343 assert!(event.is_terminal());
344 }
345
346 #[test]
347 fn test_event_type_name() {
348 let id = Uuid::new_v4();
349
350 let event = ExecutionEvent::Started {
351 execution_id: id,
352 command: "test".to_string(),
353 timestamp: Utc::now(),
354 };
355 assert_eq!(event.event_type_name(), "started");
356
357 let event = ExecutionEvent::Stdout {
358 execution_id: id,
359 line: "output".to_string(),
360 timestamp: Utc::now(),
361 };
362 assert_eq!(event.event_type_name(), "stdout");
363
364 let event = ExecutionEvent::StatusChanged {
365 execution_id: id,
366 old_status: ExecutionStatus::Pending,
367 new_status: ExecutionStatus::Running,
368 timestamp: Utc::now(),
369 };
370 assert_eq!(event.event_type_name(), "status_changed");
371 }
372
373 #[tokio::test]
374 async fn test_noop_event_handler() {
375 let handler = NoopEventHandler;
376 let event = ExecutionEvent::Started {
377 execution_id: Uuid::new_v4(),
378 command: "test".to_string(),
379 timestamp: Utc::now(),
380 };
381 handler.handle_event(event).await;
382 }
384
385 #[tokio::test]
386 async fn test_logging_event_handler() {
387 let handler = LoggingEventHandler::new(false);
388 let event = ExecutionEvent::Started {
389 execution_id: Uuid::new_v4(),
390 command: "test".to_string(),
391 timestamp: Utc::now(),
392 };
393 handler.handle_event(event).await;
394 }
396
397 #[tokio::test]
398 async fn test_multi_event_handler() {
399 let mut multi = MultiEventHandler::new();
400 multi.add_handler(Box::new(NoopEventHandler));
401 multi.add_handler(Box::new(LoggingEventHandler::new(false)));
402
403 let event = ExecutionEvent::Started {
404 execution_id: Uuid::new_v4(),
405 command: "test".to_string(),
406 timestamp: Utc::now(),
407 };
408 multi.handle_event(event).await;
409 }
411
412 #[test]
413 fn test_event_serialization() {
414 let event = ExecutionEvent::Started {
415 execution_id: Uuid::new_v4(),
416 command: "echo hello".to_string(),
417 timestamp: Utc::now(),
418 };
419
420 let json = serde_json::to_string(&event).unwrap();
421 assert!(json.contains("started"));
422 assert!(json.contains("echo hello"));
423
424 let deserialized: ExecutionEvent = serde_json::from_str(&json).unwrap();
425 match deserialized {
426 ExecutionEvent::Started { command, .. } => {
427 assert_eq!(command, "echo hello");
428 }
429 _ => panic!("Wrong event type"),
430 }
431 }
432}