docker_wrapper/compose/
config.rs1use crate::compose::{ComposeCommandV2 as ComposeCommand, ComposeConfig};
4use crate::error::Result;
5use async_trait::async_trait;
6
7#[derive(Debug, Clone, Default)]
11#[allow(clippy::struct_excessive_bools)]
12pub struct ComposeConfigCommand {
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 ConfigFormat {
51 #[must_use]
53 pub fn as_arg(&self) -> &str {
54 match self {
55 Self::Yaml => "yaml",
56 Self::Json => "json",
57 }
58 }
59}
60
61#[derive(Debug, Clone)]
63pub struct ConfigResult {
64 pub config: String,
66 pub is_valid: bool,
68}
69
70impl ComposeConfigCommand {
71 #[must_use]
73 pub fn new() -> Self {
74 Self::default()
75 }
76
77 #[must_use]
79 pub fn file<P: Into<std::path::PathBuf>>(mut self, file: P) -> Self {
80 self.config.files.push(file.into());
81 self
82 }
83
84 #[must_use]
86 pub fn project_name(mut self, name: impl Into<String>) -> Self {
87 self.config.project_name = Some(name.into());
88 self
89 }
90
91 #[must_use]
93 pub fn format(mut self, format: ConfigFormat) -> Self {
94 self.format = Some(format);
95 self
96 }
97
98 #[must_use]
100 pub fn resolve_image_digests(mut self) -> Self {
101 self.resolve_image_digests = true;
102 self
103 }
104
105 #[must_use]
107 pub fn no_interpolate(mut self) -> Self {
108 self.no_interpolate = true;
109 self
110 }
111
112 #[must_use]
114 pub fn no_normalize(mut self) -> Self {
115 self.no_normalize = true;
116 self
117 }
118
119 #[must_use]
121 pub fn no_consistency(mut self) -> Self {
122 self.no_consistency = true;
123 self
124 }
125
126 #[must_use]
128 pub fn services(mut self) -> Self {
129 self.services = true;
130 self
131 }
132
133 #[must_use]
135 pub fn volumes(mut self) -> Self {
136 self.volumes = true;
137 self
138 }
139
140 #[must_use]
142 pub fn profiles(mut self) -> Self {
143 self.profiles = true;
144 self
145 }
146
147 #[must_use]
149 pub fn images(mut self) -> Self {
150 self.images = true;
151 self
152 }
153
154 #[must_use]
156 pub fn hash(mut self, hash: impl Into<String>) -> Self {
157 self.hash = Some(hash.into());
158 self
159 }
160
161 #[must_use]
163 pub fn output(mut self, path: impl Into<String>) -> Self {
164 self.output = Some(path.into());
165 self
166 }
167
168 #[must_use]
170 pub fn quiet(mut self) -> Self {
171 self.quiet = true;
172 self
173 }
174
175 fn build_args(&self) -> Vec<String> {
176 let mut args = vec!["config".to_string()];
177
178 if let Some(format) = &self.format {
180 args.push("--format".to_string());
181 args.push(format.as_arg().to_string());
182 }
183
184 if self.resolve_image_digests {
186 args.push("--resolve-image-digests".to_string());
187 }
188 if self.no_interpolate {
189 args.push("--no-interpolate".to_string());
190 }
191 if self.no_normalize {
192 args.push("--no-normalize".to_string());
193 }
194 if self.no_consistency {
195 args.push("--no-consistency".to_string());
196 }
197 if self.services {
198 args.push("--services".to_string());
199 }
200 if self.volumes {
201 args.push("--volumes".to_string());
202 }
203 if self.profiles {
204 args.push("--profiles".to_string());
205 }
206 if self.images {
207 args.push("--images".to_string());
208 }
209 if self.quiet {
210 args.push("--quiet".to_string());
211 }
212
213 if let Some(hash) = &self.hash {
215 args.push("--hash".to_string());
216 args.push(hash.clone());
217 }
218
219 if let Some(output) = &self.output {
221 args.push("--output".to_string());
222 args.push(output.clone());
223 }
224
225 args
226 }
227}
228
229#[async_trait]
230impl ComposeCommand for ComposeConfigCommand {
231 type Output = ConfigResult;
232
233 fn get_config(&self) -> &ComposeConfig {
234 &self.config
235 }
236
237 fn get_config_mut(&mut self) -> &mut ComposeConfig {
238 &mut self.config
239 }
240
241 async fn execute_compose(&self, args: Vec<String>) -> Result<Self::Output> {
242 let output = self.execute_compose_command(args).await?;
243
244 Ok(ConfigResult {
245 config: output.stdout,
246 is_valid: output.success,
247 })
248 }
249
250 async fn execute(&self) -> Result<Self::Output> {
251 let args = self.build_args();
252 self.execute_compose(args).await
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_config_command_basic() {
262 let cmd = ComposeConfigCommand::new();
263 let args = cmd.build_args();
264 assert_eq!(args[0], "config");
265 }
266
267 #[test]
268 fn test_config_command_with_format() {
269 let cmd = ComposeConfigCommand::new().format(ConfigFormat::Json);
270 let args = cmd.build_args();
271 assert!(args.contains(&"--format".to_string()));
272 assert!(args.contains(&"json".to_string()));
273 }
274
275 #[test]
276 fn test_config_command_with_flags() {
277 let cmd = ComposeConfigCommand::new()
278 .resolve_image_digests()
279 .no_interpolate()
280 .services()
281 .quiet();
282 let args = cmd.build_args();
283 assert!(args.contains(&"--resolve-image-digests".to_string()));
284 assert!(args.contains(&"--no-interpolate".to_string()));
285 assert!(args.contains(&"--services".to_string()));
286 assert!(args.contains(&"--quiet".to_string()));
287 }
288}