docker_wrapper/compose/
logs.rs1use super::{execute_compose_command, ComposeCommand, ComposeConfig, ComposeOutput};
4use crate::error::Result;
5use async_trait::async_trait;
6
7#[derive(Debug, Clone)]
9#[allow(clippy::struct_excessive_bools)] pub struct ComposeLogsCommand {
11 config: ComposeConfig,
13 services: Vec<String>,
15 follow: bool,
17 timestamps: bool,
19 tail: Option<String>,
21 since: Option<String>,
23 until: Option<String>,
25 no_log_prefix: bool,
27 no_color: bool,
29}
30
31impl ComposeLogsCommand {
32 #[must_use]
34 pub fn new() -> Self {
35 Self {
36 config: ComposeConfig::new(),
37 services: Vec::new(),
38 follow: false,
39 timestamps: false,
40 tail: None,
41 since: None,
42 until: None,
43 no_log_prefix: false,
44 no_color: false,
45 }
46 }
47
48 #[must_use]
50 pub fn with_config(config: ComposeConfig) -> Self {
51 Self {
52 config,
53 ..Self::new()
54 }
55 }
56
57 #[must_use]
59 pub fn service(mut self, service: impl Into<String>) -> Self {
60 self.services.push(service.into());
61 self
62 }
63
64 #[must_use]
66 pub fn follow(mut self) -> Self {
67 self.follow = true;
68 self
69 }
70
71 #[must_use]
73 pub fn timestamps(mut self) -> Self {
74 self.timestamps = true;
75 self
76 }
77
78 #[must_use]
80 pub fn tail(mut self, lines: impl Into<String>) -> Self {
81 self.tail = Some(lines.into());
82 self
83 }
84
85 #[must_use]
87 pub fn since(mut self, timestamp: impl Into<String>) -> Self {
88 self.since = Some(timestamp.into());
89 self
90 }
91
92 #[must_use]
94 pub fn until(mut self, timestamp: impl Into<String>) -> Self {
95 self.until = Some(timestamp.into());
96 self
97 }
98
99 #[must_use]
101 pub fn no_log_prefix(mut self) -> Self {
102 self.no_log_prefix = true;
103 self
104 }
105
106 #[must_use]
108 pub fn no_color(mut self) -> Self {
109 self.no_color = true;
110 self
111 }
112
113 #[must_use]
115 pub fn file(mut self, path: impl Into<std::path::PathBuf>) -> Self {
116 self.config = self.config.file(path);
117 self
118 }
119
120 #[must_use]
122 pub fn project_name(mut self, name: impl Into<String>) -> Self {
123 self.config = self.config.project_name(name);
124 self
125 }
126
127 pub async fn run(&self) -> Result<ComposeLogsResult> {
135 let output = self.execute().await?;
136
137 Ok(ComposeLogsResult {
138 output,
139 services: self.services.clone(),
140 })
141 }
142}
143
144impl Default for ComposeLogsCommand {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150#[async_trait]
151impl ComposeCommand for ComposeLogsCommand {
152 type Output = ComposeOutput;
153
154 fn subcommand(&self) -> &'static str {
155 "logs"
156 }
157
158 fn build_args(&self) -> Vec<String> {
159 let mut args = Vec::new();
160
161 if self.follow {
162 args.push("--follow".to_string());
163 }
164
165 if self.timestamps {
166 args.push("--timestamps".to_string());
167 }
168
169 if let Some(ref tail) = self.tail {
170 args.push("--tail".to_string());
171 args.push(tail.clone());
172 }
173
174 if let Some(ref since) = self.since {
175 args.push("--since".to_string());
176 args.push(since.clone());
177 }
178
179 if let Some(ref until) = self.until {
180 args.push("--until".to_string());
181 args.push(until.clone());
182 }
183
184 if self.no_log_prefix {
185 args.push("--no-log-prefix".to_string());
186 }
187
188 if self.no_color {
189 args.push("--no-color".to_string());
190 }
191
192 args.extend(self.services.clone());
194
195 args
196 }
197
198 async fn execute(&self) -> Result<Self::Output> {
199 execute_compose_command(&self.config, self.subcommand(), self.build_args()).await
200 }
201
202 fn config(&self) -> &ComposeConfig {
203 &self.config
204 }
205}
206
207#[derive(Debug, Clone)]
209pub struct ComposeLogsResult {
210 pub output: ComposeOutput,
212 pub services: Vec<String>,
214}
215
216impl ComposeLogsResult {
217 #[must_use]
219 pub fn success(&self) -> bool {
220 self.output.success
221 }
222
223 #[must_use]
225 pub fn services(&self) -> &[String] {
226 &self.services
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_compose_logs_basic() {
236 let cmd = ComposeLogsCommand::new();
237 let args = cmd.build_args();
238 assert!(args.is_empty());
239 }
240
241 #[test]
242 fn test_compose_logs_follow() {
243 let cmd = ComposeLogsCommand::new().follow().timestamps();
244 let args = cmd.build_args();
245 assert_eq!(args, vec!["--follow", "--timestamps"]);
246 }
247
248 #[test]
249 fn test_compose_logs_with_tail() {
250 let cmd = ComposeLogsCommand::new().tail("100").service("web");
251 let args = cmd.build_args();
252 assert_eq!(args, vec!["--tail", "100", "web"]);
253 }
254
255 #[test]
256 fn test_compose_logs_all_options() {
257 let cmd = ComposeLogsCommand::new()
258 .follow()
259 .timestamps()
260 .tail("50")
261 .since("2024-01-01T00:00:00")
262 .no_color()
263 .service("web")
264 .service("db");
265
266 let args = cmd.build_args();
267 assert!(args.contains(&"--follow".to_string()));
268 assert!(args.contains(&"--timestamps".to_string()));
269 assert!(args.contains(&"--tail".to_string()));
270 assert!(args.contains(&"50".to_string()));
271 assert!(args.contains(&"--since".to_string()));
272 assert!(args.contains(&"2024-01-01T00:00:00".to_string()));
273 assert!(args.contains(&"--no-color".to_string()));
274 assert!(args.contains(&"web".to_string()));
275 assert!(args.contains(&"db".to_string()));
276 }
277}