docker_wrapper/compose/
rm.rs

1//! Docker Compose rm command implementation.
2
3use crate::compose::{ComposeCommandV2 as ComposeCommand, ComposeConfig};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Docker Compose rm command
8///
9/// Remove stopped service containers.
10#[derive(Debug, Clone, Default)]
11#[allow(clippy::struct_excessive_bools)]
12pub struct ComposeRmCommand {
13    /// Base configuration
14    pub config: ComposeConfig,
15    /// Force removal without confirmation
16    pub force: bool,
17    /// Stop containers if running
18    pub stop: bool,
19    /// Remove volumes
20    pub volumes: bool,
21    /// Remove all containers (not just stopped)
22    pub all: bool,
23    /// Services to remove
24    pub services: Vec<String>,
25}
26
27/// Result from rm command
28#[derive(Debug, Clone)]
29pub struct RmResult {
30    /// Output from the command
31    pub output: String,
32    /// Whether the operation succeeded
33    pub success: bool,
34}
35
36impl ComposeRmCommand {
37    /// Create a new rm command
38    #[must_use]
39    pub fn new() -> Self {
40        Self::default()
41    }
42
43    /// Add a compose file
44    #[must_use]
45    pub fn file<P: Into<std::path::PathBuf>>(mut self, file: P) -> Self {
46        self.config.files.push(file.into());
47        self
48    }
49
50    /// Set project name
51    #[must_use]
52    pub fn project_name(mut self, name: impl Into<String>) -> Self {
53        self.config.project_name = Some(name.into());
54        self
55    }
56
57    /// Force removal without confirmation
58    #[must_use]
59    pub fn force(mut self) -> Self {
60        self.force = true;
61        self
62    }
63
64    /// Stop containers if running
65    #[must_use]
66    pub fn stop(mut self) -> Self {
67        self.stop = true;
68        self
69    }
70
71    /// Remove volumes
72    #[must_use]
73    pub fn volumes(mut self) -> Self {
74        self.volumes = true;
75        self
76    }
77
78    /// Remove all containers
79    #[must_use]
80    pub fn all(mut self) -> Self {
81        self.all = true;
82        self
83    }
84
85    /// Add a service to remove
86    #[must_use]
87    pub fn service(mut self, service: impl Into<String>) -> Self {
88        self.services.push(service.into());
89        self
90    }
91
92    /// Add multiple services to remove
93    #[must_use]
94    pub fn services<I, S>(mut self, services: I) -> Self
95    where
96        I: IntoIterator<Item = S>,
97        S: Into<String>,
98    {
99        self.services.extend(services.into_iter().map(Into::into));
100        self
101    }
102
103    fn build_args(&self) -> Vec<String> {
104        let mut args = vec!["rm".to_string()];
105
106        // Add flags
107        if self.force {
108            args.push("--force".to_string());
109        }
110        if self.stop {
111            args.push("--stop".to_string());
112        }
113        if self.volumes {
114            args.push("--volumes".to_string());
115        }
116        if self.all {
117            args.push("--all".to_string());
118        }
119
120        // Add services
121        args.extend(self.services.clone());
122
123        args
124    }
125}
126
127#[async_trait]
128impl ComposeCommand for ComposeRmCommand {
129    type Output = RmResult;
130
131    fn get_config(&self) -> &ComposeConfig {
132        &self.config
133    }
134
135    fn get_config_mut(&mut self) -> &mut ComposeConfig {
136        &mut self.config
137    }
138
139    async fn execute_compose(&self, args: Vec<String>) -> Result<Self::Output> {
140        let output = self.execute_compose_command(args).await?;
141
142        Ok(RmResult {
143            output: output.stdout,
144            success: output.success,
145        })
146    }
147
148    async fn execute(&self) -> Result<Self::Output> {
149        let args = self.build_args();
150        self.execute_compose(args).await
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_rm_command_basic() {
160        let cmd = ComposeRmCommand::new();
161        let args = cmd.build_args();
162        assert_eq!(args[0], "rm");
163    }
164
165    #[test]
166    fn test_rm_command_with_force() {
167        let cmd = ComposeRmCommand::new().force().stop();
168        let args = cmd.build_args();
169        assert!(args.contains(&"--force".to_string()));
170        assert!(args.contains(&"--stop".to_string()));
171    }
172
173    #[test]
174    fn test_rm_command_with_services() {
175        let cmd = ComposeRmCommand::new().service("web").service("db");
176        let args = cmd.build_args();
177        assert!(args.contains(&"web".to_string()));
178        assert!(args.contains(&"db".to_string()));
179    }
180
181    #[test]
182    fn test_rm_command_with_volumes() {
183        let cmd = ComposeRmCommand::new().volumes().all();
184        let args = cmd.build_args();
185        assert!(args.contains(&"--volumes".to_string()));
186        assert!(args.contains(&"--all".to_string()));
187    }
188}