cuenv_events/
event.rs

1//! Event type definitions for structured cuenv events.
2//!
3//! This module defines the core event types that flow through the cuenv event system.
4//! Events are categorized by domain (Task, CI, Command, etc.) and include rich metadata.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10/// A structured cuenv event with full metadata.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct CuenvEvent {
13    /// Unique event identifier.
14    pub id: Uuid,
15    /// Correlation ID for request tracing across operations.
16    pub correlation_id: Uuid,
17    /// When the event occurred.
18    pub timestamp: DateTime<Utc>,
19    /// Source information for the event.
20    pub source: EventSource,
21    /// The event category and data.
22    pub category: EventCategory,
23}
24
25impl CuenvEvent {
26    /// Create a new event with the given category.
27    #[must_use]
28    pub fn new(correlation_id: Uuid, source: EventSource, category: EventCategory) -> Self {
29        Self {
30            id: Uuid::new_v4(),
31            correlation_id,
32            timestamp: Utc::now(),
33            source,
34            category,
35        }
36    }
37}
38
39/// Source information for an event.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct EventSource {
42    /// The tracing target (e.g., "`cuenv::task`", "`cuenv::ci`").
43    pub target: String,
44    /// Source file path, if available.
45    pub file: Option<String>,
46    /// Source line number, if available.
47    pub line: Option<u32>,
48}
49
50impl EventSource {
51    /// Create a new event source with just a target.
52    #[must_use]
53    pub fn new(target: impl Into<String>) -> Self {
54        Self {
55            target: target.into(),
56            file: None,
57            line: None,
58        }
59    }
60
61    /// Create a new event source with file and line information.
62    #[must_use]
63    pub fn with_location(target: impl Into<String>, file: impl Into<String>, line: u32) -> Self {
64        Self {
65            target: target.into(),
66            file: Some(file.into()),
67            line: Some(line),
68        }
69    }
70}
71
72/// Event categories organized by domain.
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(tag = "type", content = "data")]
75pub enum EventCategory {
76    /// Task execution lifecycle events.
77    Task(TaskEvent),
78    /// CI pipeline events.
79    Ci(CiEvent),
80    /// Command lifecycle events.
81    Command(CommandEvent),
82    /// User interaction events.
83    Interactive(InteractiveEvent),
84    /// System/supervisor events.
85    System(SystemEvent),
86    /// Generic output events (for migration and compatibility).
87    Output(OutputEvent),
88}
89
90/// Task execution lifecycle events.
91#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(tag = "event", content = "data")]
93pub enum TaskEvent {
94    /// Task execution started.
95    Started {
96        /// Task name.
97        name: String,
98        /// Command being executed.
99        command: String,
100        /// Whether this is a hermetic execution.
101        hermetic: bool,
102    },
103    /// Task cache hit - using cached result.
104    CacheHit {
105        /// Task name.
106        name: String,
107        /// Cache key that matched.
108        cache_key: String,
109    },
110    /// Task cache miss - will execute.
111    CacheMiss {
112        /// Task name.
113        name: String,
114    },
115    /// Task produced output.
116    Output {
117        /// Task name.
118        name: String,
119        /// Output stream.
120        stream: Stream,
121        /// Output content.
122        content: String,
123    },
124    /// Task execution completed.
125    Completed {
126        /// Task name.
127        name: String,
128        /// Whether the task succeeded.
129        success: bool,
130        /// Exit code, if available.
131        exit_code: Option<i32>,
132        /// Duration in milliseconds.
133        duration_ms: u64,
134    },
135    /// Task group execution started.
136    GroupStarted {
137        /// Group name/prefix.
138        name: String,
139        /// Whether tasks run sequentially.
140        sequential: bool,
141        /// Number of tasks in the group.
142        task_count: usize,
143    },
144    /// Task group execution completed.
145    GroupCompleted {
146        /// Group name/prefix.
147        name: String,
148        /// Whether all tasks succeeded.
149        success: bool,
150        /// Duration in milliseconds.
151        duration_ms: u64,
152    },
153}
154
155/// CI pipeline events.
156#[derive(Debug, Clone, Serialize, Deserialize)]
157#[serde(tag = "event", content = "data")]
158pub enum CiEvent {
159    /// CI context detected.
160    ContextDetected {
161        /// CI provider name.
162        provider: String,
163        /// Event type (push, `pull_request`, etc.).
164        event_type: String,
165        /// Git ref name.
166        ref_name: String,
167    },
168    /// Changed files found.
169    ChangedFilesFound {
170        /// Number of changed files.
171        count: usize,
172    },
173    /// Projects discovered.
174    ProjectsDiscovered {
175        /// Number of projects found.
176        count: usize,
177    },
178    /// Project skipped (no affected tasks).
179    ProjectSkipped {
180        /// Project path.
181        path: String,
182        /// Reason for skipping.
183        reason: String,
184    },
185    /// Task executing within CI.
186    TaskExecuting {
187        /// Project path.
188        project: String,
189        /// Task name.
190        task: String,
191    },
192    /// Task result within CI.
193    TaskResult {
194        /// Project path.
195        project: String,
196        /// Task name.
197        task: String,
198        /// Whether the task succeeded.
199        success: bool,
200        /// Error message, if failed.
201        error: Option<String>,
202    },
203    /// CI report generated.
204    ReportGenerated {
205        /// Report file path.
206        path: String,
207    },
208}
209
210/// Command lifecycle events.
211#[derive(Debug, Clone, Serialize, Deserialize)]
212#[serde(tag = "event", content = "data")]
213pub enum CommandEvent {
214    /// Command started.
215    Started {
216        /// Command name.
217        command: String,
218        /// Command arguments.
219        args: Vec<String>,
220    },
221    /// Command progress update.
222    Progress {
223        /// Command name.
224        command: String,
225        /// Progress percentage (0.0 to 1.0).
226        progress: f32,
227        /// Progress message.
228        message: String,
229    },
230    /// Command completed.
231    Completed {
232        /// Command name.
233        command: String,
234        /// Whether the command succeeded.
235        success: bool,
236        /// Duration in milliseconds.
237        duration_ms: u64,
238    },
239}
240
241/// User interaction events.
242#[derive(Debug, Clone, Serialize, Deserialize)]
243#[serde(tag = "event", content = "data")]
244pub enum InteractiveEvent {
245    /// Prompt requested from user.
246    PromptRequested {
247        /// Unique prompt identifier.
248        prompt_id: String,
249        /// The prompt message.
250        message: String,
251        /// Available options.
252        options: Vec<String>,
253    },
254    /// Prompt resolved with user response.
255    PromptResolved {
256        /// Prompt identifier.
257        prompt_id: String,
258        /// User's response.
259        response: String,
260    },
261    /// Wait/progress indicator.
262    WaitProgress {
263        /// What we're waiting for.
264        target: String,
265        /// Elapsed seconds.
266        elapsed_secs: u64,
267    },
268}
269
270/// System/supervisor events.
271#[derive(Debug, Clone, Serialize, Deserialize)]
272#[serde(tag = "event", content = "data")]
273pub enum SystemEvent {
274    /// Supervisor log message.
275    SupervisorLog {
276        /// Log tag/category.
277        tag: String,
278        /// Log message.
279        message: String,
280    },
281    /// System shutdown.
282    Shutdown,
283}
284
285/// Generic output events for migration and compatibility.
286#[derive(Debug, Clone, Serialize, Deserialize)]
287#[serde(tag = "event", content = "data")]
288pub enum OutputEvent {
289    /// Standard output.
290    Stdout {
291        /// Content to output.
292        content: String,
293    },
294    /// Standard error.
295    Stderr {
296        /// Content to output.
297        content: String,
298    },
299}
300
301/// Output stream identifier.
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
303pub enum Stream {
304    /// Standard output.
305    Stdout,
306    /// Standard error.
307    Stderr,
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn test_event_creation() {
316        let event = CuenvEvent::new(
317            Uuid::new_v4(),
318            EventSource::new("cuenv::test"),
319            EventCategory::Output(OutputEvent::Stdout {
320                content: "test".to_string(),
321            }),
322        );
323
324        assert!(!event.id.is_nil());
325        assert_eq!(event.source.target, "cuenv::test");
326    }
327
328    #[test]
329    fn test_event_serialization() {
330        let event = CuenvEvent::new(
331            Uuid::new_v4(),
332            EventSource::new("cuenv::task"),
333            EventCategory::Task(TaskEvent::Started {
334                name: "build".to_string(),
335                command: "cargo build".to_string(),
336                hermetic: true,
337            }),
338        );
339
340        let json = serde_json::to_string(&event).unwrap();
341        assert!(json.contains("cuenv::task"));
342        assert!(json.contains("build"));
343
344        let parsed: CuenvEvent = serde_json::from_str(&json).unwrap();
345        assert_eq!(parsed.id, event.id);
346    }
347
348    #[test]
349    fn test_event_source_with_location() {
350        let source = EventSource::with_location("cuenv::task", "src/main.rs", 42);
351        assert_eq!(source.target, "cuenv::task");
352        assert_eq!(source.file, Some("src/main.rs".to_string()));
353        assert_eq!(source.line, Some(42));
354    }
355}