From 080e41e21995895c1583d5925ac0f1a4ad172829 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sat, 6 Apr 2024 18:21:02 -0500 Subject: [PATCH 01/10] feat(volume): add `Volume::is_empty()` --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/volume.rs | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26f1b4b..72d9b71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "compose_spec" -version = "0.1.0" +version = "0.1.1-beta.1" dependencies = [ "compose_spec_macros", "indexmap", diff --git a/Cargo.toml b/Cargo.toml index b0b8d4b..fd951b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,7 +96,7 @@ serde_yaml = "0.9" [package] name = "compose_spec" -version = "0.1.0" +version = "0.1.1-beta.1" authors.workspace = true edition.workspace = true license.workspace = true diff --git a/src/volume.rs b/src/volume.rs index 708416b..efc468e 100644 --- a/src/volume.rs +++ b/src/volume.rs @@ -63,3 +63,23 @@ pub struct Volume { #[serde(flatten)] pub extensions: Extensions, } + +impl Volume { + /// Returns `true` if all fields are [`None`] or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + driver, + driver_opts, + labels, + name, + extensions, + } = self; + + driver.is_none() + && driver_opts.is_empty() + && labels.is_empty() + && name.is_none() + && extensions.is_empty() + } +} From d04922db17d91455d4f00b048233f72089fc9198 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sun, 7 Apr 2024 00:07:00 -0500 Subject: [PATCH 02/10] feat(network): add `Network::is_empty()` Added `is_empty()` methods to `compose_spec::{Network, network::{Ipam, IpamConfig}}`. --- src/network.rs | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/src/network.rs b/src/network.rs index 3df2563..a83ac3f 100644 --- a/src/network.rs +++ b/src/network.rs @@ -96,6 +96,54 @@ pub struct Network { pub extensions: Extensions, } +impl Network { + /// Returns `true` if all fields are [`None`], `false`, or empty. + /// + /// The `ipam` field counts as empty if it is [`None`] or [empty](Ipam::is_empty()). + /// + /// # Examples + /// + /// ``` + /// use compose_spec::{Network, network::Ipam}; + /// + /// let mut network = Network::default(); + /// assert!(network.is_empty()); + /// + /// network.ipam = Some(Ipam::default()); + /// assert!(network.is_empty()); + /// + /// network.ipam = Some(Ipam { + /// driver: Some("driver".to_owned()), + /// ..Ipam::default() + /// }); + /// assert!(!network.is_empty()); + /// ``` + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + driver, + driver_opts, + attachable, + enable_ipv6, + ipam, + internal, + labels, + name, + extensions, + } = self; + + driver.is_none() + && driver_opts.is_empty() + && !attachable + && !enable_ipv6 + && !ipam.as_ref().is_some_and(|ipam| !ipam.is_empty()) + && !internal + && labels.is_empty() + && name.is_none() + && extensions.is_empty() + } +} + /// [`Network`] driver. /// /// Default and available values are platform specific. @@ -195,6 +243,45 @@ pub struct Ipam { pub extensions: Extensions, } +impl Ipam { + /// Returns `true` if all fields are [`None`] or empty. + /// + /// The `config` field counts as empty if all [`IpamConfig`]s are + /// [empty](IpamConfig::is_empty()) or if the [`Vec`] is empty. + /// + /// # Examples + /// + /// ``` + /// use compose_spec::network::{Ipam, IpamConfig}; + /// + /// let mut ipam = Ipam::default(); + /// assert!(ipam.is_empty()); + /// + /// ipam.config.push(IpamConfig::default()); + /// assert!(ipam.is_empty()); + /// + /// ipam.config.push(IpamConfig { + /// subnet: Some("10.0.0.0/24".parse().unwrap()), + /// ..IpamConfig::default() + /// }); + /// assert!(!ipam.is_empty()); + /// ``` + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + driver, + config, + options, + extensions, + } = self; + + driver.is_none() + && config.iter().all(IpamConfig::is_empty) + && options.is_empty() + && extensions.is_empty() + } +} + /// [`Ipam`] configuration. /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/06-networks.md#ipam) @@ -223,3 +310,23 @@ pub struct IpamConfig { #[serde(flatten)] pub extensions: Extensions, } + +impl IpamConfig { + /// Returns `true` if all fields are [`None`] or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + subnet, + ip_range, + gateway, + aux_addresses, + extensions, + } = self; + + subnet.is_none() + && ip_range.is_none() + && gateway.is_none() + && aux_addresses.is_empty() + && extensions.is_empty() + } +} From cd95a5c0854b2035d70caae8ef5fec965adde3be Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sun, 7 Apr 2024 00:14:15 -0500 Subject: [PATCH 03/10] feat(service): add `Logging::is_empty()` --- src/service.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/service.rs b/src/service.rs index 216f438..c0d74e4 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1215,6 +1215,20 @@ pub struct Logging { pub extensions: Extensions, } +impl Logging { + /// Returns `true` if all fields are [`None`] or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + driver, + options, + extensions, + } = self; + + driver.is_none() && options.is_empty() && extensions.is_empty() + } +} + /// Preference for a [`Service`] container to be killed by the platform in the case of memory /// starvation. /// From 064f9dd91d10ce404a990230ef1d133358b69ae1 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sun, 7 Apr 2024 00:23:24 -0500 Subject: [PATCH 04/10] feat(service): add `healthcheck::Command::is_empty()` --- src/service/healthcheck.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/service/healthcheck.rs b/src/service/healthcheck.rs index 9bfd754..3e08e32 100644 --- a/src/service/healthcheck.rs +++ b/src/service/healthcheck.rs @@ -342,6 +342,30 @@ pub struct Command { pub extensions: Extensions, } +impl Command { + /// Returns `true` if all fields are [`None`] or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + test, + interval, + timeout, + retries, + start_period, + start_interval, + extensions, + } = self; + + test.is_none() + && interval.is_none() + && timeout.is_none() + && retries.is_none() + && start_period.is_none() + && start_interval.is_none() + && extensions.is_empty() + } +} + /// Command run to check container health as part of a [`Healthcheck`]. /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/05-services.md#healthcheck) From e7233b5651d69d746886f39908f0f1b2649054b4 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sun, 7 Apr 2024 00:34:04 -0500 Subject: [PATCH 05/10] feat(service): add `Build::is_empty()` --- src/service/build.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/service/build.rs b/src/service/build.rs index 001987a..390257c 100644 --- a/src/service/build.rs +++ b/src/service/build.rs @@ -188,6 +188,58 @@ pub struct Build { pub extensions: Extensions, } +impl Build { + /// Returns `true` if all fields are [`None`] or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + context, + dockerfile, + args, + ssh, + cache_from, + cache_to, + additional_contexts, + extra_hosts, + isolation, + privileged, + labels, + no_cache, + pull, + network, + shm_size, + target, + secrets, + tags, + ulimits, + platforms, + extensions, + } = self; + + context.is_none() + && dockerfile.is_none() + && args.is_empty() + && ssh.is_empty() + && cache_from.is_empty() + && cache_to.is_empty() + && additional_contexts.is_empty() + && extra_hosts.is_empty() + && isolation.is_none() + && !privileged + && labels.is_empty() + && !no_cache + && !pull + && network.is_none() + && shm_size.is_none() + && target.is_none() + && secrets.is_empty() + && tags.is_empty() + && ulimits.is_empty() + && platforms.is_empty() + && extensions.is_empty() + } +} + /// Deserialize `additional_contexts` field of [`Build`]. /// /// Converts from [`ListOrMap`]. From 6dd52b3dcddd51826ad58224f7bd200d0ad445cf Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sun, 7 Apr 2024 00:38:34 -0500 Subject: [PATCH 06/10] feat(service): add `BlkioConfig::is_empty()` --- src/service/blkio_config.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/service/blkio_config.rs b/src/service/blkio_config.rs index fa6c0fc..e5ecacc 100644 --- a/src/service/blkio_config.rs +++ b/src/service/blkio_config.rs @@ -53,6 +53,28 @@ pub struct BlkioConfig { pub weight_device: Vec, } +impl BlkioConfig { + /// Returns `true` if all fields are empty or [`None`]. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + device_read_bps, + device_read_iops, + device_write_bps, + device_write_iops, + weight, + weight_device, + } = self; + + device_read_bps.is_empty() + && device_read_iops.is_empty() + && device_write_bps.is_empty() + && device_write_iops.is_empty() + && weight.is_none() + && weight_device.is_empty() + } +} + /// Limit in bytes per second for read/write operations on a given device. /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/05-services.md#device_read_bps-device_write_bps) From e817bdd7ffc2ae22ee46cc7691cb37f3d8d1937e Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sun, 7 Apr 2024 01:00:33 -0500 Subject: [PATCH 07/10] feat(service): add `is_empty()` methods to `volumes::mount` types Added `is_empty()` methods to `volumes::mount::{VolumeOptions, BindOptions, TmpfsOptions}`. --- src/service/volumes/mount.rs | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/service/volumes/mount.rs b/src/service/volumes/mount.rs index 2a359ca..fe6dd0f 100644 --- a/src/service/volumes/mount.rs +++ b/src/service/volumes/mount.rs @@ -317,6 +317,20 @@ pub struct VolumeOptions { pub extensions: Extensions, } +impl VolumeOptions { + /// Returns `true` if all fields are `false`, [`None`], or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + nocopy, + subpath, + extensions, + } = self; + + !nocopy && subpath.is_none() && extensions.is_empty() + } +} + impl PartialEq for VolumeOptions { fn eq(&self, other: &Self) -> bool { let Self { @@ -452,6 +466,19 @@ impl BindOptions { propagation.is_none() && *create_host_path && extensions.is_empty() } + + /// Returns `true` if all fields are [`None`], `false`, or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + propagation, + create_host_path, + selinux, + extensions, + } = self; + + propagation.is_none() && !create_host_path && selinux.is_none() && extensions.is_empty() + } } impl PartialEq for BindOptions { @@ -599,6 +626,20 @@ pub struct TmpfsOptions { pub extensions: Extensions, } +impl TmpfsOptions { + /// Returns `true` if all fields are [`None`] or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + size, + mode, + extensions, + } = self; + + size.is_none() && mode.is_none() && extensions.is_empty() + } +} + impl PartialEq for TmpfsOptions { fn eq(&self, other: &Self) -> bool { let Self { From 3440a1f2e7e764e7bd98e61d4990fe111119fe83 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sun, 7 Apr 2024 01:50:05 -0500 Subject: [PATCH 08/10] feat(service): add `Default` impl for `deploy::resources::Device` --- src/service/deploy/resources.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service/deploy/resources.rs b/src/service/deploy/resources.rs index 0d7c2d9..268b4bc 100644 --- a/src/service/deploy/resources.rs +++ b/src/service/deploy/resources.rs @@ -234,7 +234,7 @@ impl<'de> Visitor<'de> for CpusVisitor { /// A device a container may [reserve](Reservations). /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#devices) -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] pub struct Device { /// Generic and driver specific device capabilities. /// From 356466fe4794f77edb652a764af8fb00f77a539b Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sun, 7 Apr 2024 02:01:27 -0500 Subject: [PATCH 09/10] feat(service): add `deploy::Resources::is_empty()` Added `is_empty()` methods to all of: ``` compose_spec::service::deploy::resources::{ Resources, Limits, Reservations, Device, GenericResource, DiscreteResourceSpec, } ``` --- src/service/deploy/resources.rs | 168 ++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/src/service/deploy/resources.rs b/src/service/deploy/resources.rs index 268b4bc..acb2558 100644 --- a/src/service/deploy/resources.rs +++ b/src/service/deploy/resources.rs @@ -40,6 +40,47 @@ pub struct Resources { pub extensions: Extensions, } +impl Resources { + /// Returns `true` if all fields are [`None`] or empty. + /// + /// The `limits` field counts as empty if it is [`None`] or [empty](Limits::is_empty()). + /// + /// The `reservations` field counts as empty if it is [`None`] or + /// [empty](Reservations::is_empty()). + /// + /// # Examples + /// + /// ``` + /// use compose_spec::service::deploy::{Resources, resources::Limits}; + /// + /// let mut resources = Resources::default(); + /// assert!(resources.is_empty()); + /// + /// resources.limits = Some(Limits::default()); + /// assert!(resources.is_empty()); + /// + /// resources.limits = Some(Limits { + /// pids: Some(100), + /// ..Limits::default() + /// }); + /// assert!(!resources.is_empty()); + /// ``` + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + limits, + reservations, + extensions, + } = self; + + !limits.as_ref().is_some_and(|limits| !limits.is_empty()) + && !reservations + .as_ref() + .is_some_and(|reservations| !reservations.is_empty()) + && extensions.is_empty() + } +} + /// Limits on [`Resources`] a container may allocate. /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#resources) @@ -70,6 +111,21 @@ pub struct Limits { pub extensions: Extensions, } +impl Limits { + /// Returns `true` if all fields are [`None`] or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + cpus, + memory, + pids, + extensions, + } = self; + + cpus.is_none() && memory.is_none() && pids.is_none() && extensions.is_empty() + } +} + /// [`Resources`] the platform must guarantee the container can allocate. /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#resources) @@ -104,6 +160,47 @@ pub struct Reservations { pub extensions: Extensions, } +impl Reservations { + /// Returns `true` if all fields are [`None`] or empty. + /// + /// The `devices` field counts as empty if all [`Device`]s are [empty](Device::is_empty()) or + /// the [`Vec`] is empty. + /// + /// The `generic_resources` field counts as empty if all [`GenericResource`]s are + /// [empty](GenericResource::is_empty()) or the [`Vec`] is empty. + /// + /// # Examples + /// + /// ``` + /// use compose_spec::service::deploy::resources::{Device, Reservations}; + /// + /// let mut reservations = Reservations::default(); + /// assert!(reservations.is_empty()); + /// + /// reservations.devices.push(Device::default()); + /// assert!(reservations.is_empty()); + /// + /// reservations.devices.push(Device::new(["capability"])); + /// assert!(!reservations.is_empty()); + /// ``` + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + cpus, + memory, + devices, + generic_resources, + extensions, + } = self; + + cpus.is_none() + && memory.is_none() + && devices.iter().all(Device::is_empty) + && generic_resources.iter().all(GenericResource::is_empty) + && extensions.is_empty() + } +} + /// How much of the available CPU resources, as number of cores, a container reserves for use. /// /// Must be a positive and finite number. @@ -292,6 +389,26 @@ impl Device { extensions: Extensions::new(), } } + + /// Returns `true` if all fields are [`None`] or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + capabilities, + driver, + count, + device_ids, + options, + extensions, + } = self; + + capabilities.is_empty() + && driver.is_none() + && count.is_none() + && device_ids.is_empty() + && options.is_empty() + && extensions.is_empty() + } } /// [`Device`] capability. @@ -487,6 +604,43 @@ pub struct GenericResource { pub extensions: Extensions, } +impl GenericResource { + /// Returns `true` if all fields are [`None`] or empty. + /// + /// The `discrete_resource_spec` field counts as empty if it is [`None`] or + /// [empty](DiscreteResourceSpec::is_empty()). + /// + /// # Examples + /// + /// ``` + /// use compose_spec::service::deploy::resources::{DiscreteResourceSpec, GenericResource}; + /// + /// let mut resource = GenericResource::default(); + /// assert!(resource.is_empty()); + /// + /// resource.discrete_resource_spec = Some(DiscreteResourceSpec::default()); + /// assert!(resource.is_empty()); + /// + /// resource.discrete_resource_spec = Some(DiscreteResourceSpec { + /// kind: Some("kind".to_owned()), + /// ..DiscreteResourceSpec::default() + /// }); + /// assert!(!resource.is_empty()); + /// ``` + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + discrete_resource_spec, + extensions, + } = self; + + !discrete_resource_spec + .as_ref() + .is_some_and(|discrete_resource_spec| !discrete_resource_spec.is_empty()) + && extensions.is_empty() + } +} + /// Discrete resource spec. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] pub struct DiscreteResourceSpec { @@ -504,3 +658,17 @@ pub struct DiscreteResourceSpec { #[serde(flatten)] pub extensions: Extensions, } + +impl DiscreteResourceSpec { + /// Returns `true` if all fields are [`None`] or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + kind, + value, + extensions, + } = self; + + kind.is_none() && value.is_none() && extensions.is_empty() + } +} From 2be912781382eed5714cbd13f396692ed5cccde3 Mon Sep 17 00:00:00 2001 From: Paul Nettleton Date: Sun, 7 Apr 2024 02:43:29 -0500 Subject: [PATCH 10/10] feat(service): add `Deploy::is_empty()` Added `is_empty()` methods to all of: ``` compose_spec::service::deploy::{ Deploy, Placement, Preference, RestartPolicy, UpdateRollbackConfig, } ``` --- src/service/deploy.rs | 154 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/src/service/deploy.rs b/src/service/deploy.rs index 2322392..6ffbe89 100644 --- a/src/service/deploy.rs +++ b/src/service/deploy.rs @@ -88,6 +88,67 @@ pub struct Deploy { pub extensions: Extensions, } +impl Deploy { + /// Returns `true` if all fields are [`None`] or empty. + /// + /// The `placement`, `resources`, `restart_policy`, `rollback_config`, and `update_config` + /// fields count as empty if they are [`None`] or contain an empty value. + /// + /// # Examples + /// + /// ``` + /// use compose_spec::service::{Deploy, deploy::Placement}; + /// + /// let mut deploy = Deploy::default(); + /// assert!(deploy.is_empty()); + /// + /// deploy.placement = Some(Placement::default()); + /// assert!(deploy.is_empty()); + /// + /// deploy.placement = Some(Placement { + /// constraints: vec!["constraint".to_owned()], + /// ..Placement::default() + /// }); + /// assert!(!deploy.is_empty()); + /// ``` + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + endpoint_mode, + labels, + mode, + placement, + replicas, + resources, + restart_policy, + rollback_config, + update_config, + extensions, + } = self; + + endpoint_mode.is_none() + && labels.is_empty() + && mode.is_none() + && !placement + .as_ref() + .is_some_and(|placement| !placement.is_empty()) + && replicas.is_none() + && !resources + .as_ref() + .is_some_and(|resources| !resources.is_empty()) + && !restart_policy + .as_ref() + .is_some_and(|restart| !restart.is_empty()) + && !rollback_config + .as_ref() + .is_some_and(|rollback| !rollback.is_empty()) + && !update_config + .as_ref() + .is_some_and(|update| !update.is_empty()) + && extensions.is_empty() + } +} + /// The replication model used to run the service on the platform. /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#mode) @@ -150,6 +211,45 @@ pub struct Placement { pub extensions: Extensions, } +impl Placement { + /// Returns `true` if all fields are empty or [`None`]. + /// + /// The `preferences` field counts as empty if all [`Preference`]s are + /// [empty](Preference::is_empty()) or if the [`Vec`] is empty. + /// + /// # Examples + /// + /// ``` + /// use compose_spec::service::deploy::{Placement, Preference}; + /// + /// let mut placement = Placement::default(); + /// assert!(placement.is_empty()); + /// + /// placement.preferences.push(Preference::default()); + /// assert!(placement.is_empty()); + /// + /// placement.preferences.push(Preference { + /// spread: Some("spread".to_owned()), + /// ..Preference::default() + /// }); + /// assert!(!placement.is_empty()); + /// ``` + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + constraints, + preferences, + max_replicas_per_node, + extensions, + } = self; + + constraints.is_empty() + && preferences.iter().all(Preference::is_empty) + && max_replicas_per_node.is_none() + && extensions.is_empty() + } +} + /// A property the platform's node should fulfill to run service container. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] pub struct Preference { @@ -164,6 +264,16 @@ pub struct Preference { pub extensions: Extensions, } +impl Preference { + /// Returns `true` if all fields are [`None`] or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { spread, extensions } = self; + + spread.is_none() && extensions.is_empty() + } +} + /// If and how to restart containers when they exit. /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#restart_policy) @@ -206,6 +316,26 @@ pub struct RestartPolicy { pub extensions: Extensions, } +impl RestartPolicy { + /// Returns `true` if all fields are [`None`] or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + condition, + delay, + max_attempts, + window, + extensions, + } = self; + + condition.is_none() + && delay.is_none() + && max_attempts.is_none() + && window.is_none() + && extensions.is_empty() + } +} + /// When to restart containers based on their exit status. /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#restart_policy) @@ -305,6 +435,30 @@ pub struct UpdateOrRollbackConfig { pub extensions: Extensions, } +impl UpdateOrRollbackConfig { + /// Returns `true` if all fields are [`None`] or empty. + #[must_use] + pub fn is_empty(&self) -> bool { + let Self { + parallelism, + delay, + failure_action, + monitor, + max_failure_ratio, + order, + extensions, + } = self; + + parallelism.is_none() + && delay.is_none() + && failure_action.is_none() + && monitor.is_none() + && max_failure_ratio.is_none() + && order.is_none() + && extensions.is_empty() + } +} + /// What to do if an [update or rollback](UpdateOrRollbackConfig) fails. /// /// [compose-spec](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#rollback_config)