use core::fmt;
use indexmap::{IndexMap, IndexSet};
use merge_it::*;
#[cfg(feature = "schemars")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet};
type StringBTreeMap = BTreeMap<String, String>;
mod service;
pub use service::*;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(untagged)]
pub enum StringOrNum {
String(String),
Num(i64),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(untagged)]
pub enum SingleValue {
String(String),
Bool(bool),
Int(i64),
Float(f64),
}
impl fmt::Display for SingleValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::String(s) => f.write_str(s),
Self::Bool(b) => write!(f, "{b}"),
Self::Int(i) => write!(f, "{i}"),
Self::Float(fl) => write!(f, "{fl}"),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(untagged)]
pub enum ListOrMap {
List(BTreeSet<String>),
Map(BTreeMap<String, String>),
}
impl ListOrMap {
pub fn contains(&self, key: &str) -> bool {
match self {
Self::List(list) => list.contains(key),
Self::Map(map) => map.contains_key(key),
}
}
pub fn get(&self, key: &str) -> Option<&String> {
match self {
Self::List(list) => list.get(key),
Self::Map(map) => map.get(key),
}
}
pub fn is_empty(&self) -> bool {
match self {
Self::List(btree_set) => btree_set.is_empty(),
Self::Map(btree_map) => btree_map.is_empty(),
}
}
}
impl Default for ListOrMap {
fn default() -> Self {
Self::List(Default::default())
}
}
impl Merge for ListOrMap {
fn merge(&mut self, other: Self) {
match self {
Self::List(left_list) => match other {
Self::List(other_list) => {
left_list.extend(other_list);
}
Self::Map(other_map) => {
if left_list.is_empty() || !other_map.is_empty() {
*self = Self::Map(other_map);
}
}
},
Self::Map(left_map) => match other {
Self::List(other_list) => {
if left_map.is_empty() || !other_list.is_empty() {
*self = Self::List(other_list);
}
}
Self::Map(other_map) => {
left_map.extend(other_map);
}
},
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(untagged)]
pub enum StringOrList {
String(String),
List(Vec<String>),
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(untagged)]
pub enum StringOrSortedList {
String(String),
List(BTreeSet<String>),
}
impl StringOrSortedList {
pub fn is_empty(&self) -> bool {
match self {
Self::String(str) => str.is_empty(),
Self::List(list) => list.is_empty(),
}
}
}
impl Default for StringOrSortedList {
fn default() -> Self {
Self::List(Default::default())
}
}
impl Merge for StringOrSortedList {
fn merge(&mut self, right: Self) {
match self {
Self::String(left_string) => {
match right {
Self::List(mut right_list) => {
let left_string = std::mem::take(left_string);
right_list.insert(left_string);
*self = Self::List(right_list);
}
Self::String(right_string) => {
if left_string.is_empty() || !right_string.is_empty() {
*left_string = right_string;
}
}
};
}
Self::List(left_list) => match right {
Self::String(right_string) => {
left_list.insert(right_string);
}
Self::List(right_list) => {
left_list.extend(right_list);
}
},
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default, Merge)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(default)]
pub struct ComposeFile {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
pub include: BTreeSet<Include>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
#[cfg(feature = "presets")]
pub services: BTreeMap<String, ServicePresetRef>,
#[cfg(not(feature = "presets"))]
pub services: BTreeMap<String, Service>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub configs: BTreeMap<String, TopLevelConfig>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub models: BTreeMap<String, TopLevelModel>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub networks: BTreeMap<String, TopLevelNetwork>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub secrets: BTreeMap<String, TopLevelSecret>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub volumes: BTreeMap<String, TopLevelVolume>,
#[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
pub extensions: BTreeMap<String, Value>,
}
impl ComposeFile {
pub fn new() -> Self {
Default::default()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default, Eq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(default)]
pub struct IncludeSettings {
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<StringOrSortedList>,
#[serde(skip_serializing_if = "Option::is_none")]
pub project_directory: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub env_file: Option<StringOrList>,
}
impl PartialOrd for IncludeSettings {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for IncludeSettings {
fn cmp(&self, other: &Self) -> Ordering {
self.path.cmp(&other.path)
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Ord, Eq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(untagged)]
pub enum Include {
Short(String),
Long(IncludeSettings),
}
impl Default for Include {
fn default() -> Self {
Self::Short(Default::default())
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(untagged)]
pub enum Ulimit {
Single(StringOrNum),
SoftHard {
soft: StringOrNum,
hard: StringOrNum,
},
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct TopLevelNetwork {
#[serde(skip_serializing_if = "Option::is_none")]
pub external: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub internal: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub driver: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub attachable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
enable_ipv4: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub enable_ipv6: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub ipam: Option<Ipam>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub driver_opts: Option<BTreeMap<String, Option<SingleValue>>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub labels: Option<ListOrMap>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(default)]
pub struct Ipam {
#[serde(skip_serializing_if = "Option::is_none")]
pub driver: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<BTreeSet<IpamConfig>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<StringBTreeMap>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(default)]
pub struct IpamConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub subnet: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ip_range: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gateway: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub aux_addresses: Option<StringBTreeMap>,
}
impl PartialOrd for IpamConfig {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for IpamConfig {
fn cmp(&self, other: &Self) -> Ordering {
self.subnet.cmp(&other.subnet)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(rename_all = "lowercase")]
pub enum EndpointMode {
Vip,
Dnsrr,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum DeployMode {
Global,
Replicated,
ReplicatedJob,
GlobalJob,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(default)]
pub struct Deploy {
#[serde(skip_serializing_if = "Option::is_none")]
pub endpoint_mode: Option<EndpointMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<DeployMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub replicas: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub labels: Option<ListOrMap>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rollback_config: Option<RollbackConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub update_config: Option<UpdateConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resources: Option<Resources>,
#[serde(skip_serializing_if = "Option::is_none")]
pub restart_policy: Option<RestartPolicy>,
#[serde(skip_serializing_if = "Option::is_none")]
pub placement: Option<Placement>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(default)]
pub struct Limits {
#[serde(skip_serializing_if = "Option::is_none")]
pub cpus: Option<StringOrNum>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pids: Option<StringOrNum>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(default)]
#[serde(deny_unknown_fields)]
pub struct Reservations {
#[serde(skip_serializing_if = "Option::is_none")]
pub cpus: Option<StringOrNum>,
#[serde(skip_serializing_if = "Option::is_none")]
pub memory: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub generic_resources: Option<BTreeSet<GenericResource>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub devices: Option<BTreeSet<Device>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default, PartialOrd, Ord)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(default)]
#[serde(deny_unknown_fields)]
pub struct GenericResource {
#[serde(skip_serializing_if = "Option::is_none")]
pub discrete_resource_spec: Option<DiscreteResourceSpec>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(default)]
#[serde(deny_unknown_fields)]
pub struct DiscreteResourceSpec {
#[serde(skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<StringOrNum>,
}
impl PartialOrd for DiscreteResourceSpec {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DiscreteResourceSpec {
fn cmp(&self, other: &Self) -> Ordering {
self.kind.cmp(&other.kind)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct Device {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub driver: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub count: Option<StringOrNum>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub device_ids: Option<BTreeSet<String>>,
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
pub capabilities: BTreeSet<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub options: Option<ListOrMap>,
}
impl PartialOrd for Device {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Device {
fn cmp(&self, other: &Self) -> Ordering {
self.driver.cmp(&other.driver)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(default)]
#[serde(deny_unknown_fields)]
pub struct Placement {
#[serde(skip_serializing_if = "Option::is_none")]
pub constraints: Option<BTreeSet<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub preferences: Option<BTreeSet<Preferences>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct Preferences {
pub spread: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(default)]
pub struct Resources {
#[serde(skip_serializing_if = "Option::is_none")]
pub limits: Option<Limits>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reservations: Option<Reservations>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum RestartPolicyCondition {
None,
OnFailure,
Any,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(default)]
#[serde(deny_unknown_fields)]
pub struct RestartPolicy {
#[serde(skip_serializing_if = "Option::is_none")]
pub condition: Option<RestartPolicyCondition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_attempts: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub window: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum UpdateFailureAction {
Continue,
Rollback,
Pause,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum RollbackFailureAction {
Continue,
Pause,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum OperationsOrder {
StartFirst,
StopFirst,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(default)]
#[serde(deny_unknown_fields)]
pub struct RollbackConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub parallelism: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub failure_action: Option<RollbackFailureAction>,
#[serde(skip_serializing_if = "Option::is_none")]
pub monitor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_failure_ratio: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order: Option<OperationsOrder>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(default)]
pub struct UpdateConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub parallelism: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delay: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub failure_action: Option<UpdateFailureAction>,
#[serde(skip_serializing_if = "Option::is_none")]
pub monitor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_failure_ratio: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order: Option<OperationsOrder>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields, rename_all = "snake_case")]
pub enum TopLevelSecret {
File(String),
Environment(String),
#[serde(untagged)]
External {
external: bool,
name: String,
},
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(untagged)]
pub enum ServiceConfigOrSecret {
String(String),
Advanced(ServiceConfigOrSecretSettings),
}
impl PartialOrd for ServiceConfigOrSecretSettings {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ServiceConfigOrSecretSettings {
fn cmp(&self, other: &Self) -> Ordering {
self.source.cmp(&other.source)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct ServiceConfigOrSecretSettings {
pub source: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub target: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub uid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub gid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub mode: Option<StringOrNum>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default, Eq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(default)]
#[serde(deny_unknown_fields)]
pub struct TopLevelConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub external: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub environment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(untagged)]
pub enum ExtraHosts {
List(BTreeSet<String>),
Map(BTreeMap<String, StringOrSortedList>),
}
impl Merge for ExtraHosts {
fn merge(&mut self, other: Self) {
if let Self::List(left_list) = self
&& let Self::List(right_list) = other
{
left_list.extend(right_list);
} else if let Self::Map(left_map) = self
&& let Self::Map(right_map) = other
{
left_map.extend(right_map);
} else {
*self = other;
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct TopLevelModel {
pub model: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub context_size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub runtime_flags: Option<Vec<String>>,
}
impl PartialOrd for TopLevelModel {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TopLevelModel {
fn cmp(&self, other: &Self) -> Ordering {
self.name
.cmp(&other.name)
.then_with(|| self.model.cmp(&other.model))
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct TopLevelVolume {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub external: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub driver: Option<String>,
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
#[serde(default)]
pub driver_opts: BTreeMap<String, SingleValue>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub labels: Option<ListOrMap>,
}