kube_core/
error_boundary.rsuse std::borrow::Cow;
use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
use serde::Deserialize;
use serde_value::DeserializerError;
use thiserror::Error;
use crate::{PartialObjectMeta, Resource};
#[derive(Debug, Clone)]
pub struct DeserializeGuard<K>(pub Result<K, InvalidObject>);
#[derive(Debug, Clone, Error)]
#[error("{error}")]
pub struct InvalidObject {
pub error: String,
pub metadata: ObjectMeta,
}
impl<'de, K> Deserialize<'de> for DeserializeGuard<K>
where
K: Deserialize<'de>,
K: Resource,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let buffer = serde_value::Value::deserialize(deserializer)?;
K::deserialize(buffer.clone())
.map(Ok)
.or_else(|err| {
let PartialObjectMeta { metadata, .. } =
PartialObjectMeta::<K>::deserialize(buffer).map_err(DeserializerError::into_error)?;
Ok(Err(InvalidObject {
error: err.to_string(),
metadata,
}))
})
.map(DeserializeGuard)
}
}
impl<K: Resource> Resource for DeserializeGuard<K> {
type DynamicType = K::DynamicType;
type Scope = K::Scope;
fn kind(dt: &Self::DynamicType) -> Cow<str> {
K::kind(dt)
}
fn group(dt: &Self::DynamicType) -> Cow<str> {
K::group(dt)
}
fn version(dt: &Self::DynamicType) -> Cow<str> {
K::version(dt)
}
fn plural(dt: &Self::DynamicType) -> Cow<str> {
K::plural(dt)
}
fn meta(&self) -> &ObjectMeta {
self.0.as_ref().map_or_else(|err| &err.metadata, K::meta)
}
fn meta_mut(&mut self) -> &mut ObjectMeta {
self.0.as_mut().map_or_else(|err| &mut err.metadata, K::meta_mut)
}
}
#[cfg(test)]
mod tests {
use k8s_openapi::api::core::v1::{ConfigMap, Pod};
use serde_json::json;
use crate::{DeserializeGuard, Resource};
#[test]
fn should_parse_meta_of_invalid_objects() {
let pod_error = serde_json::from_value::<DeserializeGuard<Pod>>(json!({
"metadata": {
"name": "the-name",
"namespace": "the-namespace",
},
"spec": {
"containers": "not-a-list",
},
}))
.unwrap();
assert_eq!(pod_error.meta().name.as_deref(), Some("the-name"));
assert_eq!(pod_error.meta().namespace.as_deref(), Some("the-namespace"));
pod_error.0.unwrap_err();
}
#[test]
fn should_allow_valid_objects() {
let configmap = serde_json::from_value::<DeserializeGuard<ConfigMap>>(json!({
"metadata": {
"name": "the-name",
"namespace": "the-namespace",
},
"data": {
"foo": "bar",
},
}))
.unwrap();
assert_eq!(configmap.meta().name.as_deref(), Some("the-name"));
assert_eq!(configmap.meta().namespace.as_deref(), Some("the-namespace"));
assert_eq!(
configmap.0.unwrap().data,
Some([("foo".to_string(), "bar".to_string())].into())
)
}
#[test]
fn should_catch_invalid_objects() {
serde_json::from_value::<DeserializeGuard<Pod>>(json!({
"spec": {
"containers": "not-a-list"
}
}))
.unwrap()
.0
.unwrap_err();
}
}