docker_compose_types/
lib.rs

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