docker_wrapper/compose/
down.rs1use super::{execute_compose_command, ComposeCommand, ComposeConfig, ComposeOutput};
4use crate::error::Result;
5use async_trait::async_trait;
6use std::time::Duration;
7
8#[derive(Debug, Clone)]
10pub struct ComposeDownCommand {
11 config: ComposeConfig,
13 remove_images: Option<RemoveImages>,
15 volumes: bool,
17 remove_orphans: bool,
19 timeout: Option<Duration>,
21 services: Vec<String>,
23}
24
25#[derive(Debug, Clone, Copy)]
27pub enum RemoveImages {
28 All,
30 Local,
32}
33
34impl std::fmt::Display for RemoveImages {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 match self {
37 Self::All => write!(f, "all"),
38 Self::Local => write!(f, "local"),
39 }
40 }
41}
42
43impl ComposeDownCommand {
44 #[must_use]
46 pub fn new() -> Self {
47 Self {
48 config: ComposeConfig::new(),
49 remove_images: None,
50 volumes: false,
51 remove_orphans: false,
52 timeout: None,
53 services: Vec::new(),
54 }
55 }
56
57 #[must_use]
59 pub fn with_config(config: ComposeConfig) -> Self {
60 Self {
61 config,
62 ..Self::new()
63 }
64 }
65
66 #[must_use]
68 pub fn remove_images(mut self, policy: RemoveImages) -> Self {
69 self.remove_images = Some(policy);
70 self
71 }
72
73 #[must_use]
75 pub fn volumes(mut self) -> Self {
76 self.volumes = true;
77 self
78 }
79
80 #[must_use]
82 pub fn remove_orphans(mut self) -> Self {
83 self.remove_orphans = true;
84 self
85 }
86
87 #[must_use]
89 pub fn timeout(mut self, timeout: Duration) -> Self {
90 self.timeout = Some(timeout);
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 #[must_use]
114 pub fn file(mut self, path: impl Into<std::path::PathBuf>) -> Self {
115 self.config = self.config.file(path);
116 self
117 }
118
119 #[must_use]
121 pub fn project_name(mut self, name: impl Into<String>) -> Self {
122 self.config = self.config.project_name(name);
123 self
124 }
125
126 pub async fn run(&self) -> Result<ComposeDownResult> {
134 let output = self.execute().await?;
135
136 Ok(ComposeDownResult {
137 output,
138 removed_volumes: self.volumes,
139 removed_images: self.remove_images.is_some(),
140 })
141 }
142}
143
144impl Default for ComposeDownCommand {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150#[async_trait]
151impl ComposeCommand for ComposeDownCommand {
152 type Output = ComposeOutput;
153
154 fn subcommand(&self) -> &'static str {
155 "down"
156 }
157
158 fn build_args(&self) -> Vec<String> {
159 let mut args = Vec::new();
160
161 if let Some(ref remove) = self.remove_images {
162 args.push("--rmi".to_string());
163 args.push(remove.to_string());
164 }
165
166 if self.volumes {
167 args.push("--volumes".to_string());
168 }
169
170 if self.remove_orphans {
171 args.push("--remove-orphans".to_string());
172 }
173
174 if let Some(timeout) = self.timeout {
175 args.push("--timeout".to_string());
176 args.push(timeout.as_secs().to_string());
177 }
178
179 args.extend(self.services.clone());
181
182 args
183 }
184
185 async fn execute(&self) -> Result<Self::Output> {
186 execute_compose_command(&self.config, self.subcommand(), self.build_args()).await
187 }
188
189 fn config(&self) -> &ComposeConfig {
190 &self.config
191 }
192}
193
194#[derive(Debug, Clone)]
196pub struct ComposeDownResult {
197 pub output: ComposeOutput,
199 pub removed_volumes: bool,
201 pub removed_images: bool,
203}
204
205impl ComposeDownResult {
206 #[must_use]
208 pub fn success(&self) -> bool {
209 self.output.success
210 }
211
212 #[must_use]
214 pub fn volumes_removed(&self) -> bool {
215 self.removed_volumes
216 }
217
218 #[must_use]
220 pub fn images_removed(&self) -> bool {
221 self.removed_images
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[test]
230 fn test_compose_down_basic() {
231 let cmd = ComposeDownCommand::new();
232 let args = cmd.build_args();
233 assert!(args.is_empty());
234 }
235
236 #[test]
237 fn test_compose_down_with_volumes() {
238 let cmd = ComposeDownCommand::new().volumes();
239 let args = cmd.build_args();
240 assert_eq!(args, vec!["--volumes"]);
241 }
242
243 #[test]
244 fn test_compose_down_remove_images() {
245 let cmd = ComposeDownCommand::new().remove_images(RemoveImages::All);
246 let args = cmd.build_args();
247 assert_eq!(args, vec!["--rmi", "all"]);
248 }
249
250 #[test]
251 fn test_compose_down_all_options() {
252 let cmd = ComposeDownCommand::new()
253 .remove_images(RemoveImages::Local)
254 .volumes()
255 .remove_orphans()
256 .timeout(Duration::from_secs(30))
257 .service("web")
258 .service("db");
259
260 let args = cmd.build_args();
261 assert_eq!(
262 args,
263 vec![
264 "--rmi",
265 "local",
266 "--volumes",
267 "--remove-orphans",
268 "--timeout",
269 "30",
270 "web",
271 "db"
272 ]
273 );
274 }
275
276 #[test]
277 fn test_remove_images_display() {
278 assert_eq!(RemoveImages::All.to_string(), "all");
279 assert_eq!(RemoveImages::Local.to_string(), "local");
280 }
281}