docker_wrapper/command/compose/
top.rs

1//! Docker Compose top 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 top command builder
8#[derive(Debug, Clone)]
9pub struct ComposeTopCommand {
10    /// Base command executor
11    pub executor: CommandExecutor,
12    /// Base compose configuration
13    pub config: ComposeConfig,
14    /// Services to show processes for (empty for all)
15    pub services: Vec<String>,
16}
17
18/// Result from compose top command
19#[derive(Debug, Clone)]
20pub struct ComposeTopResult {
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 queried
28    pub services: Vec<String>,
29}
30
31impl ComposeTopCommand {
32    /// Create a new compose top 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 show processes for
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 show processes for
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 ComposeTopCommand {
62    fn default() -> Self {
63        Self::new()
64    }
65}
66
67#[async_trait]
68impl DockerCommand for ComposeTopCommand {
69    type Output = ComposeTopResult;
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        <Self as ComposeCommand>::build_command_args(self)
81    }
82
83    async fn execute(&self) -> Result<Self::Output> {
84        let args = <Self as ComposeCommand>::build_command_args(self);
85        let output = self.execute_command(args).await?;
86
87        Ok(ComposeTopResult {
88            stdout: output.stdout,
89            stderr: output.stderr,
90            success: output.success,
91            services: self.services.clone(),
92        })
93    }
94}
95
96impl ComposeCommand for ComposeTopCommand {
97    fn get_config(&self) -> &ComposeConfig {
98        &self.config
99    }
100
101    fn get_config_mut(&mut self) -> &mut ComposeConfig {
102        &mut self.config
103    }
104
105    fn subcommand(&self) -> &'static str {
106        "top"
107    }
108
109    fn build_subcommand_args(&self) -> Vec<String> {
110        self.services.clone()
111    }
112}
113
114impl ComposeTopResult {
115    /// Check if the command was successful
116    #[must_use]
117    pub fn success(&self) -> bool {
118        self.success
119    }
120
121    /// Get the services that were queried
122    #[must_use]
123    pub fn services(&self) -> &[String] {
124        &self.services
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn test_compose_top_basic() {
134        let cmd = ComposeTopCommand::new();
135        let args = cmd.build_subcommand_args();
136        assert!(args.is_empty());
137
138        let full_args = ComposeCommand::build_command_args(&cmd);
139        assert_eq!(full_args[0], "compose");
140        assert!(full_args.contains(&"top".to_string()));
141    }
142
143    #[test]
144    fn test_compose_top_with_services() {
145        let cmd = ComposeTopCommand::new().services(vec!["web", "db"]);
146        let args = cmd.build_subcommand_args();
147        assert_eq!(args, vec!["web", "db"]);
148    }
149}