Skip to content

Commit

Permalink
Implement deep merge strategies according to the OpenAPI spec.
Browse files Browse the repository at this point in the history
Fixes #128
Closes #138
  • Loading branch information
nightkr authored and Arnavion committed Apr 1, 2023
1 parent 59f2249 commit c840bb4
Show file tree
Hide file tree
Showing 1,227 changed files with 5,760 additions and 2,314 deletions.
22 changes: 19 additions & 3 deletions k8s-openapi-codegen-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ pub fn run(
field_type_name,
required,
is_flattened,
merge_type: &schema.merge_type,
});
}

Expand Down Expand Up @@ -666,6 +667,12 @@ pub fn run(
where_part: Some(&template_generics_where_part),
};

let items_merge_type = swagger20::MergeType::List {
strategy: swagger20::KubernetesListType::Map,
keys: vec!["metadata().namespace".to_string(), "metadata().name".to_string()],
item_merge_type: Box::new(swagger20::MergeType::Default),
};

let template_properties = vec![
templates::Property {
name: "items",
Expand All @@ -674,6 +681,7 @@ pub fn run(
field_type_name: "Vec<T>".to_owned(),
required: templates::PropertyRequired::Required { is_default: true },
is_flattened: false,
merge_type: &items_merge_type,
},

templates::Property {
Expand All @@ -683,6 +691,7 @@ pub fn run(
field_type_name: (*metadata_rust_type).to_owned(),
required: templates::PropertyRequired::Required { is_default: true },
is_flattened: false,
merge_type: &swagger20::MergeType::Default,
},
];

Expand Down Expand Up @@ -729,6 +738,12 @@ pub fn run(
)?;

if definition.impl_deep_merge {
let template_generics_where_part = format!("T: {local}DeepMerge + {local}Metadata<Ty = {local}apimachinery::pkg::apis::meta::v1::ObjectMeta> + {local}ListableResource");
let template_generics = templates::Generics {
where_part: Some(&template_generics_where_part),
..template_generics
};

templates::struct_deep_merge::generate(
&mut out,
type_name,
Expand Down Expand Up @@ -808,6 +823,7 @@ pub fn run(
field_type_name,
required: templates::PropertyRequired::Optional,
is_flattened: false,
merge_type: &schema.merge_type,
});
}

Expand Down Expand Up @@ -1008,7 +1024,7 @@ pub fn run(
schema_feature,
map_namespace,
)?;
}
}

swagger20::SchemaKind::Ty(swagger20::Type::WatchEvent(_)) => {
templates::impl_schema::generate(
Expand Down Expand Up @@ -1441,7 +1457,7 @@ pub fn get_rust_ident(name: &str) -> std::borrow::Cow<'static, str> {
}
else {
result.push(match c {
'.' | '-' => '_',
'-' => '_',
c => c,
});
}
Expand Down Expand Up @@ -1691,7 +1707,7 @@ pub fn write_operation(
path == "io.k8s.ListOptional" ||
path == "io.k8s.PatchOptional" ||
path == "io.k8s.ReplaceOptional" ||
path == "io.k8s.WatchOptional"
path == "io.k8s.WatchOptional"
}
else {
false
Expand Down
91 changes: 91 additions & 0 deletions k8s-openapi-codegen-common/src/swagger20/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,49 @@ pub enum IntegerFormat {
Int64,
}

/// The value of an `x-kubernetes-list-type` annotation on a property.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub enum KubernetesListType {
#[default]
#[cfg_attr(feature = "serde", serde(rename = "atomic"))]
Atomic,

#[cfg_attr(feature = "serde", serde(rename = "map"))]
Map,

#[cfg_attr(feature = "serde", serde(rename = "set"))]
Set,
}

/// The value of an `x-kubernetes-map-type` annotation on a property.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub enum KubernetesMapType {
#[cfg_attr(feature = "serde", serde(rename = "atomic"))]
Atomic,

#[default]
#[cfg_attr(feature = "serde", serde(rename = "granular"))]
Granular,
}

#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum MergeType {
Default,

List {
strategy: KubernetesListType,
keys: Vec<String>,
item_merge_type: Box<MergeType>,
},

Map {
strategy: KubernetesMapType,
value_merge_type: Box<MergeType>,
},
}

