docker_wrapper/command/
compose_rm.rs1use super::{CommandExecutor, ComposeCommand, ComposeConfig, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7#[derive(Debug, Clone)]
9#[allow(clippy::struct_excessive_bools)] pub struct ComposeRmCommand {
11 pub executor: CommandExecutor,
13 pub config: ComposeConfig,
15 pub services: Vec<String>,
17 pub force: bool,
19 pub stop: bool,
21 pub volumes: bool,
23}
24
25#[derive(Debug, Clone)]
27pub struct ComposeRmResult {
28 pub stdout: String,
30 pub stderr: String,
32 pub success: bool,
34 pub services: Vec<String>,
36 pub volumes_removed: bool,
38}
39
40impl ComposeRmCommand {
41 #[must_use]
43 pub fn new() -> Self {
44 Self {
45 executor: CommandExecutor::new(),
46 config: ComposeConfig::new(),
47 services: Vec::new(),
48 force: false,
49 stop: false,
50 volumes: false,
51 }
52 }
53
54 #[must_use]
56 pub fn service(mut self, service: impl Into<String>) -> Self {
57 self.services.push(service.into());
58 self
59 }
60
61 #[must_use]
63 pub fn services<I, S>(mut self, services: I) -> Self
64 where
65 I: IntoIterator<Item = S>,
66 S: Into<String>,
67 {
68 self.services.extend(services.into_iter().map(Into::into));
69 self
70 }
71
72 #[must_use]
74 pub fn force(mut self) -> Self {
75 self.force = true;
76 self
77 }
78
79 #[must_use]
81 pub fn stop(mut self) -> Self {
82 self.stop = true;
83 self
84 }
85
86 #[must_use]
88 pub fn volumes(mut self) -> Self {
89 self.volumes = true;
90 self
91 }
92}
93
94impl Default for ComposeRmCommand {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100#[async_trait]
101impl DockerCommand for ComposeRmCommand {
102 type Output = ComposeRmResult;
103
104 fn get_executor(&self) -> &CommandExecutor {
105 &self.executor
106 }
107
108 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
109 &mut self.executor
110 }
111
112 fn build_command_args(&self) -> Vec<String> {
113 <Self as ComposeCommand>::build_command_args(self)
115 }
116
117 async fn execute(&self) -> Result<Self::Output> {
118 let args = <Self as ComposeCommand>::build_command_args(self);
119 let output = self.execute_command(args).await?;
120
121 Ok(ComposeRmResult {
122 stdout: output.stdout,
123 stderr: output.stderr,
124 success: output.success,
125 services: self.services.clone(),
126 volumes_removed: self.volumes,
127 })
128 }
129}
130
131impl ComposeCommand for ComposeRmCommand {
132 fn get_config(&self) -> &ComposeConfig {
133 &self.config
134 }
135
136 fn get_config_mut(&mut self) -> &mut ComposeConfig {
137 &mut self.config
138 }
139
140 fn subcommand(&self) -> &'static str {
141 "rm"
142 }
143
144 fn build_subcommand_args(&self) -> Vec<String> {
145 let mut args = Vec::new();
146
147 if self.force {
148 args.push("--force".to_string());
149 }
150
151 if self.stop {
152 args.push("--stop".to_string());
153 }
154
155 if self.volumes {
156 args.push("--volumes".to_string());
157 }
158
159 args.extend(self.services.clone());
161
162 args
163 }
164}
165
166impl ComposeRmResult {
167 #[must_use]
169 pub fn success(&self) -> bool {
170 self.success
171 }
172
173 #[must_use]
175 pub fn services(&self) -> &[String] {
176 &self.services
177 }
178
179 #[must_use]
181 pub fn volumes_removed(&self) -> bool {
182 self.volumes_removed
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_compose_rm_basic() {
192 let cmd = ComposeRmCommand::new();
193 let args = cmd.build_subcommand_args();
194 assert!(args.is_empty());
195
196 let full_args = ComposeCommand::build_command_args(&cmd);
197 assert_eq!(full_args[0], "compose");
198 assert!(full_args.contains(&"rm".to_string()));
199 }
200
201 #[test]
202 fn test_compose_rm_with_services() {
203 let cmd = ComposeRmCommand::new().service("web").service("worker");
204 let args = cmd.build_subcommand_args();
205 assert_eq!(args, vec!["web", "worker"]);
206 }
207
208 #[test]
209 fn test_compose_rm_with_flags() {
210 let cmd = ComposeRmCommand::new()
211 .force()
212 .stop()
213 .volumes()
214 .service("app");
215
216 let args = cmd.build_subcommand_args();
217 assert!(args.contains(&"--force".to_string()));
218 assert!(args.contains(&"--stop".to_string()));
219 assert!(args.contains(&"--volumes".to_string()));
220 assert!(args.contains(&"app".to_string()));
221 }
222
223 #[test]
224 fn test_compose_rm_force_only() {
225 let cmd = ComposeRmCommand::new().force().service("database");
226 let args = cmd.build_subcommand_args();
227 assert_eq!(args, vec!["--force", "database"]);
228 }
229
230 #[test]
231 fn test_compose_rm_with_volumes() {
232 let cmd = ComposeRmCommand::new()
233 .volumes()
234 .stop()
235 .services(vec!["cache", "queue"]);
236
237 let args = cmd.build_subcommand_args();
238 assert!(args.contains(&"--volumes".to_string()));
239 assert!(args.contains(&"--stop".to_string()));
240 assert!(args.contains(&"cache".to_string()));
241 assert!(args.contains(&"queue".to_string()));
242 }
243
244 #[test]
245 fn test_compose_config_integration() {
246 let cmd = ComposeRmCommand::new()
247 .file("docker-compose.yml")
248 .project_name("myapp")
249 .force()
250 .volumes()
251 .service("web");
252
253 let args = ComposeCommand::build_command_args(&cmd);
254 assert!(args.contains(&"--file".to_string()));
255 assert!(args.contains(&"docker-compose.yml".to_string()));
256 assert!(args.contains(&"--project-name".to_string()));
257 assert!(args.contains(&"myapp".to_string()));
258 assert!(args.contains(&"--force".to_string()));
259 assert!(args.contains(&"--volumes".to_string()));
260 assert!(args.contains(&"web".to_string()));
261 }
262}