docker_wrapper/command/compose/
convert.rs

1//! Docker Compose convert 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 convert command builder
8#[derive(Debug, Clone)]
9pub struct ComposeConvertCommand {
10    /// Base command executor
11    pub executor: CommandExecutor,
12    /// Base compose configuration
13    pub config: ComposeConfig,
14    /// Output format
15    pub format: Option<ConvertFormat>,
16    /// Output file path
17    pub output: Option<String>,
18}
19
20/// Convert output format
21#[derive(Debug, Clone, Copy)]
22pub enum ConvertFormat {
23    /// YAML format (default)
24    Yaml,
25    /// JSON format
26    Json,
27}
28
29impl std::fmt::Display for ConvertFormat {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            Self::Yaml => write!(f, "yaml"),
33            Self::Json => write!(f, "json"),
34        }
35    }
36}
37
38/// Result from compose convert command
39#[derive(Debug, Clone)]
40pub struct ComposeConvertResult {
41    /// Raw stdout output
42    pub stdout: String,
43    /// Raw stderr output
44    pub stderr: String,
45    /// Success status
46    pub success: bool,
47    /// Converted configuration
48    pub converted_config: String,
49}
50
51impl ComposeConvertCommand {
52    /// Create a new compose convert command
53    #[must_use]
54    pub fn new() -> Self {
55        Self {
56            executor: CommandExecutor::new(),
57            config: ComposeConfig::new(),
58            format: None,
59            output: None,
60        }
61    }
62
63    /// Set output format
64    #[must_use]
65    pub fn format(mut self, format: ConvertFormat) -> Self {
66        self.format = Some(format);
67        self
68    }
69
70    /// Set output format to JSON
71    #[must_use]
72    pub fn format_json(mut self) -> Self {
73        self.format = Some(ConvertFormat::Json);
74        self
75    }
76
77    /// Set output format to YAML
78    #[must_use]
79    pub fn format_yaml(mut self) -> Self {
80        self.format = Some(ConvertFormat::Yaml);
81        self
82    }
83
84    /// Set output file path
85    #[must_use]
86    pub fn output(mut self, output: impl Into<String>) -> Self {
87        self.output = Some(output.into());
88        self
89    }
90}
91
92impl Default for ComposeConvertCommand {
93    fn default() -> Self {
94        Self::new()
95    }
96}
97
98#[async_trait]
99impl DockerCommand for ComposeConvertCommand {
100    type Output = ComposeConvertResult;
101
102    fn get_executor(&self) -> &CommandExecutor {
103        &self.executor
104    }
105
106    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
107        &mut self.executor
108    }
109
110    fn build_command_args(&self) -> Vec<String> {
111        <Self as ComposeCommand>::build_command_args(self)
112    }
113
114    async fn execute(&self) -> Result<Self::Output> {
115        let args = <Self as ComposeCommand>::build_command_args(self);
116        let output = self.execute_command(args).await?;
117
118        Ok(ComposeConvertResult {
119            stdout: output.stdout.clone(),
120            stderr: output.stderr,
121            success: output.success,
122            converted_config: output.stdout,
123        })
124    }
125}
126
127impl ComposeCommand for ComposeConvertCommand {
128    fn get_config(&self) -> &ComposeConfig {
129        &self.config
130    }
131
132    fn get_config_mut(&mut self) -> &mut ComposeConfig {
133        &mut self.config
134    }
135
136    fn subcommand(&self) -> &'static str {
137        "convert"
138    }
139
140    fn build_subcommand_args(&self) -> Vec<String> {
141        let mut args = Vec::new();
142
143        if let Some(format) = self.format {
144            args.push("--format".to_string());
145            args.push(format.to_string());
146        }
147
148        if let Some(ref output) = self.output {
149            args.push("--output".to_string());
150            args.push(output.clone());
151        }
152
153        args
154    }
155}
156
157impl ComposeConvertResult {
158    /// Check if the command was successful
159    #[must_use]
160    pub fn success(&self) -> bool {
161        self.success
162    }
163
164    /// Get the converted configuration
165    #[must_use]
166    pub fn converted_config(&self) -> &str {
167        &self.converted_config
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn test_compose_convert_basic() {
177        let cmd = ComposeConvertCommand::new();
178        let args = cmd.build_subcommand_args();
179        assert!(args.is_empty());
180
181        let full_args = ComposeCommand::build_command_args(&cmd);
182        assert_eq!(full_args[0], "compose");
183        assert!(full_args.contains(&"convert".to_string()));
184    }
185
186    #[test]
187    fn test_compose_convert_with_format() {
188        let cmd = ComposeConvertCommand::new().format_json();
189        let args = cmd.build_subcommand_args();
190        assert!(args.contains(&"--format".to_string()));
191        assert!(args.contains(&"json".to_string()));
192    }
193
194    #[test]
195    fn test_compose_convert_with_output() {
196        let cmd = ComposeConvertCommand::new().output("output.yml");
197        let args = cmd.build_subcommand_args();
198        assert!(args.contains(&"--output".to_string()));
199        assert!(args.contains(&"output.yml".to_string()));
200    }
201}