docker_wrapper/command/compose/
version.rs1use crate::command::{CommandExecutor, ComposeCommand, ComposeConfig, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6use serde::Deserialize;
7
8#[derive(Debug, Clone)]
10pub struct ComposeVersionCommand {
11 pub executor: CommandExecutor,
13 pub config: ComposeConfig,
15 pub format: Option<VersionFormat>,
17 pub short: bool,
19}
20
21#[derive(Debug, Clone, Copy)]
23pub enum VersionFormat {
24 Pretty,
26 Json,
28}
29
30impl std::fmt::Display for VersionFormat {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 Self::Pretty => write!(f, "pretty"),
34 Self::Json => write!(f, "json"),
35 }
36 }
37}
38
39#[derive(Debug, Clone, Deserialize)]
41#[serde(rename_all = "PascalCase")]
42pub struct VersionInfo {
43 pub version: String,
45}
46
47#[derive(Debug, Clone)]
49pub struct ComposeVersionResult {
50 pub stdout: String,
52 pub stderr: String,
54 pub success: bool,
56 pub version_info: Option<VersionInfo>,
58}
59
60impl ComposeVersionCommand {
61 #[must_use]
63 pub fn new() -> Self {
64 Self {
65 executor: CommandExecutor::new(),
66 config: ComposeConfig::new(),
67 format: None,
68 short: false,
69 }
70 }
71
72 #[must_use]
74 pub fn format(mut self, format: VersionFormat) -> Self {
75 self.format = Some(format);
76 self
77 }
78
79 #[must_use]
81 pub fn format_json(mut self) -> Self {
82 self.format = Some(VersionFormat::Json);
83 self
84 }
85
86 #[must_use]
88 pub fn format_pretty(mut self) -> Self {
89 self.format = Some(VersionFormat::Pretty);
90 self
91 }
92
93 #[must_use]
95 pub fn short(mut self) -> Self {
96 self.short = true;
97 self
98 }
99}
100
101impl Default for ComposeVersionCommand {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107#[async_trait]
108impl DockerCommand for ComposeVersionCommand {
109 type Output = ComposeVersionResult;
110
111 fn get_executor(&self) -> &CommandExecutor {
112 &self.executor
113 }
114
115 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
116 &mut self.executor
117 }
118
119 fn build_command_args(&self) -> Vec<String> {
120 <Self as ComposeCommand>::build_command_args(self)
122 }
123
124 async fn execute(&self) -> Result<Self::Output> {
125 let args = <Self as ComposeCommand>::build_command_args(self);
126 let output = self.execute_command(args).await?;
127
128 let version_info = if matches!(self.format, Some(VersionFormat::Json)) {
130 serde_json::from_str(&output.stdout).ok()
131 } else {
132 None
133 };
134
135 Ok(ComposeVersionResult {
136 stdout: output.stdout,
137 stderr: output.stderr,
138 success: output.success,
139 version_info,
140 })
141 }
142}
143
144impl ComposeCommand for ComposeVersionCommand {
145 fn get_config(&self) -> &ComposeConfig {
146 &self.config
147 }
148
149 fn get_config_mut(&mut self) -> &mut ComposeConfig {
150 &mut self.config
151 }
152
153 fn subcommand(&self) -> &'static str {
154 "version"
155 }
156
157 fn build_subcommand_args(&self) -> Vec<String> {
158 let mut args = Vec::new();
159
160 if self.short {
161 args.push("--short".to_string());
162 }
163
164 if let Some(format) = self.format {
165 args.push("--format".to_string());
166 args.push(format.to_string());
167 }
168
169 args
170 }
171}
172
173impl ComposeVersionResult {
174 #[must_use]
176 pub fn success(&self) -> bool {
177 self.success
178 }
179
180 #[must_use]
182 pub fn version_info(&self) -> Option<&VersionInfo> {
183 self.version_info.as_ref()
184 }
185
186 #[must_use]
188 pub fn version_string(&self) -> Option<String> {
189 if let Some(info) = &self.version_info {
190 Some(info.version.clone())
191 } else {
192 self.stdout
194 .lines()
195 .find(|line| line.contains("version"))
196 .map(|line| line.trim().to_string())
197 }
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn test_compose_version_basic() {
207 let cmd = ComposeVersionCommand::new();
208 let args = cmd.build_subcommand_args();
209 assert!(args.is_empty());
210
211 let full_args = ComposeCommand::build_command_args(&cmd);
212 assert_eq!(full_args[0], "compose");
213 assert!(full_args.contains(&"version".to_string()));
214 }
215
216 #[test]
217 fn test_compose_version_with_format() {
218 let cmd = ComposeVersionCommand::new().format_json();
219 let args = cmd.build_subcommand_args();
220 assert!(args.contains(&"--format".to_string()));
221 assert!(args.contains(&"json".to_string()));
222 }
223
224 #[test]
225 fn test_compose_version_with_short() {
226 let cmd = ComposeVersionCommand::new().short();
227 let args = cmd.build_subcommand_args();
228 assert!(args.contains(&"--short".to_string()));
229 }
230
231 #[test]
232 fn test_compose_version_pretty_format() {
233 let cmd = ComposeVersionCommand::new().format_pretty();
234 let args = cmd.build_subcommand_args();
235 assert!(args.contains(&"--format".to_string()));
236 assert!(args.contains(&"pretty".to_string()));
237 }
238
239 #[test]
240 fn test_compose_version_all_options() {
241 let cmd = ComposeVersionCommand::new().format_json().short();
242
243 let args = cmd.build_subcommand_args();
244 assert!(args.contains(&"--short".to_string()));
245 assert!(args.contains(&"--format".to_string()));
246 assert!(args.contains(&"json".to_string()));
247 }
248
249 #[test]
250 fn test_version_format_display() {
251 assert_eq!(VersionFormat::Pretty.to_string(), "pretty");
252 assert_eq!(VersionFormat::Json.to_string(), "json");
253 }
254
255 #[test]
256 fn test_compose_config_integration() {
257 let cmd = ComposeVersionCommand::new()
258 .file("docker-compose.yml")
259 .project_name("myapp")
260 .format_json();
261
262 let args = ComposeCommand::build_command_args(&cmd);
263 assert!(args.contains(&"--file".to_string()));
264 assert!(args.contains(&"docker-compose.yml".to_string()));
265 assert!(args.contains(&"--project-name".to_string()));
266 assert!(args.contains(&"myapp".to_string()));
267 assert!(args.contains(&"--format".to_string()));
268 assert!(args.contains(&"json".to_string()));
269 }
270}