ironflow_store/entities/log_entry.rs
1//! Log entry entities for persisted run/step output.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use super::LogStream;
8
9/// A persisted log line from step execution.
10///
11/// Each entry records a single line of output alongside its metadata.
12/// Entries are ordered by [`id`](LogEntry::id) (UUID v7, time-ordered).
13///
14/// # Examples
15///
16/// ```
17/// use ironflow_store::entities::{LogEntry, LogStream};
18/// use uuid::Uuid;
19/// use chrono::Utc;
20///
21/// let entry = LogEntry {
22/// id: Uuid::now_v7(),
23/// run_id: Uuid::now_v7(),
24/// step_id: Uuid::now_v7(),
25/// step_name: "build".to_string(),
26/// stream: LogStream::Stdout,
27/// line: "Compiling ironflow v0.1.0".to_string(),
28/// created_at: Utc::now(),
29/// };
30/// assert_eq!(entry.stream, LogStream::Stdout);
31/// ```
32#[derive(Debug, Clone, Serialize, Deserialize)]
33#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
34pub struct LogEntry {
35 /// Unique entry ID (UUID v7, time-ordered).
36 pub id: Uuid,
37 /// Run that produced this log line.
38 pub run_id: Uuid,
39 /// Step that produced this log line.
40 pub step_id: Uuid,
41 /// Human-readable step name.
42 pub step_name: String,
43 /// Output stream.
44 pub stream: LogStream,
45 /// The log line content.
46 pub line: String,
47 /// When the line was recorded.
48 pub created_at: DateTime<Utc>,
49}
50
51/// Parameters for appending a batch of log lines.
52///
53/// All lines in a batch share the same run, step, and stream.
54///
55/// # Examples
56///
57/// ```
58/// use ironflow_store::entities::{NewLogEntries, LogStream};
59/// use uuid::Uuid;
60///
61/// let entries = NewLogEntries {
62/// run_id: Uuid::now_v7(),
63/// step_id: Uuid::now_v7(),
64/// step_name: "build".to_string(),
65/// stream: LogStream::Stdout,
66/// lines: vec!["line 1".to_string(), "line 2".to_string()],
67/// };
68/// assert_eq!(entries.lines.len(), 2);
69/// ```
70#[derive(Debug, Clone)]
71pub struct NewLogEntries {
72 /// Run that produced these log lines.
73 pub run_id: Uuid,
74 /// Step that produced these log lines.
75 pub step_id: Uuid,
76 /// Human-readable step name.
77 pub step_name: String,
78 /// Output stream.
79 pub stream: LogStream,
80 /// The log lines to persist.
81 pub lines: Vec<String>,
82}
83
84/// Filter criteria for listing log entries.
85///
86/// All fields are optional. When `None`, no filtering is applied
87/// for that dimension.
88///
89/// # Examples
90///
91/// ```
92/// use ironflow_store::entities::{LogFilter, LogStream};
93/// use uuid::Uuid;
94///
95/// let filter = LogFilter {
96/// step_id: Some(Uuid::now_v7()),
97/// stream: Some(LogStream::Stderr),
98/// };
99/// ```
100#[derive(Debug, Clone, Default)]
101pub struct LogFilter {
102 /// Filter by step ID.
103 pub step_id: Option<Uuid>,
104 /// Filter by output stream.
105 pub stream: Option<LogStream>,
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn log_entry_serde_roundtrip() {
114 let entry = LogEntry {
115 id: Uuid::now_v7(),
116 run_id: Uuid::now_v7(),
117 step_id: Uuid::now_v7(),
118 step_name: "build".to_string(),
119 stream: LogStream::Stdout,
120 line: "Compiling ironflow v0.1.0".to_string(),
121 created_at: Utc::now(),
122 };
123
124 let json = serde_json::to_string(&entry).unwrap();
125 let back: LogEntry = serde_json::from_str(&json).unwrap();
126
127 assert_eq!(back.id, entry.id);
128 assert_eq!(back.stream, LogStream::Stdout);
129 assert_eq!(back.line, "Compiling ironflow v0.1.0");
130 }
131
132 #[test]
133 fn log_filter_default_is_empty() {
134 let filter = LogFilter::default();
135 assert!(filter.step_id.is_none());
136 assert!(filter.stream.is_none());
137 }
138
139 #[test]
140 fn new_log_entries_creation() {
141 let entries = NewLogEntries {
142 run_id: Uuid::now_v7(),
143 step_id: Uuid::now_v7(),
144 step_name: "deploy".to_string(),
145 stream: LogStream::Stderr,
146 lines: vec!["error!".to_string()],
147 };
148
149 assert_eq!(entries.stream, LogStream::Stderr);
150 assert_eq!(entries.lines.len(), 1);
151 }
152}