docker_wrapper/compose/
watch.rs

1//! Docker Compose watch command implementation.
2
3use crate::compose::{ComposeCommandV2 as ComposeCommand, ComposeConfig};
4use crate::error::Result;
5use async_trait::async_trait;
6
7/// Docker Compose watch command
8///
9/// Watch build context for changes and rebuild/restart services automatically.
10#[derive(Debug, Clone, Default)]
11pub struct ComposeWatchCommand {
12    /// Base configuration
13    pub config: ComposeConfig,
14    /// Don't build images
15    pub no_up: bool,
16    /// Services to watch
17    pub services: Vec<String>,
18}
19
20/// Result from watch command
21#[derive(Debug, Clone)]
22pub struct WatchResult {
23    /// Output from the command
24    pub output: String,
25    /// Whether the operation succeeded
26    pub success: bool,
27}
28
29impl ComposeWatchCommand {
30    /// Create a new watch command
31    #[must_use]
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    /// Add a compose file
37    #[must_use]
38    pub fn file<P: Into<std::path::PathBuf>>(mut self, file: P) -> Self {
39        self.config.files.push(file.into());
40        self
41    }
42
43    /// Set project name
44    #[must_use]
45    pub fn project_name(mut self, name: impl Into<String>) -> Self {
46        self.config.project_name = Some(name.into());
47        self
48    }
49
50    /// Don't start services before watching
51    #[must_use]
52    pub fn no_up(mut self) -> Self {
53        self.no_up = true;
54        self
55    }
56
57    /// Add a service to watch
58    #[must_use]
59    pub fn service(mut self, service: impl Into<String>) -> Self {
60        self.services.push(service.into());
61        self
62    }
63
64    /// Add multiple services to watch
65    #[must_use]
66    pub fn services<I, S>(mut self, services: I) -> Self
67    where
68        I: IntoIterator<Item = S>,
69        S: Into<String>,
70    {
71        self.services.extend(services.into_iter().map(Into::into));
72        self
73    }
74
75    fn build_args(&self) -> Vec<String> {
76        let mut args = vec!["watch".to_string()];
77
78        // Add flags
79        if self.no_up {
80            args.push("--no-up".to_string());
81        }
82
83        // Add services
84        args.extend(self.services.clone());
85
86        args
87    }
88}
89
90#[async_trait]
91impl ComposeCommand for ComposeWatchCommand {
92    type Output = WatchResult;
93
94    fn get_config(&self) -> &ComposeConfig {
95        &self.config
96    }
97
98    fn get_config_mut(&mut self) -> &mut ComposeConfig {
99        &mut self.config
100    }
101
102    async fn execute_compose(&self, args: Vec<String>) -> Result<Self::Output> {
103        let output = self.execute_compose_command(args).await?;
104
105        Ok(WatchResult {
106            output: output.stdout,
107            success: output.success,
108        })
109    }
110
111    async fn execute(&self) -> Result<Self::Output> {
112        let args = self.build_args();
113        self.execute_compose(args).await
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_watch_command_basic() {
123        let cmd = ComposeWatchCommand::new();
124        let args = cmd.build_args();
125        assert_eq!(args[0], "watch");
126    }
127
128    #[test]
129    fn test_watch_command_with_no_up() {
130        let cmd = ComposeWatchCommand::new().no_up();
131        let args = cmd.build_args();
132        assert!(args.contains(&"--no-up".to_string()));
133    }
134
135    #[test]
136    fn test_watch_command_with_services() {
137        let cmd = ComposeWatchCommand::new().service("web").service("worker");
138        let args = cmd.build_args();
139        assert!(args.contains(&"web".to_string()));
140        assert!(args.contains(&"worker".to_string()));
141    }
142}