docker_wrapper/command/
compose_restart.rs

1//! Docker Compose restart command implementation using unified trait pattern.
2
3use super::{CommandExecutor, ComposeCommand, ComposeConfig, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6use std::time::Duration;
7
8/// Docker Compose restart command builder
9#[derive(Debug, Clone)]
10pub struct ComposeRestartCommand {
11    /// Base command executor
12    pub executor: CommandExecutor,
13    /// Base compose configuration
14    pub config: ComposeConfig,
15    /// Services to restart (empty for all)
16    pub services: Vec<String>,
17    /// Timeout for stopping containers before restarting
18    pub timeout: Option<Duration>,
19}
20
21/// Result from compose restart command
22#[derive(Debug, Clone)]
23pub struct ComposeRestartResult {
24    /// Raw stdout output
25    pub stdout: String,
26    /// Raw stderr output
27    pub stderr: String,
28    /// Success status
29    pub success: bool,
30    /// Services that were restarted
31    pub services: Vec<String>,
32}
33
34impl ComposeRestartCommand {
35    /// Create a new compose restart command
36    #[must_use]
37    pub fn new() -> Self {
38        Self {
39            executor: CommandExecutor::new(),
40            config: ComposeConfig::new(),
41            services: Vec::new(),
42            timeout: None,
43        }
44    }
45
46    /// Add a service to restart
47    #[must_use]
48    pub fn service(mut self, service: impl Into<String>) -> Self {
49        self.services.push(service.into());
50        self
51    }
52
53    /// Add multiple services to restart
54    #[must_use]
55    pub fn services<I, S>(mut self, services: I) -> Self
56    where
57        I: IntoIterator<Item = S>,
58        S: Into<String>,
59    {
60        self.services.extend(services.into_iter().map(Into::into));
61        self
62    }
63
64    /// Set the timeout for stopping containers before restarting
65    #[must_use]
66    pub fn timeout(mut self, timeout: Duration) -> Self {
67        self.timeout = Some(timeout);
68        self
69    }
70}
71
72impl Default for ComposeRestartCommand {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78#[async_trait]
79impl DockerCommand for ComposeRestartCommand {
80    type Output = ComposeRestartResult;
81
82    fn get_executor(&self) -> &CommandExecutor {
83        &self.executor
84    }
85
86    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
87        &mut self.executor
88    }
89
90    fn build_command_args(&self) -> Vec<String> {
91        // Use the ComposeCommand implementation explicitly
92        <Self as ComposeCommand>::build_command_args(self)
93    }
94
95    async fn execute(&self) -> Result<Self::Output> {
96        let args = <Self as ComposeCommand>::build_command_args(self);
97        let output = self.execute_command(args).await?;
98
99        Ok(ComposeRestartResult {
100            stdout: output.stdout,
101            stderr: output.stderr,
102            success: output.success,
103            services: self.services.clone(),
104        })
105    }
106}
107
108impl ComposeCommand for ComposeRestartCommand {
109    fn get_config(&self) -> &ComposeConfig {
110        &self.config
111    }
112
113    fn get_config_mut(&mut self) -> &mut ComposeConfig {
114        &mut self.config
115    }
116
117    fn subcommand(&self) -> &'static str {
118        "restart"
119    }
120
121    fn build_subcommand_args(&self) -> Vec<String> {
122        let mut args = Vec::new();
123
124        if let Some(timeout) = self.timeout {
125            args.push("--timeout".to_string());
126            args.push(timeout.as_secs().to_string());
127        }
128
129        // Add service names at the end
130        args.extend(self.services.clone());
131
132        args
133    }
134}
135
136impl ComposeRestartResult {
137    /// Check if the command was successful
138    #[must_use]
139    pub fn success(&self) -> bool {
140        self.success
141    }
142
143    /// Get the services that were restarted
144    #[must_use]
145    pub fn services(&self) -> &[String] {
146        &self.services
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_compose_restart_basic() {
156        let cmd = ComposeRestartCommand::new();
157        let args = cmd.build_subcommand_args();
158        assert!(args.is_empty());
159
160        let full_args = ComposeCommand::build_command_args(&cmd);
161        assert_eq!(full_args[0], "compose");
162        assert!(full_args.contains(&"restart".to_string()));
163    }
164
165    #[test]
166    fn test_compose_restart_with_services() {
167        let cmd = ComposeRestartCommand::new().service("web").service("db");
168        let args = cmd.build_subcommand_args();
169        assert_eq!(args, vec!["web", "db"]);
170    }
171
172    #[test]
173    fn test_compose_restart_with_timeout() {
174        let cmd = ComposeRestartCommand::new()
175            .timeout(Duration::from_secs(30))
176            .service("app");
177
178        let args = cmd.build_subcommand_args();
179        assert_eq!(args, vec!["--timeout", "30", "app"]);
180    }
181
182    #[test]
183    fn test_compose_restart_with_services_method() {
184        let cmd = ComposeRestartCommand::new().services(vec!["service1", "service2"]);
185        let args = cmd.build_subcommand_args();
186        assert_eq!(args, vec!["service1", "service2"]);
187    }
188
189    #[test]
190    fn test_compose_restart_builder_pattern() {
191        let cmd = ComposeRestartCommand::new()
192            .service("service1")
193            .service("service2")
194            .timeout(Duration::from_secs(60));
195
196        let args = cmd.build_subcommand_args();
197        assert_eq!(args, vec!["--timeout", "60", "service1", "service2"]);
198    }
199
200    #[test]
201    fn test_compose_config_integration() {
202        let cmd = ComposeRestartCommand::new()
203            .file("docker-compose.yml")
204            .project_name("myapp")
205            .timeout(Duration::from_secs(10))
206            .service("web");
207
208        let args = ComposeCommand::build_command_args(&cmd);
209        assert!(args.contains(&"--file".to_string()));
210        assert!(args.contains(&"docker-compose.yml".to_string()));
211        assert!(args.contains(&"--project-name".to_string()));
212        assert!(args.contains(&"myapp".to_string()));
213        assert!(args.contains(&"--timeout".to_string()));
214        assert!(args.contains(&"10".to_string()));
215        assert!(args.contains(&"web".to_string()));
216    }
217}