Skip to main content

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