docker_wrapper/command/compose/
config.rs1use crate::command::{CommandExecutor, ComposeCommand, ComposeConfig, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6
7#[derive(Debug, Clone)]
9#[allow(clippy::struct_excessive_bools)] pub struct ComposeConfigCommand {
11 pub executor: CommandExecutor,
13 pub config: ComposeConfig,
15 pub format: Option<ConfigFormat>,
17 pub resolve_image_digests: bool,
19 pub no_interpolate: bool,
21 pub no_normalize: bool,
23 pub no_consistency: bool,
25 pub services: bool,
27 pub volumes: bool,
29 pub profiles: bool,
31 pub images: bool,
33 pub hash: Option<String>,
35 pub output: Option<String>,
37 pub quiet: bool,
39}
40
41#[derive(Debug, Clone, Copy)]
43pub enum ConfigFormat {
44 Yaml,
46 Json,
48}
49
50impl std::fmt::Display for ConfigFormat {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 match self {
53 Self::Yaml => write!(f, "yaml"),
54 Self::Json => write!(f, "json"),
55 }
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct ComposeConfigResult {
62 pub stdout: String,
64 pub stderr: String,
66 pub success: bool,
68 pub is_valid: bool,
70}
71
72impl ComposeConfigCommand {
73 #[must_use]
75 pub fn new() -> Self {
76 Self {
77 executor: CommandExecutor::new(),
78 config: ComposeConfig::new(),
79 format: None,
80 resolve_image_digests: false,
81 no_interpolate: false,
82 no_normalize: false,
83 no_consistency: false,
84 services: false,
85 volumes: false,
86 profiles: false,
87 images: false,
88 hash: None,
89 output: None,
90 quiet: false,
91 }
92 }
93
94 #[must_use]
96 pub fn format(mut self, format: ConfigFormat) -> Self {
97 self.format = Some(format);
98 self
99 }
100
101 #[must_use]
103 pub fn format_json(mut self) -> Self {
104 self.format = Some(ConfigFormat::Json);
105 self
106 }
107
108 #[must_use]
110 pub fn format_yaml(mut self) -> Self {
111 self.format = Some(ConfigFormat::Yaml);
112 self
113 }
114
115 #[must_use]
117 pub fn resolve_image_digests(mut self) -> Self {
118 self.resolve_image_digests = true;
119 self
120 }
121
122 #[must_use]
124 pub fn no_interpolate(mut self) -> Self {
125 self.no_interpolate = true;
126 self
127 }
128
129 #[must_use]
131 pub fn no_normalize(mut self) -> Self {
132 self.no_normalize = true;
133 self
134 }
135
136 #[must_use]
138 pub fn no_consistency(mut self) -> Self {
139 self.no_consistency = true;
140 self
141 }
142
143 #[must_use]
145 pub fn services(mut self) -> Self {
146 self.services = true;
147 self
148 }
149
150 #[must_use]
152 pub fn volumes(mut self) -> Self {
153 self.volumes = true;
154 self
155 }
156
157 #[must_use]
159 pub fn profiles(mut self) -> Self {
160 self.profiles = true;
161 self
162 }
163
164 #[must_use]
166 pub fn images(mut self) -> Self {
167 self.images = true;
168 self
169 }
170
171 #[must_use]
173 pub fn hash(mut self, hash: impl Into<String>) -> Self {
174 self.hash = Some(hash.into());
175 self
176 }
177
178 #[must_use]
180 pub fn output(mut self, output: impl Into<String>) -> Self {
181 self.output = Some(output.into());
182 self
183 }
184
185 #[must_use]
187 pub fn quiet(mut self) -> Self {
188 self.quiet = true;
189 self
190 }
191}
192
193impl Default for ComposeConfigCommand {
194 fn default() -> Self {
195 Self::new()
196 }
197}
198
199#[async_trait]
200impl DockerCommand for ComposeConfigCommand {
201 type Output = ComposeConfigResult;
202
203 fn get_executor(&self) -> &CommandExecutor {
204 &self.executor
205 }
206
207 fn get_executor_mut(&mut self) -> &mut CommandExecutor {
208 &mut self.executor
209 }
210
211 fn build_command_args(&self) -> Vec<String> {
212 <Self as ComposeCommand>::build_command_args(self)
214 }
215
216 async fn execute(&self) -> Result<Self::Output> {
217 let args = <Self as ComposeCommand>::build_command_args(self);
218 let output = self.execute_command(args).await?;
219
220 Ok(ComposeConfigResult {
221 stdout: output.stdout.clone(),
222 stderr: output.stderr,
223 success: output.success,
224 is_valid: output.success && !output.stdout.is_empty(),
225 })
226 }
227}
228
229impl ComposeCommand for ComposeConfigCommand {
230 fn get_config(&self) -> &ComposeConfig {
231 &self.config
232 }
233
234 fn get_config_mut(&mut self) -> &mut ComposeConfig {
235 &mut self.config
236 }
237
238 fn subcommand(&self) -> &'static str {
239 "config"
240 }
241
242 fn build_subcommand_args(&self) -> Vec<String> {
243 let mut args = Vec::new();
244
245 if let Some(format) = self.format {
246 args.push("--format".to_string());
247 args.push(format.to_string());
248 }
249
250 if self.resolve_image_digests {
251 args.push("--resolve-image-digests".to_string());
252 }
253
254 if self.no_interpolate {
255 args.push("--no-interpolate".to_string());
256 }
257
258 if self.no_normalize {
259 args.push("--no-normalize".to_string());
260 }
261
262 if self.no_consistency {
263 args.push("--no-consistency".to_string());
264 }
265
266 if self.services {
267 args.push("--services".to_string());
268 }
269
270 if self.volumes {
271 args.push("--volumes".to_string());
272 }
273
274 if self.profiles {
275 args.push("--profiles".to_string());
276 }
277
278 if self.images {
279 args.push("--images".to_string());
280 }
281
282 if let Some(ref hash) = self.hash {
283 args.push("--hash".to_string());
284 args.push(hash.clone());
285 }
286
287 if let Some(ref output) = self.output {
288 args.push("--output".to_string());
289 args.push(output.clone());
290 }
291
292 if self.quiet {
293 args.push("--quiet".to_string());
294 }
295
296 args
297 }
298}
299
300impl ComposeConfigResult {
301 #[must_use]
303 pub fn success(&self) -> bool {
304 self.success
305 }
306
307 #[must_use]
309 pub fn is_valid(&self) -> bool {
310 self.is_valid
311 }
312
313 #[must_use]
315 pub fn config_output(&self) -> &str {
316 &self.stdout
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn test_compose_config_basic() {
326 let cmd = ComposeConfigCommand::new();
327 let args = cmd.build_subcommand_args();
328 assert!(args.is_empty());
329
330 let full_args = ComposeCommand::build_command_args(&cmd);
331 assert_eq!(full_args[0], "compose");
332 assert!(full_args.contains(&"config".to_string()));
333 }
334
335 #[test]
336 fn test_compose_config_with_format() {
337 let cmd = ComposeConfigCommand::new().format_json();
338 let args = cmd.build_subcommand_args();
339 assert!(args.contains(&"--format".to_string()));
340 assert!(args.contains(&"json".to_string()));
341 }
342
343 #[test]
344 fn test_compose_config_with_flags() {
345 let cmd = ComposeConfigCommand::new()
346 .resolve_image_digests()
347 .no_interpolate()
348 .services()
349 .quiet();
350
351 let args = cmd.build_subcommand_args();
352 assert!(args.contains(&"--resolve-image-digests".to_string()));
353 assert!(args.contains(&"--no-interpolate".to_string()));
354 assert!(args.contains(&"--services".to_string()));
355 assert!(args.contains(&"--quiet".to_string()));
356 }
357
358 #[test]
359 fn test_compose_config_show_options() {
360 let cmd = ComposeConfigCommand::new().volumes().profiles().images();
361
362 let args = cmd.build_subcommand_args();
363 assert!(args.contains(&"--volumes".to_string()));
364 assert!(args.contains(&"--profiles".to_string()));
365 assert!(args.contains(&"--images".to_string()));
366 }
367
368 #[test]
369 fn test_compose_config_with_hash_and_output() {
370 let cmd = ComposeConfigCommand::new()
371 .hash("web=sha256:123")
372 .output("output.yml");
373
374 let args = cmd.build_subcommand_args();
375 assert!(args.contains(&"--hash".to_string()));
376 assert!(args.contains(&"web=sha256:123".to_string()));
377 assert!(args.contains(&"--output".to_string()));
378 assert!(args.contains(&"output.yml".to_string()));
379 }
380
381 #[test]
382 fn test_config_format_display() {
383 assert_eq!(ConfigFormat::Yaml.to_string(), "yaml");
384 assert_eq!(ConfigFormat::Json.to_string(), "json");
385 }
386
387 #[test]
388 fn test_compose_config_integration() {
389 let cmd = ComposeConfigCommand::new()
390 .file("docker-compose.yml")
391 .project_name("myapp")
392 .format_json()
393 .services();
394
395 let args = ComposeCommand::build_command_args(&cmd);
396 assert!(args.contains(&"--file".to_string()));
397 assert!(args.contains(&"docker-compose.yml".to_string()));
398 assert!(args.contains(&"--project-name".to_string()));
399 assert!(args.contains(&"myapp".to_string()));
400 assert!(args.contains(&"--format".to_string()));
401 assert!(args.contains(&"json".to_string()));
402 assert!(args.contains(&"--services".to_string()));
403 }
404}