Skip to content

Commit

Permalink
Change WatchEvent::Bookmark to store the resource version directly …
Browse files Browse the repository at this point in the history
…instead of the full object.

In some cases, the API server sends a BOOKMARK watch event that can't be
successfully deserialized as the resource type. The example in #70 is of
a watch event for pods where the `spec.containers` field is `null`, even though
the spec requires that `containers` be non-null.

Since the only point of bookmark events is to carry a resource version,
this change makes it so that the deserializer only looks for
the `metadata.resourceVersion` field, in addition to the `apiVersion`
and `kind` fields like before.

Apart from changing the definition of the `WatchEvent::Bookmark` variant,
this change also adds a bound to the type parameter of `WatchEvent` -
it must now also implement `k8s_openapi::Resource` since its API_VERSION
and KIND are still needed to validate the bookmark object.

Fixes #70
  • Loading branch information
Arnavion committed Jul 4, 2020
1 parent 6fa2a64 commit f506c01
Show file tree
Hide file tree
Showing 22 changed files with 1,166 additions and 62 deletions.
1 change: 1 addition & 0 deletions k8s-openapi-codegen-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ pub fn run<W>(
templates::watch_event::generate(
&mut out,
&type_name,
crate_root,
has_bookmark_event_type,
&error_status_rust_type,
&error_other_rust_type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ pub(crate) fn generate(
let type_generics_impl = "<T>";
let type_generics_type = "<T>";
let type_generics_where: std::borrow::Cow<'_, str> = match operation_action {
OperationAction::List => format!(" where T: serde::de::DeserializeOwned + {}::ListableResource", crate_root).into(),

OperationAction::Create |
OperationAction::Delete |
OperationAction::Replace |
OperationAction::Patch |
OperationAction::Watch => " where T: serde::de::DeserializeOwned".into(),
OperationAction::Patch => " where T: serde::de::DeserializeOwned".into(),

OperationAction::List => format!(" where T: serde::de::DeserializeOwned + {}::ListableResource", crate_root).into(),

OperationAction::Watch => format!(" where T: serde::de::DeserializeOwned + {}::Resource", crate_root).into(),
};

let mut variants = String::new();
Expand Down
25 changes: 22 additions & 3 deletions k8s-openapi-codegen-common/src/templates/watch_event.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub(crate) fn generate(
mut writer: impl std::io::Write,
type_name: &str,
crate_root: &str,
has_bookmark_event_type: bool,
error_status_rust_type: &str,
error_other_rust_type: &str,
Expand All @@ -15,7 +16,7 @@ pub(crate) fn generate(
) =
if has_bookmark_event_type {
(
"Bookmark(T),\n ",
"Bookmark { resource_version: String },\n ",
"Bookmark,\n ",
"\"BOOKMARK\" => WatchEventType::Bookmark,\n ",
"\"BOOKMARK\", ",
Expand All @@ -25,16 +26,25 @@ pub(crate) fn generate(
let mut result = String::new();
writeln!(result, " WatchEventType::Bookmark => {{")?;
writeln!(result, " let value_object = serde_value::ValueDeserializer::new(value_object);")?;
writeln!(result, " {}::Bookmark(serde::Deserialize::deserialize(value_object)?)", type_name)?;
writeln!(result, " let value: BookmarkObject<'static, T> = serde::Deserialize::deserialize(value_object)?;")?;
writeln!(result, " {type_name}::Bookmark {{", type_name = type_name)?;
writeln!(result, " resource_version: value.metadata.resource_version.into_owned(),")?;
writeln!(result, " }}")?;
writeln!(result, " }},")?;
result
},
{
use std::fmt::Write;

let mut result = String::new();
writeln!(result, "{}::Bookmark(object) => {{", type_name)?;
writeln!(result, "{type_name}::Bookmark {{ resource_version }} => {{", type_name = type_name)?;
writeln!(result, r#" serde::ser::SerializeStruct::serialize_field(&mut state, "type", "BOOKMARK")?;"#)?;
writeln!(result, " let object = BookmarkObject::<T> {{")?;
writeln!(result, " metadata: BookmarkObjectMeta {{")?;
writeln!(result, " resource_version: std::borrow::Cow::Borrowed(&**resource_version),")?;
writeln!(result, " }},")?;
writeln!(result, " _resource: Default::default(),")?;
writeln!(result, " }};")?;
writeln!(result, r#" serde::ser::SerializeStruct::serialize_field(&mut state, "object", &object)?;"#)?;
writeln!(result, " }},")?;
write!(result, " ")?;
Expand All @@ -57,6 +67,7 @@ pub(crate) fn generate(
writer,
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/watch_event.rs")),
type_name = type_name,
crate_root = crate_root,
bookmark_def = bookmark_def,
error_status_rust_type = error_status_rust_type,
error_other_rust_type = error_other_rust_type,
Expand All @@ -67,5 +78,13 @@ pub(crate) fn generate(
bookmark_serialize = bookmark_serialize,
)?;

if has_bookmark_event_type {
writeln!(
writer,
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/watch_event_bookmark_object.rs")),
crate_root = crate_root,
)?;
}

Ok(())
}
6 changes: 3 additions & 3 deletions k8s-openapi-codegen-common/templates/watch_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ enum {type_name}<T> {{
ErrorOther({error_other_rust_type}),
}}

impl<'de, T> serde::Deserialize<'de> for {type_name}<T> where T: serde::Deserialize<'de> {{
impl<'de, T> serde::Deserialize<'de> for {type_name}<T> where T: serde::Deserialize<'de> + {crate_root}::Resource {{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {{
#[allow(non_camel_case_types)]
enum Field {{
Expand Down Expand Up @@ -77,7 +77,7 @@ impl<'de, T> serde::Deserialize<'de> for {type_name}<T> where T: serde::Deserial

struct Visitor<T>(std::marker::PhantomData<T>);

impl<'de, T> serde::de::Visitor<'de> for Visitor<T> where T: serde::Deserialize<'de> {{
impl<'de, T> serde::de::Visitor<'de> for Visitor<T> where T: serde::Deserialize<'de> + {crate_root}::Resource {{
type Value = {type_name}<T>;

fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{
Expand Down Expand Up @@ -147,7 +147,7 @@ impl<'de, T> serde::Deserialize<'de> for {type_name}<T> where T: serde::Deserial
}}
}}

impl<T> serde::Serialize for {type_name}<T> where T: serde::Serialize {{
impl<T> serde::Serialize for {type_name}<T> where T: serde::Serialize + {crate_root}::Resource {{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {{
let mut state = serializer.serialize_struct(
{type_name:?},
Expand Down
186 changes: 186 additions & 0 deletions k8s-openapi-codegen-common/templates/watch_event_bookmark_object.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@

#[derive(Debug, PartialEq)]
struct BookmarkObject<'a, T> {{
metadata: BookmarkObjectMeta<'a>,
_resource: std::marker::PhantomData<T>,
}}

#[derive(Debug, PartialEq)]
struct BookmarkObjectMeta<'a> {{
resource_version: std::borrow::Cow<'a, str>,
}}

impl<'de, T> serde::Deserialize<'de> for BookmarkObject<'static, T> where T: {crate_root}::Resource {{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {{
#[allow(non_camel_case_types)]
enum Field {{
Key_api_version,
Key_kind,
Key_metadata,
Other,
}}

impl<'de> serde::Deserialize<'de> for Field {{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {{
struct Visitor;

impl<'de> serde::de::Visitor<'de> for Visitor {{
type Value = Field;

fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{
f.write_str("field identifier")
}}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where E: serde::de::Error {{
Ok(match v {{
"apiVersion" => Field::Key_api_version,
"kind" => Field::Key_kind,
"metadata" => Field::Key_metadata,
_ => Field::Other,
}})
}}
}}

deserializer.deserialize_identifier(Visitor)
}}
}}

struct Visitor<T>(std::marker::PhantomData<T>);

impl<'de, T> serde::de::Visitor<'de> for Visitor<T> where T: {crate_root}::Resource {{
type Value = BookmarkObject<'static, T>;

fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{
f.write_str(<T as {crate_root}::Resource>::KIND)
}}

fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> where A: serde::de::MapAccess<'de> {{
let mut value_metadata: Option<BookmarkObjectMeta<'static>> = None;

while let Some(key) = serde::de::MapAccess::next_key::<Field>(&mut map)? {{
match key {{
Field::Key_api_version => {{
let value_api_version: String = serde::de::MapAccess::next_value(&mut map)?;
if value_api_version != <T as {crate_root}::Resource>::API_VERSION {{
return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(&value_api_version), &<T as {crate_root}::Resource>::API_VERSION));
}}
}},
Field::Key_kind => {{
let value_kind: String = serde::de::MapAccess::next_value(&mut map)?;
if value_kind != <T as {crate_root}::Resource>::KIND {{
return Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(&value_kind), &<T as {crate_root}::Resource>::KIND));
}}
}},
Field::Key_metadata => value_metadata = serde::de::MapAccess::next_value(&mut map)?,
Field::Other => {{ let _: serde::de::IgnoredAny = serde::de::MapAccess::next_value(&mut map)?; }},
}}
}}

Ok(BookmarkObject {{
metadata: value_metadata.ok_or_else(|| serde::de::Error::missing_field("metadata"))?,
_resource: Default::default()
}})
}}
}}

deserializer.deserialize_struct(
<T as {crate_root}::Resource>::KIND,
&[
"apiVersion",
"kind",
"metadata",
],
Visitor(Default::default()),
)
}}
}}

