docker_wrapper/command/compose/
watch.rs

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