Skip to main content

atlas_local/models/
log_output.rs

1use bytes::Bytes;
2
3/// Represents a single log output entry from a container.
4///
5/// Container logs can come from different streams (stdout, stderr, stdin)
6/// and this enum represents which stream the log entry came from along
7/// with its content.
8///
9/// # Accessing Log Content
10///
11/// You can access the log content via pattern matching or helper methods:
12///
13/// ```rust
14/// use atlas_local::models::LogOutput;
15/// use bytes::Bytes;
16///
17/// let log = LogOutput::StdOut {
18///     message: Bytes::from("Hello, world!\n"),
19/// };
20///
21/// // Pattern matching
22/// match log {
23///     LogOutput::StdOut { message } => {
24///         println!("stdout: {:?}", message);
25///     }
26///     LogOutput::StdErr { message } => {
27///         println!("stderr: {:?}", message);
28///     }
29///     _ => {}
30/// }
31///
32/// // Or use helper methods
33/// let log = LogOutput::StdOut {
34///     message: Bytes::from("Hello, world!\n"),
35/// };
36/// println!("Message: {}", log.as_str_lossy());
37/// println!("Bytes: {:?}", log.as_bytes());
38/// ```
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum LogOutput {
41    /// Standard output log entry
42    StdOut {
43        /// The log message content
44        message: Bytes,
45    },
46    /// Standard error log entry
47    StdErr {
48        /// The log message content
49        message: Bytes,
50    },
51    /// Standard input log entry
52    StdIn {
53        /// The log message content
54        message: Bytes,
55    },
56    /// Console log entry
57    Console {
58        /// The log message content
59        message: Bytes,
60    },
61}
62
63impl LogOutput {
64    /// Returns the message content as a byte slice.
65    pub fn as_bytes(&self) -> &[u8] {
66        match self {
67            LogOutput::StdOut { message } => message.as_ref(),
68            LogOutput::StdErr { message } => message.as_ref(),
69            LogOutput::StdIn { message } => message.as_ref(),
70            LogOutput::Console { message } => message.as_ref(),
71        }
72    }
73
74    /// Returns the message content as a UTF-8 string, replacing invalid sequences.
75    pub fn as_str_lossy(&self) -> std::borrow::Cow<'_, str> {
76        String::from_utf8_lossy(self.as_bytes())
77    }
78
79    /// Returns true if this is a stdout log entry.
80    pub fn is_stdout(&self) -> bool {
81        matches!(self, LogOutput::StdOut { .. })
82    }
83
84    /// Returns true if this is a stderr log entry.
85    pub fn is_stderr(&self) -> bool {
86        matches!(self, LogOutput::StdErr { .. })
87    }
88
89    /// Returns true if this is a stdin log entry.
90    pub fn is_stdin(&self) -> bool {
91        matches!(self, LogOutput::StdIn { .. })
92    }
93
94    /// Returns true if this is a console log entry.
95    pub fn is_console(&self) -> bool {
96        matches!(self, LogOutput::Console { .. })
97    }
98}
99
100impl From<bollard::container::LogOutput> for LogOutput {
101    fn from(output: bollard::container::LogOutput) -> Self {
102        match output {
103            bollard::container::LogOutput::StdOut { message } => LogOutput::StdOut { message },
104            bollard::container::LogOutput::StdErr { message } => LogOutput::StdErr { message },
105            bollard::container::LogOutput::StdIn { message } => LogOutput::StdIn { message },
106            bollard::container::LogOutput::Console { message } => LogOutput::Console { message },
107        }
108    }
109}
110
111impl std::fmt::Display for LogOutput {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        write!(f, "{}", self.as_str_lossy())
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_log_output_stdout() {
123        let output = LogOutput::StdOut {
124            message: Bytes::from("test message\n"),
125        };
126
127        assert!(output.is_stdout());
128        assert!(!output.is_stderr());
129        assert!(!output.is_stdin());
130        assert!(!output.is_console());
131        assert_eq!(output.as_bytes(), b"test message\n");
132        assert_eq!(output.as_str_lossy(), "test message\n");
133    }
134
135    #[test]
136    fn test_log_output_stderr() {
137        let output = LogOutput::StdErr {
138            message: Bytes::from("error message\n"),
139        };
140
141        assert!(!output.is_stdout());
142        assert!(output.is_stderr());
143        assert!(!output.is_stdin());
144        assert!(!output.is_console());
145        assert_eq!(output.as_bytes(), b"error message\n");
146        assert_eq!(output.as_str_lossy(), "error message\n");
147    }
148
149    #[test]
150    fn test_log_output_stdin() {
151        let output = LogOutput::StdIn {
152            message: Bytes::from("input message\n"),
153        };
154
155        assert!(!output.is_stdout());
156        assert!(!output.is_stderr());
157        assert!(output.is_stdin());
158        assert!(!output.is_console());
159    }
160
161    #[test]
162    fn test_log_output_console() {
163        let output = LogOutput::Console {
164            message: Bytes::from("console message\n"),
165        };
166
167        assert!(!output.is_stdout());
168        assert!(!output.is_stderr());
169        assert!(!output.is_stdin());
170        assert!(output.is_console());
171    }
172
173    #[test]
174    fn test_log_output_display() {
175        let output = LogOutput::StdOut {
176            message: Bytes::from("display test\n"),
177        };
178
179        assert_eq!(format!("{}", output), "display test\n");
180    }
181
182    #[test]
183    fn test_log_output_from_bollard() {
184        let bollard_output = bollard::container::LogOutput::StdOut {
185            message: Bytes::from("test\n"),
186        };
187
188        let output: LogOutput = bollard_output.into();
189        assert!(output.is_stdout());
190        assert_eq!(output.as_str_lossy(), "test\n");
191    }
192
193    #[test]
194    fn test_log_output_lossy_conversion() {
195        // Test with invalid UTF-8
196        let invalid_utf8 = vec![0xFF, 0xFE, 0xFD];
197        let output = LogOutput::StdOut {
198            message: Bytes::from(invalid_utf8),
199        };
200
201        // Should not panic, should use replacement characters
202        let lossy = output.as_str_lossy();
203        assert!(!lossy.is_empty());
204    }
205}