docker_wrapper/compose/
events.rs1use crate::compose::{ComposeCommandV2 as ComposeCommand, ComposeConfig};
4use crate::error::Result;
5use async_trait::async_trait;
6use serde::Deserialize;
7
8#[derive(Debug, Clone, Default)]
12pub struct ComposeEventsCommand {
13 pub config: ComposeConfig,
15 pub json: bool,
17 pub since: Option<String>,
19 pub until: Option<String>,
21 pub services: Vec<String>,
23}
24
25#[derive(Debug, Clone, Deserialize)]
27pub struct ComposeEvent {
28 pub time: String,
30 #[serde(rename = "type")]
32 pub event_type: String,
33 pub action: String,
35 pub service: Option<String>,
37 pub container: Option<String>,
39 pub attributes: Option<serde_json::Value>,
41}
42
43#[derive(Debug, Clone)]
45pub struct EventsResult {
46 pub output: String,
48 pub events: Vec<ComposeEvent>,
50}
51
52impl ComposeEventsCommand {
53 #[must_use]
55 pub fn new() -> Self {
56 Self::default()
57 }
58
59 #[must_use]
61 pub fn file<P: Into<std::path::PathBuf>>(mut self, file: P) -> Self {
62 self.config.files.push(file.into());
63 self
64 }
65
66 #[must_use]
68 pub fn project_name(mut self, name: impl Into<String>) -> Self {
69 self.config.project_name = Some(name.into());
70 self
71 }
72
73 #[must_use]
75 pub fn json(mut self) -> Self {
76 self.json = true;
77 self
78 }
79
80 #[must_use]
82 pub fn since(mut self, timestamp: impl Into<String>) -> Self {
83 self.since = Some(timestamp.into());
84 self
85 }
86
87 #[must_use]
89 pub fn until(mut self, timestamp: impl Into<String>) -> Self {
90 self.until = Some(timestamp.into());
91 self
92 }
93
94 #[must_use]
96 pub fn service(mut self, service: impl Into<String>) -> Self {
97 self.services.push(service.into());
98 self
99 }
100
101 #[must_use]
103 pub fn services<I, S>(mut self, services: I) -> Self
104 where
105 I: IntoIterator<Item = S>,
106 S: Into<String>,
107 {
108 self.services.extend(services.into_iter().map(Into::into));
109 self
110 }
111
112 fn build_args(&self) -> Vec<String> {
113 let mut args = vec!["events".to_string()];
114
115 if self.json {
117 args.push("--json".to_string());
118 }
119
120 if let Some(since) = &self.since {
122 args.push("--since".to_string());
123 args.push(since.clone());
124 }
125 if let Some(until) = &self.until {
126 args.push("--until".to_string());
127 args.push(until.clone());
128 }
129
130 args.extend(self.services.clone());
132
133 args
134 }
135}
136
137#[async_trait]
138impl ComposeCommand for ComposeEventsCommand {
139 type Output = EventsResult;
140
141 fn get_config(&self) -> &ComposeConfig {
142 &self.config
143 }
144
145 fn get_config_mut(&mut self) -> &mut ComposeConfig {
146 &mut self.config
147 }
148
149 async fn execute_compose(&self, args: Vec<String>) -> Result<Self::Output> {
150 let output = self.execute_compose_command(args).await?;
151
152 let events = if self.json {
154 output
155 .stdout
156 .lines()
157 .filter_map(|line| serde_json::from_str(line).ok())
158 .collect()
159 } else {
160 Vec::new()
161 };
162
163 Ok(EventsResult {
164 output: output.stdout,
165 events,
166 })
167 }
168
169 async fn execute(&self) -> Result<Self::Output> {
170 let args = self.build_args();
171 self.execute_compose(args).await
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_events_command_basic() {
181 let cmd = ComposeEventsCommand::new();
182 let args = cmd.build_args();
183 assert_eq!(args[0], "events");
184 }
185
186 #[test]
187 fn test_events_command_with_json() {
188 let cmd = ComposeEventsCommand::new().json();
189 let args = cmd.build_args();
190 assert!(args.contains(&"--json".to_string()));
191 }
192
193 #[test]
194 fn test_events_command_with_timestamps() {
195 let cmd = ComposeEventsCommand::new()
196 .since("2025-08-23T00:00:00")
197 .until("2025-08-23T23:59:59");
198 let args = cmd.build_args();
199 assert!(args.contains(&"--since".to_string()));
200 assert!(args.contains(&"2025-08-23T00:00:00".to_string()));
201 assert!(args.contains(&"--until".to_string()));
202 assert!(args.contains(&"2025-08-23T23:59:59".to_string()));
203 }
204
205 #[test]
206 fn test_events_command_with_services() {
207 let cmd = ComposeEventsCommand::new().service("web").service("db");
208 let args = cmd.build_args();
209 assert!(args.contains(&"web".to_string()));
210 assert!(args.contains(&"db".to_string()));
211 }
212}