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}