docker_compose_types/
lib.rs

1use derive_builder::*;
2#[cfg(feature = "indexmap")]
3use indexmap::IndexMap;
4use serde::{Deserialize, Deserializer, Serialize};
5#[cfg(feature = "yml")]
6use serde_yml as serde_yaml;
7#[cfg(not(feature = "indexmap"))]
8use std::collections::HashMap;
9use std::convert::TryFrom;
10use std::fmt;
11use std::str::FromStr;
12
13use serde_yaml::Value;
14
15#[allow(clippy::large_enum_variant)]
16#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
17#[serde(untagged)]
18pub enum ComposeFile {
19    V2Plus(Compose),
20    #[cfg(feature = "indexmap")]
21    V1(IndexMap<String, Service>),
22    #[cfg(not(feature = "indexmap"))]
23    V1(HashMap<String, Service>),
24    Single(SingleService),
25}
26
27#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
28pub struct SingleService {
29    pub service: Service,
30}
31
32#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
33pub struct Compose {
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub version: Option<String>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub name: Option<String>,
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub includes: Option<Includes>,
40    #[serde(default, skip_serializing_if = "Services::is_empty")]
41    pub services: Services,
42    #[serde(default, skip_serializing_if = "TopLevelVolumes::is_empty")]
43    pub volumes: TopLevelVolumes,
44    #[serde(default, skip_serializing_if = "ComposeNetworks::is_empty")]
45    pub networks: ComposeNetworks,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub service: Option<Service>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub secrets: Option<ComposeSecrets>,
50    #[cfg(feature = "indexmap")]
51    #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
52    pub extensions: IndexMap<Extension, Value>,
53    #[cfg(not(feature = "indexmap"))]
54    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
55    pub extensions: HashMap<Extension, Value>,
56}
57
58impl Compose {
59    pub fn new() -> Self {
60        Default::default()
61    }
62}
63
64#[derive(Builder, Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
65#[builder(setter(into), default)]
66pub struct Include {
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub project_directory: Option<String>,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub path: Option<String>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub env_file: Option<EnvFile>,
73}
74
75#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
76#[serde(untagged)]
77pub enum Includes {
78    Short(Vec<String>),
79    Long(Vec<Include>),
80}
81
82impl Includes {
83    pub fn is_empty(&self) -> bool {
84        match self {
85            Includes::Short(xs) => xs.is_empty(),
86            Includes::Long(xs) => xs.is_empty(),
87        }
88    }
89}
90
91#[derive(Builder, Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
92#[builder(setter(into), default)]
93pub struct Service {
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub hostname: Option<String>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub domainname: Option<String>,
98    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
99    pub privileged: bool,
100    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
101    pub read_only: bool,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub healthcheck: Option<Healthcheck>,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub deploy: Option<Deploy>,
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub image: Option<String>,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub container_name: Option<String>,
110    #[serde(skip_serializing_if = "Option::is_none", rename = "build")]
111    pub build_: Option<BuildStep>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub pid: Option<String>,
114    #[serde(default, skip_serializing_if = "Ports::is_empty")]
115    pub ports: Ports,
116    #[serde(default, skip_serializing_if = "Environment::is_empty")]
117    pub environment: Environment,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub network_mode: Option<String>,
120    #[serde(default, skip_serializing_if = "Vec::is_empty")]
121    pub devices: Vec<String>,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub restart: Option<String>,
124    #[serde(default, skip_serializing_if = "Labels::is_empty")]
125    pub labels: Labels,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub tmpfs: Option<Tmpfs>,
128    #[serde(default, skip_serializing_if = "Ulimits::is_empty")]
129    pub ulimits: Ulimits,
130    #[serde(default, skip_serializing_if = "Vec::is_empty")]
131    pub volumes: Vec<Volumes>,
132    #[serde(default, skip_serializing_if = "Networks::is_empty")]
133    pub networks: Networks,
134    #[serde(default, skip_serializing_if = "Vec::is_empty")]
135    pub cap_add: Vec<String>,
136    #[serde(default, skip_serializing_if = "Vec::is_empty")]
137    pub cap_drop: Vec<String>,
138    #[serde(default, skip_serializing_if = "DependsOnOptions::is_empty")]
139    pub depends_on: DependsOnOptions,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub command: Option<Command>,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub entrypoint: Option<Entrypoint>,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub env_file: Option<EnvFile>,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub stop_grace_period: Option<String>,
148    #[serde(default, skip_serializing_if = "Vec::is_empty")]
149    pub profiles: Vec<String>,
150    #[serde(default, skip_serializing_if = "Vec::is_empty")]
151    pub links: Vec<String>,
152    #[serde(default, skip_serializing_if = "Vec::is_empty")]
153    pub dns: Vec<String>,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub ipc: Option<String>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub net: Option<String>,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub stop_signal: Option<String>,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub user: Option<String>,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub userns_mode: Option<String>,
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub working_dir: Option<String>,
166    #[serde(default, skip_serializing_if = "Vec::is_empty")]
167    pub expose: Vec<String>,
168    #[serde(default, skip_serializing_if = "Vec::is_empty")]
169    pub volumes_from: Vec<String>,
170    #[cfg(feature = "indexmap")]
171    #[serde(
172        default,
173        deserialize_with = "de_extends_indexmap",
174        skip_serializing_if = "IndexMap::is_empty"
175    )]
176    pub extends: IndexMap<String, String>,
177    #[cfg(not(feature = "indexmap"))]
178    #[serde(
179        default,
180        deserialize_with = "de_extends_hashmap",
181        skip_serializing_if = "HashMap::is_empty"
182    )]
183    pub extends: HashMap<String, String>,
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub logging: Option<LoggingParameters>,
186    #[serde(default, skip_serializing_if = "is_zero")]
187    pub scale: i64,
188    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
189    pub init: bool,
190    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
191    pub stdin_open: bool,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub shm_size: Option<String>,
194    #[cfg(feature = "indexmap")]
195    #[serde(flatten, skip_serializing_if = "IndexMap::is_empty")]
196    pub extensions: IndexMap<Extension, Value>,
197    #[cfg(not(feature = "indexmap"))]
198    #[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
199    pub extensions: HashMap<Extension, Value>,
200    #[serde(default, skip_serializing_if = "Vec::is_empty")]
201    pub extra_hosts: Vec<String>,
202    #[serde(default, skip_serializing_if = "Vec::is_empty")]
203    pub group_add: Vec<Group>,
204    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
205    pub tty: bool,
206    #[serde(default, skip_serializing_if = "SysCtls::is_empty")]
207    pub sysctls: SysCtls,
208    #[serde(default, skip_serializing_if = "Vec::is_empty")]
209    pub security_opt: Vec<String>,
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub secrets: Option<Secrets>,
212    #[serde(default, skip_serializing_if = "Option::is_none")]
213    pub pull_policy: Option<PullPolicy>,
214    #[serde(default, skip_serializing_if = "Option::is_none")]
215    pub cgroup_parent: Option<String>,
216    #[serde(default, skip_serializing_if = "Option::is_none")]
217    pub mem_limit: Option<String>,
218    #[serde(default, skip_serializing_if = "Option::is_none")]
219    pub mem_reservation: Option<String>,
220    #[serde(default, skip_serializing_if = "Option::is_none")]
221    pub mem_swappiness: Option<u16>,
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub runtime: Option<String>,
224}
225
226#[cfg(feature = "indexmap")]
227fn de_extends_indexmap<'de, D>(deserializer: D) -> Result<IndexMap<String, String>, D::Error>
228where
229    D: Deserializer<'de>,
230{
231    let value = Value::deserialize(deserializer)?;
232    if let Some(value_str) = value.as_str() {
233        let mut map = IndexMap::new();
234        map.insert("service".to_string(), value_str.to_string());
235        return Ok(map);
236    }
237
238    if let Some(value_map) = value.as_mapping() {
239        let mut map = IndexMap::new();
240        for (k, v) in value_map {
241            if !k.is_string() || !v.is_string() {
242                return Err(serde::de::Error::custom(
243                    "extends must must have string type for both Keys and Values".to_string(),
244                ));
245            }
246            //Should be safe due to previous check
247            map.insert(
248                k.as_str().unwrap().to_string(),
249                v.as_str().unwrap().to_string(),
250            );
251        }
252        return Ok(map);
253    }
254
255    Err(serde::de::Error::custom(
256        "extends must either be a map or a string".to_string(),
257    ))
258}
259
260#[cfg(not(feature = "indexmap"))]
261fn de_extends_hashmap<'de, D>(deserializer: D) -> Result<HashMap<String, String>, D::Error>
262where
263    D: Deserializer<'de>,
264{
265    let value = Value::deserialize(deserializer)?;
266    if let Some(value_str) = value.as_str() {
267        let mut map = HashMap::new();
268        map.insert("service".to_string(), value_str.to_string());
269        return Ok(map);
270    }
271
272    if let Some(value_map) = value.as_mapping() {
273        let mut map = HashMap::new();
274        for (k, v) in value_map {
275            if !k.is_string() || !v.is_string() {
276                return Err(serde::de::Error::custom(
277                    "extends must must have string type for both Keys and Values".to_string(),
278                ));
279            }
280            //Should be safe due to previous check
281            map.insert(
282                k.as_str().unwrap().to_string(),
283                v.as_str().unwrap().to_string(),
284            );
285        }
286        return Ok(map);
287    }
288
289    Err(serde::de::Error::custom(
290        "extends must either be a map or a string".to_string(),
291    ))
292}
293
294impl Service {
295    pub fn image(&self) -> &str {
296        self.image.as_deref().unwrap_or_default()
297    }
298
299    pub fn network_mode(&self) -> &str {
300        self.network_mode.as_deref().unwrap_or_default()
301    }
302}
303
304#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
305#[serde(untagged)]
306pub enum EnvFile {
307    Simple(String),
308    List(Vec<String>),
309}
310
311#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
312#[serde(untagged)]
313pub enum DependsOnOptions {
314    Simple(Vec<String>),
315    #[cfg(feature = "indexmap")]
316    Conditional(IndexMap<String, DependsCondition>),
317    #[cfg(not(feature = "indexmap"))]
318    Conditional(HashMap<String, DependsCondition>),
319}
320
321impl Default for DependsOnOptions {
322    fn default() -> Self {
323        Self::Simple(Vec::new())
324    }
325}
326
327impl DependsOnOptions {
328    pub fn is_empty(&self) -> bool {
329        match self {
330            Self::Simple(v) => v.is_empty(),
331            Self::Conditional(m) => m.is_empty(),
332        }
333    }
334}
335
336#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
337pub struct DependsCondition {
338    pub condition: String,
339}
340
341#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
342pub struct LoggingParameters {
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub driver: Option<String>,
345    #[cfg(feature = "indexmap")]
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub options: Option<IndexMap<String, SingleValue>>,
348    #[cfg(not(feature = "indexmap"))]
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub options: Option<HashMap<String, SingleValue>>,
351}
352
353#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
354#[serde(untagged)]
355pub enum Ports {
356    Short(Vec<String>),
357    Long(Vec<Port>),
358}
359
360impl Default for Ports {
361    fn default() -> Self {
362        Self::Short(Vec::default())
363    }
364}
365
366impl Ports {
367    pub fn is_empty(&self) -> bool {
368        match self {
369            Self::Short(v) => v.is_empty(),
370            Self::Long(v) => v.is_empty(),
371        }
372    }
373}
374
375#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
376pub struct Port {
377    pub target: u16,
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub host_ip: Option<String>,
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub published: Option<PublishedPort>,
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub protocol: Option<String>,
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub mode: Option<String>,
386}
387
388#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
389#[serde(untagged)]
390pub enum PublishedPort {
391    Single(u16),
392    Range(String),
393}
394
395#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
396#[serde(untagged)]
397pub enum Environment {
398    List(Vec<String>),
399    #[cfg(feature = "indexmap")]
400    KvPair(IndexMap<String, Option<SingleValue>>),
401    #[cfg(not(feature = "indexmap"))]
402    KvPair(HashMap<String, Option<SingleValue>>),
403}
404
405impl Default for Environment {
406    fn default() -> Self {
407        Self::List(Vec::new())
408    }
409}
410
411impl Environment {
412    pub fn is_empty(&self) -> bool {
413        match self {
414            Self::List(v) => v.is_empty(),
415            Self::KvPair(m) => m.is_empty(),
416        }
417    }
418}
419
420#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default, Ord, PartialOrd)]
421#[serde(try_from = "String")]
422pub struct Extension(String);
423
424impl FromStr for Extension {
425    type Err = ExtensionParseError;
426
427    fn from_str(s: &str) -> Result<Self, Self::Err> {
428        let owned = s.to_owned();
429        Extension::try_from(owned)
430    }
431}
432
433impl TryFrom<String> for Extension {
434    type Error = ExtensionParseError;
435
436    fn try_from(s: String) -> Result<Self, Self::Error> {
437        if s.starts_with("x-") {
438            Ok(Self(s))
439        } else {
440            Err(ExtensionParseError(s))
441        }
442    }
443}
444
445/// The result of a failed TryFrom<String> conversion for [`Extension`]
446///
447/// Contains the string that was being converted
448#[derive(Clone, Debug, Eq, PartialEq, Hash)]
449pub struct ExtensionParseError(pub String);
450
451impl fmt::Display for ExtensionParseError {
452    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
453        write!(f, "unknown attribute {:?}, extensions must start with 'x-' (see https://docs.docker.com/compose/compose-file/#extension)", self.0)
454    }
455}
456
457impl std::error::Error for ExtensionParseError {}
458
459#[cfg(feature = "indexmap")]
460#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
461pub struct Services(pub IndexMap<String, Option<Service>>);
462#[cfg(not(feature = "indexmap"))]
463#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
464pub struct Services(pub HashMap<String, Option<Service>>);
465
466impl Services {
467    pub fn is_empty(&self) -> bool {
468        self.0.is_empty()
469    }
470}
471
472#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
473#[serde(untagged)]
474pub enum Labels {
475    List(Vec<String>),
476    #[cfg(feature = "indexmap")]
477    Map(IndexMap<String, String>),
478    #[cfg(not(feature = "indexmap"))]
479    Map(HashMap<String, String>),
480}
481
482impl Default for Labels {
483    fn default() -> Self {
484        Self::List(Vec::new())
485    }
486}
487
488impl Labels {
489    pub fn is_empty(&self) -> bool {
490        match self {
491            Self::List(v) => v.is_empty(),
492            Self::Map(m) => m.is_empty(),
493        }
494    }
495}
496
497#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
498#[serde(untagged)]
499pub enum Tmpfs {
500    Simple(String),
501    List(Vec<String>),
502}
503
504#[cfg(feature = "indexmap")]
505#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
506pub struct Ulimits(pub IndexMap<String, Ulimit>);
507#[cfg(not(feature = "indexmap"))]
508#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
509pub struct Ulimits(pub HashMap<String, Ulimit>);
510
511impl Ulimits {
512    pub fn is_empty(&self) -> bool {
513        self.0.is_empty()
514    }
515}
516
517#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
518#[serde(untagged)]
519pub enum Ulimit {
520    Single(i64),
521    SoftHard { soft: i64, hard: i64 },
522}
523
524#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
525#[serde(untagged)]
526pub enum Networks {
527    Simple(Vec<String>),
528    Advanced(AdvancedNetworks),
529}
530
531impl Default for Networks {
532    fn default() -> Self {
533        Self::Simple(Vec::new())
534    }
535}
536
537impl Networks {
538    pub fn is_empty(&self) -> bool {
539        match self {
540            Self::Simple(n) => n.is_empty(),
541            Self::Advanced(n) => n.0.is_empty(),
542        }
543    }
544}
545
546#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
547#[serde(untagged)]
548pub enum BuildStep {
549    Simple(String),
550    Advanced(AdvancedBuildStep),
551}
552
553#[derive(Builder, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Default)]
554#[serde(deny_unknown_fields)]
555#[builder(setter(into), default)]
556pub struct AdvancedBuildStep {
557    pub context: String,
558    #[serde(default, skip_serializing_if = "Option::is_none")]
559    pub dockerfile: Option<String>,
560    #[serde(default, skip_serializing_if = "Option::is_none")]
561    pub args: Option<BuildArgs>,
562    #[serde(default, skip_serializing_if = "Option::is_none")]
563    pub shm_size: Option<u64>,
564    #[serde(default, skip_serializing_if = "Option::is_none")]
565    pub target: Option<String>,
566    #[serde(default, skip_serializing_if = "Option::is_none")]
567    pub network: Option<String>,
568    #[serde(default, skip_serializing_if = "Vec::is_empty")]
569    pub cache_from: Vec<String>,
570    #[serde(default, skip_serializing_if = "Labels::is_empty")]
571    pub labels: Labels,
572}
573
574#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
575#[serde(untagged)]
576pub enum BuildArgs {
577    Simple(String),
578    List(Vec<String>),
579    #[cfg(feature = "indexmap")]
580    KvPair(IndexMap<String, String>),
581    #[cfg(not(feature = "indexmap"))]
582    KvPair(HashMap<String, String>),
583}
584
585#[cfg(feature = "indexmap")]
586#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
587pub struct AdvancedNetworks(pub IndexMap<String, MapOrEmpty<AdvancedNetworkSettings>>);
588#[cfg(not(feature = "indexmap"))]
589#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
590pub struct AdvancedNetworks(pub HashMap<String, MapOrEmpty<AdvancedNetworkSettings>>);
591
592#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, Hash)]
593#[serde(deny_unknown_fields)]
594pub struct AdvancedNetworkSettings {
595    #[serde(skip_serializing_if = "Option::is_none")]
596    pub ipv4_address: Option<String>,
597    #[serde(skip_serializing_if = "Option::is_none")]
598    pub ipv6_address: Option<String>,
599    #[serde(default, skip_serializing_if = "Vec::is_empty")]
600    pub aliases: Vec<String>,
601}
602
603#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
604#[serde(untagged)]
605pub enum SysCtls {
606    List(Vec<String>),
607    #[cfg(feature = "indexmap")]
608    Map(IndexMap<String, Option<SingleValue>>),
609    #[cfg(not(feature = "indexmap"))]
610    Map(HashMap<String, Option<SingleValue>>),
611}
612
613impl Default for SysCtls {
614    fn default() -> Self {
615        Self::List(Vec::new())
616    }
617}
618
619impl SysCtls {
620    pub fn is_empty(&self) -> bool {
621        match self {
622            Self::List(v) => v.is_empty(),
623            Self::Map(m) => m.is_empty(),
624        }
625    }
626}
627
628#[cfg(feature = "indexmap")]
629#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
630pub struct TopLevelVolumes(pub IndexMap<String, MapOrEmpty<ComposeVolume>>);
631#[cfg(not(feature = "indexmap"))]
632#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
633pub struct TopLevelVolumes(pub HashMap<String, MapOrEmpty<ComposeVolume>>);
634
635impl TopLevelVolumes {
636    pub fn is_empty(&self) -> bool {
637        self.0.is_empty()
638    }
639}
640
641#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
642pub struct ComposeVolume {
643    #[serde(skip_serializing_if = "Option::is_none")]
644    pub driver: Option<String>,
645    #[cfg(feature = "indexmap")]
646    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
647    pub driver_opts: IndexMap<String, Option<SingleValue>>,
648    #[cfg(not(feature = "indexmap"))]
649    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
650    pub driver_opts: HashMap<String, Option<SingleValue>>,
651    #[serde(skip_serializing_if = "Option::is_none")]
652    pub external: Option<ExternalVolume>,
653    #[serde(default, skip_serializing_if = "Labels::is_empty")]
654    pub labels: Labels,
655    #[serde(skip_serializing_if = "Option::is_none")]
656    pub name: Option<String>,
657}
658
659#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
660#[serde(untagged)]
661pub enum ExternalVolume {
662    Bool(bool),
663    Name { name: String },
664}
665
666#[cfg(feature = "indexmap")]
667#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
668pub struct ComposeNetworks(pub IndexMap<String, MapOrEmpty<NetworkSettings>>);
669
670#[cfg(not(feature = "indexmap"))]
671#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
672pub struct ComposeNetworks(pub HashMap<String, MapOrEmpty<NetworkSettings>>);
673
674impl ComposeNetworks {
675    pub fn is_empty(&self) -> bool {
676        self.0.is_empty()
677    }
678}
679
680#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
681#[serde(untagged)]
682pub enum ComposeNetwork {
683    Detailed(ComposeNetworkSettingDetails),
684    Bool(bool),
685}
686
687#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
688#[serde(deny_unknown_fields)]
689pub struct ComposeNetworkSettingDetails {
690    pub name: String,
691}
692
693#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
694#[serde(deny_unknown_fields)]
695pub struct ExternalNetworkSettingBool(bool);
696
697#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
698#[serde(deny_unknown_fields)]
699pub struct NetworkSettings {
700    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
701    pub attachable: bool,
702    #[serde(skip_serializing_if = "Option::is_none")]
703    pub driver: Option<String>,
704    #[cfg(feature = "indexmap")]
705    #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
706    pub driver_opts: IndexMap<String, Option<SingleValue>>,
707    #[cfg(not(feature = "indexmap"))]
708    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
709    pub driver_opts: HashMap<String, Option<SingleValue>>,
710    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
711    pub enable_ipv6: bool,
712    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
713    pub internal: bool,
714    #[serde(skip_serializing_if = "Option::is_none")]
715    pub external: Option<ComposeNetwork>,
716    #[serde(skip_serializing_if = "Option::is_none")]
717    pub ipam: Option<Ipam>,
718    #[serde(default, skip_serializing_if = "Labels::is_empty")]
719    pub labels: Labels,
720    #[serde(skip_serializing_if = "Option::is_none")]
721    pub name: Option<String>,
722}
723
724#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
725#[serde(deny_unknown_fields)]
726pub struct Ipam {
727    #[serde(skip_serializing_if = "Option::is_none")]
728    pub driver: Option<String>,
729    #[serde(default, skip_serializing_if = "Vec::is_empty")]
730    pub config: Vec<IpamConfig>,
731}
732
733#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
734#[serde(deny_unknown_fields)]
735pub struct IpamConfig {
736    pub subnet: String,
737    #[serde(skip_serializing_if = "Option::is_none")]
738    pub gateway: Option<String>,
739}
740
741#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
742#[serde(deny_unknown_fields)]
743pub struct Deploy {
744    #[serde(skip_serializing_if = "Option::is_none")]
745    pub mode: Option<String>,
746    #[serde(skip_serializing_if = "Option::is_none")]
747    pub replicas: Option<i64>,
748    #[serde(default, skip_serializing_if = "Vec::is_empty")]
749    pub labels: Vec<String>,
750    #[serde(skip_serializing_if = "Option::is_none")]
751    pub update_config: Option<UpdateConfig>,
752    #[serde(skip_serializing_if = "Option::is_none")]
753    pub resources: Option<Resources>,
754    #[serde(skip_serializing_if = "Option::is_none")]
755    pub restart_policy: Option<RestartPolicy>,
756    #[serde(skip_serializing_if = "Option::is_none")]
757    pub placement: Option<Placement>,
758}
759
760fn is_zero(val: &i64) -> bool {
761    *val == 0
762}
763
764#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
765#[serde(deny_unknown_fields)]
766pub struct Healthcheck {
767    #[serde(skip_serializing_if = "Option::is_none")]
768    pub test: Option<HealthcheckTest>,
769    #[serde(skip_serializing_if = "Option::is_none")]
770    pub interval: Option<String>,
771    #[serde(skip_serializing_if = "Option::is_none")]
772    pub timeout: Option<String>,
773    #[serde(default, skip_serializing_if = "is_zero")]
774    pub retries: i64,
775    #[serde(skip_serializing_if = "Option::is_none")]
776    pub start_period: Option<String>,
777    #[serde(skip_serializing_if = "Option::is_none")]
778    pub start_interval: Option<String>,
779    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
780    pub disable: bool,
781}
782
783#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
784#[serde(untagged)]
785pub enum HealthcheckTest {
786    Single(String),
787    Multiple(Vec<String>),
788}
789
790#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
791#[serde(deny_unknown_fields)]
792pub struct Limits {
793    #[serde(skip_serializing_if = "Option::is_none")]
794    pub cpus: Option<String>,
795    #[serde(skip_serializing_if = "Option::is_none")]
796    pub memory: Option<String>,
797    #[serde(skip_serializing_if = "Option::is_none")]
798    pub devices: Option<Vec<Device>>,
799}
800
801#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
802#[serde(deny_unknown_fields)]
803pub struct Device {
804    #[serde(skip_serializing_if = "Option::is_none")]
805    pub driver: Option<String>,
806    #[serde(skip_serializing_if = "Option::is_none")]
807    pub count: Option<u32>,
808    #[serde(skip_serializing_if = "Option::is_none")]
809    pub device_ids: Option<Vec<String>>,
810    #[serde(skip_serializing_if = "Option::is_none")]
811    pub capabilities: Option<Vec<String>>,
812    #[serde(skip_serializing_if = "Option::is_none")]
813    #[cfg(feature = "indexmap")]
814    pub options: Option<IndexMap<String, Value>>,
815    #[cfg(not(feature = "indexmap"))]
816    pub options: Option<HashMap<String, Value>>,
817}
818
819#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
820#[serde(deny_unknown_fields)]
821pub struct Placement {
822    #[serde(skip_serializing_if = "Vec::is_empty", default)]
823    pub constraints: Vec<String>,
824    #[serde(skip_serializing_if = "Vec::is_empty", default)]
825    pub preferences: Vec<Preferences>,
826}
827
828#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
829#[serde(deny_unknown_fields)]
830pub struct Preferences {
831    pub spread: String,
832}
833
834#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
835#[serde(deny_unknown_fields)]
836pub struct Resources {
837    pub limits: Option<Limits>,
838    pub reservations: Option<Limits>,
839}
840
841#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
842#[serde(deny_unknown_fields)]
843pub struct RestartPolicy {
844    #[serde(skip_serializing_if = "Option::is_none")]
845    pub condition: Option<String>,
846    #[serde(skip_serializing_if = "Option::is_none")]
847    pub delay: Option<String>,
848    #[serde(skip_serializing_if = "Option::is_none")]
849    pub max_attempts: Option<i64>,
850    #[serde(skip_serializing_if = "Option::is_none")]
851    pub window: Option<String>,
852}
853
854#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
855#[serde(deny_unknown_fields)]
856pub struct UpdateConfig {
857    #[serde(skip_serializing_if = "Option::is_none")]
858    pub parallelism: Option<i64>,
859    #[serde(skip_serializing_if = "Option::is_none")]
860    pub delay: Option<String>,
861    #[serde(skip_serializing_if = "Option::is_none")]
862    pub failure_action: Option<String>,
863    #[serde(skip_serializing_if = "Option::is_none")]
864    pub monitor: Option<String>,
865    #[serde(skip_serializing_if = "Option::is_none")]
866    pub max_failure_ratio: Option<f64>,
867}
868
869#[cfg(feature = "indexmap")]
870#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
871pub struct ComposeSecrets(
872    #[serde(with = "serde_yaml::with::singleton_map_recursive")]
873    pub  IndexMap<String, Option<ComposeSecret>>,
874);
875
876#[cfg(not(feature = "indexmap"))]
877#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
878pub struct ComposeSecrets(
879    #[serde(with = "serde_yaml::with::singleton_map_recursive")]
880    pub  HashMap<String, Option<ComposeSecret>>,
881);
882
883#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
884#[serde(deny_unknown_fields, rename_all = "snake_case")]
885pub enum ComposeSecret {
886    File(String),
887    Environment(String),
888    #[serde(untagged)]
889    External {
890        external: bool,
891        name: String,
892    },
893}
894
895#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
896#[serde(untagged)]
897pub enum Secrets {
898    Simple(Vec<String>),
899    Advanced(Vec<AdvancedSecrets>),
900}
901
902impl Default for Secrets {
903    fn default() -> Self {
904        Self::Simple(Vec::new())
905    }
906}
907
908impl Secrets {
909    pub fn is_empty(&self) -> bool {
910        match self {
911            Self::Simple(v) => v.is_empty(),
912            Self::Advanced(v) => v.is_empty(),
913        }
914    }
915}
916
917#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
918#[serde(deny_unknown_fields)]
919pub struct AdvancedSecrets {
920    pub source: String,
921    #[serde(skip_serializing_if = "Option::is_none")]
922    pub target: Option<String>,
923    #[serde(default, skip_serializing_if = "Option::is_none")]
924    pub uid: Option<String>,
925    #[serde(default, skip_serializing_if = "Option::is_none")]
926    pub gid: Option<String>,
927    #[serde(default, skip_serializing_if = "Option::is_none")]
928    pub mode: Option<String>,
929}
930
931#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
932#[serde(rename_all = "lowercase")]
933pub enum PullPolicy {
934    Always,
935    Never,
936    #[serde(alias = "if_not_present")]
937    Missing,
938    Build,
939}
940
941#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
942#[serde(untagged)]
943pub enum Volumes {
944    Simple(String),
945    Advanced(AdvancedVolumes),
946}
947
948#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
949#[serde(deny_unknown_fields)]
950pub struct AdvancedVolumes {
951    #[serde(skip_serializing_if = "Option::is_none")]
952    pub source: Option<String>,
953    pub target: String,
954    #[serde(rename = "type")]
955    pub _type: String,
956    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
957    pub read_only: bool,
958    #[serde(default, skip_serializing_if = "Option::is_none")]
959    pub bind: Option<Bind>,
960    #[serde(default, skip_serializing_if = "Option::is_none")]
961    pub volume: Option<Volume>,
962    #[serde(default, skip_serializing_if = "Option::is_none")]
963    pub tmpfs: Option<TmpfsSettings>,
964}
965
966#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
967#[serde(deny_unknown_fields)]
968pub struct Bind {
969    pub propagation: Option<String>,
970    pub create_host_path: Option<bool>,
971    pub selinux: Option<String>,
972}
973
974#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
975#[serde(deny_unknown_fields)]
976pub struct Volume {
977    #[serde(skip_serializing_if = "Option::is_none")]
978    pub nocopy: Option<bool>,
979    #[serde(skip_serializing_if = "Option::is_none")]
980    pub subpath: Option<String>,
981}
982
983#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash, Default)]
984#[serde(deny_unknown_fields)]
985pub struct TmpfsSettings {
986    pub size: u64,
987}
988
989#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
990#[serde(untagged)]
991pub enum Command {
992    Simple(String),
993    Args(Vec<String>),
994}
995
996#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
997#[serde(untagged)]
998pub enum Entrypoint {
999    Simple(String),
1000    List(Vec<String>),
1001}
1002
1003#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
1004#[serde(untagged)]
1005pub enum SingleValue {
1006    String(String),
1007    Bool(bool),
1008    Unsigned(u64),
1009    Signed(i64),
1010    Float(f64),
1011}
1012
1013impl fmt::Display for SingleValue {
1014    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1015        match self {
1016            Self::String(s) => f.write_str(s),
1017            Self::Bool(b) => write!(f, "{b}"),
1018            Self::Unsigned(u) => write!(f, "{u}"),
1019            Self::Signed(i) => write!(f, "{i}"),
1020            Self::Float(fl) => write!(f, "{fl}"),
1021        }
1022    }
1023}
1024
1025#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
1026#[serde(untagged)]
1027pub enum Group {
1028    Named(String),
1029    Gid(u32),
1030}
1031
1032#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash)]
1033#[serde(untagged)]
1034pub enum MapOrEmpty<T> {
1035    Map(T),
1036    Empty,
1037}
1038
1039impl<T> Default for MapOrEmpty<T> {
1040    fn default() -> Self {
1041        Self::Empty
1042    }
1043}
1044
1045impl<T> From<MapOrEmpty<T>> for Option<T> {
1046    fn from(value: MapOrEmpty<T>) -> Self {
1047        match value {
1048            MapOrEmpty::Map(t) => Some(t),
1049            MapOrEmpty::Empty => None,
1050        }
1051    }
1052}
1053
1054impl<T> Serialize for MapOrEmpty<T>
1055where
1056    T: Serialize,
1057{
1058    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1059    where
1060        S: serde::Serializer,
1061    {
1062        match self {
1063            Self::Map(t) => t.serialize(serializer),
1064            Self::Empty => {
1065                use serde::ser::SerializeMap;
1066                serializer.serialize_map(None)?.end()
1067            }
1068        }
1069    }
1070}