docker_wrapper/command/
compose_start.rs

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