docker_wrapper/command/compose/
port.rs1use crate::command::{CommandExecutor, ComposeCommand, ComposeConfig, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7#[derive(Debug, Clone)]
9pub struct ComposePortCommand {
10 pub executor: CommandExecutor,
12 pub config: ComposeConfig,
14 pub service: String,
16 pub private_port: Option<u16>,
18 pub protocol: Option<String>,
20 pub index: Option<u16>,
22}
23
24#[derive(Debug, Clone)]
26pub struct ComposePortResult {
27 pub stdout: String,
29 pub stderr: String,
31 pub success: bool,
33 pub service: String,
35 pub port_mappings: Vec<String>,
37}
38
39impl ComposePortCommand {
40 #[must_use]
42 pub fn new(service: impl Into<String>) -> Self {
43 Self {
44 executor: CommandExecutor::new(),
45 config: ComposeConfig::new(),
46 service: service.into(),
47 private_port: None,
48 protocol: None,
49 index: None,
50 }
51 }
52
53 #[must_use]
55 pub fn private_port(mut self, port: u16) -> Self {
56 self.private_port = Some(port);
57 self
58 }
59
60 #[must_use]
62 pub fn protocol(mut self, protocol: impl Into<String>) -> Self {
63 self.protocol = Some(protocol.into());
64 self
65 }
66
67 #[must_use]
69 pub fn index(mut self, index: u16) -> Self {
70 self.index = Some(index);
71 self
72 }
73}
74
75#[async_trait]
76impl DockerCommand for ComposePortCommand {
77 type Output = ComposePortResult;
78
79 fn get_executor(&self) -> &CommandExecutor {
80 &self.executor
81 }
82
83 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
84 &mut self.executor
85 }
86
87 fn build_command_args(&self) -> Vec<String> {
88 <Self as ComposeCommand>::build_command_args(self)
89 }
90
91 async fn execute(&self) -> Result<Self::Output> {
92 let args = <Self as ComposeCommand>::build_command_args(self);
93 let output = self.execute_command(args).await?;
94
95 let port_mappings = output
96 .stdout
97 .lines()
98 .filter(|line| !line.trim().is_empty())
99 .map(|line| line.trim().to_string())
100 .collect();
101
102 Ok(ComposePortResult {
103 stdout: output.stdout,
104 stderr: output.stderr,
105 success: output.success,
106 service: self.service.clone(),
107 port_mappings,
108 })
109 }
110}
111
112impl ComposeCommand for ComposePortCommand {
113 fn get_config(&self) -> &ComposeConfig {
114 &self.config
115 }
116
117 fn get_config_mut(&mut self) -> &mut ComposeConfig {
118 &mut self.config
119 }
120
121 fn subcommand(&self) -> &'static str {
122 "port"
123 }
124
125 fn build_subcommand_args(&self) -> Vec<String> {
126 let mut args = Vec::new();
127
128 if let Some(protocol) = &self.protocol {
129 args.push("--protocol".to_string());
130 args.push(protocol.clone());
131 }
132
133 if let Some(index) = self.index {
134 args.push("--index".to_string());
135 args.push(index.to_string());
136 }
137
138 args.push(self.service.clone());
139
140 if let Some(port) = self.private_port {
141 args.push(port.to_string());
142 }
143
144 args
145 }
146}
147
148impl ComposePortResult {
149 #[must_use]
151 pub fn success(&self) -> bool {
152 self.success
153 }
154
155 #[must_use]
157 pub fn service(&self) -> &str {
158 &self.service
159 }
160
161 #[must_use]
163 pub fn port_mappings(&self) -> &[String] {
164 &self.port_mappings
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_compose_port_basic() {
174 let cmd = ComposePortCommand::new("web");
175 let args = cmd.build_subcommand_args();
176 assert!(args.contains(&"web".to_string()));
177
178 let full_args = ComposeCommand::build_command_args(&cmd);
179 assert_eq!(full_args[0], "compose");
180 assert!(full_args.contains(&"port".to_string()));
181 }
182
183 #[test]
184 fn test_compose_port_with_options() {
185 let cmd = ComposePortCommand::new("api")
186 .private_port(8080)
187 .protocol("tcp")
188 .index(1);
189
190 let args = cmd.build_subcommand_args();
191 assert!(args.contains(&"--protocol".to_string()));
192 assert!(args.contains(&"tcp".to_string()));
193 assert!(args.contains(&"--index".to_string()));
194 assert!(args.contains(&"1".to_string()));
195 assert!(args.contains(&"api".to_string()));
196 assert!(args.contains(&"8080".to_string()));
197 }
198}