Skip to main content

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}