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