/// A number format. This corresponds to the `"format"` property of a `"number"` schema type.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum NumberFormat {
Expand Down Expand Up @@ -109,6 +152,8 @@ pub struct Schema {
pub kind: SchemaKind,
pub kubernetes_group_kind_versions: Vec<super::KubernetesGroupKindVersion>,

pub merge_type: MergeType,

/// Used to store the definition path of the corresponding list type, if any.
pub list_kind: Option<String>,

Expand All @@ -134,6 +179,23 @@ impl<'de> serde::Deserialize<'de> for Schema {
#[serde(default, rename = "x-kubernetes-group-version-kind")]
kubernetes_group_kind_versions: Vec<super::KubernetesGroupKindVersion>,

#[serde(default, rename = "x-kubernetes-list-map-keys")]
kubernetes_list_map_keys: Vec<String>,

#[serde(default, rename = "x-kubernetes-list-type")]
kubernetes_list_type: KubernetesListType,

#[serde(default, rename = "x-kubernetes-map-type")]
kubernetes_map_type: KubernetesMapType,

#[serde(default, rename = "x-kubernetes-patch-merge-key")]
kubernetes_patch_merge_key: Option<String>,

/// Comma-separated list of strategy tags.
/// Ref: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md
#[serde(default, rename = "x-kubernetes-patch-strategy")]
kubernetes_patch_strategy: String,

properties: Option<std::collections::BTreeMap<PropertyName, Schema>>,

#[serde(rename = "$ref")]
Expand Down Expand Up @@ -175,11 +237,40 @@ impl<'de> serde::Deserialize<'de> for Schema {
SchemaKind::Ty(Type::Any)
};

if let Some(key) = value.kubernetes_patch_merge_key {
value.kubernetes_list_map_keys = vec![key];
}
if value.kubernetes_patch_strategy.split(',').any(|x| x == "merge") {
value.kubernetes_list_type =
if value.kubernetes_list_map_keys.is_empty() {
KubernetesListType::Set
}
else {
KubernetesListType::Map
};
}

let merge_type = match &kind {
SchemaKind::Ty(Type::Array { items }) => MergeType::List {
strategy: value.kubernetes_list_type,
keys: value.kubernetes_list_map_keys,
item_merge_type: Box::new(items.merge_type.clone()),
},

SchemaKind::Ty(Type::Object { additional_properties }) => MergeType::Map {
strategy: value.kubernetes_map_type,
value_merge_type: Box::new(additional_properties.merge_type.clone()),
},

_ => MergeType::Default,
};

Ok(Schema {
description: value.description,
kind,
kubernetes_group_kind_versions: value.kubernetes_group_kind_versions,
list_kind: None,
merge_type,
impl_deep_merge: true,
})
}
Expand Down
2 changes: 2 additions & 0 deletions k8s-openapi-codegen-common/src/swagger20/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ impl<'de> serde::Deserialize<'de> for Parameter {
kind: super::SchemaKind::Ty(super::Type::parse::<D>(&ty, None, None, None)?),
kubernetes_group_kind_versions: vec![],
list_kind: None,
merge_type: crate::swagger20::MergeType::Default,
impl_deep_merge: true,
},
),
Expand All @@ -137,6 +138,7 @@ impl<'de> serde::Deserialize<'de> for Parameter {
kind: super::SchemaKind::Ty(super::Type::parse::<D>(&ty, None, None, None)?),
kubernetes_group_kind_versions: vec![],
list_kind: None,
merge_type: crate::swagger20::MergeType::Default,
impl_deep_merge: true,
},
),
Expand Down
9 changes: 6 additions & 3 deletions k8s-openapi-codegen-common/src/templates/impl_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ fn gen_schema(
writeln!(props, "{indent} }}));")?;
writeln!(props, "{indent} {local}schemars::schema::Schema::Object(schema_obj)")?;
writeln!(props, "{indent} }},")?;
} else {
}
else {
writeln!(props, "{indent} __gen.subschema_for::<{type_name}>(),")?;
}