impl<'de> serde::Deserialize<'de> for BookmarkObjectMeta<'static> {{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {{
#[allow(non_camel_case_types)]
enum Field {{
Key_resource_version,
Other,
}}

impl<'de> serde::Deserialize<'de> for Field {{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {{
struct Visitor;

impl<'de> serde::de::Visitor<'de> for Visitor {{
type Value = Field;

fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{
f.write_str("field identifier")
}}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where E: serde::de::Error {{
Ok(match v {{
"resourceVersion" => Field::Key_resource_version,
_ => Field::Other,
}})
}}
}}

deserializer.deserialize_identifier(Visitor)
}}
}}

struct Visitor;

impl<'de> serde::de::Visitor<'de> for Visitor {{
type Value = BookmarkObjectMeta<'static>;

fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {{
f.write_str("ObjectMeta")
}}

fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> where A: serde::de::MapAccess<'de> {{
let mut value_resource_version: Option<String> = None;

while let Some(key) = serde::de::MapAccess::next_key::<Field>(&mut map)? {{
match key {{
Field::Key_resource_version => value_resource_version = serde::de::MapAccess::next_value(&mut map)?,
Field::Other => {{ let _: serde::de::IgnoredAny = serde::de::MapAccess::next_value(&mut map)?; }},
}}
}}

Ok(BookmarkObjectMeta {{
resource_version: std::borrow::Cow::Owned(value_resource_version.ok_or_else(|| serde::de::Error::missing_field("resourceVersion"))?),
}})
}}
}}

deserializer.deserialize_struct(
"ObjectMeta",
&[
"resourceVersion",
],
Visitor,
)
}}
}}

impl<'a, T> serde::Serialize for BookmarkObject<'a, T> where T: {crate_root}::Resource {{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {{
let mut state = serializer.serialize_struct(
<T as {crate_root}::Resource>::KIND,
3,
)?;
serde::ser::SerializeStruct::serialize_field(&mut state, "apiVersion", <T as {crate_root}::Resource>::API_VERSION)?;
serde::ser::SerializeStruct::serialize_field(&mut state, "kind", <T as {crate_root}::Resource>::KIND)?;
serde::ser::SerializeStruct::serialize_field(&mut state, "metadata", &self.metadata)?;
serde::ser::SerializeStruct::end(state)
}}
}}

impl<'a> serde::Serialize for BookmarkObjectMeta<'a> {{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {{
let mut state = serializer.serialize_struct(
"ObjectMeta",
1,
)?;
serde::ser::SerializeStruct::serialize_field(&mut state, "resourceVersion", &self.resource_version)?;
serde::ser::SerializeStruct::end(state)
}}
}}
Loading

0 comments on commit f506c01

Please sign in to comment.