Expand Down Expand Up @@ -279,7 +280,8 @@ fn gen_type(
writeln!(out,
"{indent} items: Some({local}schemars::schema::SingleOrVec::Single(Box::new(__gen.subschema_for::<{}>()))),",
crate::get_fully_qualified_type_name(ref_path, map_namespace))?;
} else {
}
else {
writeln!(out, "{indent} items: Some({local}schemars::schema::SingleOrVec::Single(Box::new(")?;
gen_schema(out, items, local, map_namespace, depth + 2)?;
writeln!(out, "{indent} ))),")?;
Expand Down Expand Up @@ -326,7 +328,8 @@ fn gen_type(
writeln!(out,
"{indent} additional_properties: Some(Box::new(__gen.subschema_for::<{}>())),",
crate::get_fully_qualified_type_name(ref_path, map_namespace))?;
} else {
}
else {
writeln!(out, "{indent} additional_properties: Some(Box::new(")?;
gen_schema(out, additional_properties, local, map_namespace, depth + 2)?;
writeln!(out, "{indent} )),")?;
Expand Down
1 change: 1 addition & 0 deletions k8s-openapi-codegen-common/src/templates/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub(crate) struct Property<'a> {
pub(crate) field_type_name: String,
pub(crate) required: PropertyRequired,
pub(crate) is_flattened: bool,
pub(crate) merge_type: &'a crate::swagger20::MergeType,
}

#[derive(Clone, Copy)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ pub(crate) fn generate(
let watch_append_pair =
if is_watch {
" __query_pairs.append_pair(\"watch\", \"true\");\n"
} else {
}
else {
""
};

Expand Down
89 changes: 84 additions & 5 deletions k8s-openapi-codegen-common/src/templates/struct_deep_merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ pub(crate) fn generate(
fields: &[super::Property<'_>],
map_namespace: &impl crate::MapNamespace,
) -> Result<(), crate::Error> {
use std::fmt::Write;

let local = crate::map_namespace_local_to_string(map_namespace)?;

let type_generics_type = generics.type_part.map(|part| format!("<{part}>")).unwrap_or_default();
let type_generics_where = generics.where_part.map(|part| format!(" where {part}")).unwrap_or_default();

let mut merge_body = String::new();
for super::Property { field_name, .. } in fields {
writeln!(
for super::Property { field_name, merge_type, .. } in fields {
generate_field(
&mut merge_body,
" {local}DeepMerge::merge_from(&mut self.{field_name}, other.{field_name});",
&local,
&format!("&mut self.{field_name}"),
&format!("other.{field_name}"),
merge_type,
8,
)?;
}

Expand All @@ -32,3 +34,80 @@ pub(crate) fn generate(

Ok(())
}

fn generate_field(
writer: &mut impl std::fmt::Write,
local: &str,
self_name: &str,
other_name: &str,
merge_type: &crate::swagger20::MergeType,
indent: usize,
) -> Result<(), crate::Error> {
const S: &str = "";

match merge_type {
crate::swagger20::MergeType::Default => writeln!(
writer,
"{S:indent$}{local}DeepMerge::merge_from({self_name}, {other_name});",
)?,

crate::swagger20::MergeType::List {
strategy: crate::swagger20::KubernetesListType::Atomic,
keys: _,
item_merge_type: _,
} => writeln!(
writer,
"{S:indent$}{local}merge_strategies::list::atomic({self_name}, {other_name});",
)?,

crate::swagger20::MergeType::List {
strategy: crate::swagger20::KubernetesListType::Map,
keys,
item_merge_type,
} => {
writeln!(writer, "{S:indent$}{local}merge_strategies::list::map(")?;
writeln!(writer, "{S:indent$} {self_name},")?;
writeln!(writer, "{S:indent$} {other_name},")?;
writeln!(
writer,
"{S:indent$} &[{keys}],",
keys = keys.iter().map(|k| format!("|lhs, rhs| lhs.{k} == rhs.{k}", k = crate::get_rust_ident(k))).collect::<Vec<_>>().join(", "),
)?;
writeln!(writer, "{S:indent$} |current_item, other_item| {{")?;
generate_field(writer, local, "current_item", "other_item", item_merge_type, indent + 8)?;
writeln!(writer, "{S:indent$} }},")?;
writeln!(writer, "{S:indent$});")?;
},

crate::swagger20::MergeType::List {
strategy: crate::swagger20::KubernetesListType::Set,
keys: _,
item_merge_type: _,
} => writeln!(
writer,
"{S:indent$}{local}merge_strategies::list::set({self_name}, {other_name});",
)?,

crate::swagger20::MergeType::Map {
strategy: crate::swagger20::KubernetesMapType::Granular,
value_merge_type,
} => {
writeln!(
writer,
"{S:indent$}{local}merge_strategies::map::granular({self_name}, {other_name}, |current_item, other_item| {{",
)?;
generate_field(writer, local, "current_item", "other_item", value_merge_type, indent + 4)?;
writeln!(writer, "{S:indent$}}});")?;
},

crate::swagger20::MergeType::Map {
strategy: crate::swagger20::KubernetesMapType::Atomic,
value_merge_type: _,
} => writeln!(
writer,
"{S:indent$}{local}merge_strategies::map::atomic({self_name}, {other_name});",
)?,
}

Ok(())
}
Loading

0 comments on commit c840bb4

Please sign in to comment.