kube_core/
error_boundary.rs1use std::borrow::Cow;
4
5use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
6use serde::Deserialize;
7use serde_value::DeserializerError;
8use thiserror::Error;
9
10use crate::{PartialObjectMeta, Resource};
11
12#[derive(Debug, Clone)]
18pub struct DeserializeGuard<K>(pub Result<K, InvalidObject>);
19
20#[derive(Debug, Clone, Error)]
22#[error("{error}")]
23pub struct InvalidObject {
24 pub error: String,
28 pub metadata: ObjectMeta,
30}
31
32impl<'de, K> Deserialize<'de> for DeserializeGuard<K>
33where
34 K: Deserialize<'de>,
35 K: Resource,
37{
38 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
39 where
40 D: serde::Deserializer<'de>,
41 {
42 let buffer = serde_value::Value::deserialize(deserializer)?;
45
46 K::deserialize(buffer.clone())
48 .map(Ok)
49 .or_else(|err| {
50 let PartialObjectMeta { metadata, .. } =
51 PartialObjectMeta::<K>::deserialize(buffer).map_err(DeserializerError::into_error)?;
52 Ok(Err(InvalidObject {
53 error: err.to_string(),
54 metadata,
55 }))
56 })
57 .map(DeserializeGuard)
58 }
59}
60
61impl<K: Resource> Resource for DeserializeGuard<K> {
62 type DynamicType = K::DynamicType;
63 type Scope = K::Scope;
64
65 fn kind(dt: &Self::DynamicType) -> Cow<str> {
66 K::kind(dt)
67 }
68
69 fn group(dt: &Self::DynamicType) -> Cow<str> {
70 K::group(dt)
71 }
72
73 fn version(dt: &Self::DynamicType) -> Cow<str> {
74 K::version(dt)
75 }
76
77 fn plural(dt: &Self::DynamicType) -> Cow<str> {
78 K::plural(dt)
79 }
80
81 fn meta(&self) -> &ObjectMeta {
82 self.0.as_ref().map_or_else(|err| &err.metadata, K::meta)
83 }
84
85 fn meta_mut(&mut self) -> &mut ObjectMeta {
86 self.0.as_mut().map_or_else(|err| &mut err.metadata, K::meta_mut)
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use k8s_openapi::api::core::v1::{ConfigMap, Pod};
93 use serde_json::json;
94
95 use crate::{DeserializeGuard, Resource};
96
97 #[test]
98 fn should_parse_meta_of_invalid_objects() {
99 let pod_error = serde_json::from_value::<DeserializeGuard<Pod>>(json!({
100 "metadata": {
101 "name": "the-name",
102 "namespace": "the-namespace",
103 },
104 "spec": {
105 "containers": "not-a-list",
106 },
107 }))
108 .unwrap();
109 assert_eq!(pod_error.meta().name.as_deref(), Some("the-name"));
110 assert_eq!(pod_error.meta().namespace.as_deref(), Some("the-namespace"));
111 pod_error.0.unwrap_err();
112 }
113
114 #[test]
115 fn should_allow_valid_objects() {
116 let configmap = serde_json::from_value::<DeserializeGuard<ConfigMap>>(json!({
117 "metadata": {
118 "name": "the-name",
119 "namespace": "the-namespace",
120 },
121 "data": {
122 "foo": "bar",
123 },
124 }))
125 .unwrap();
126 assert_eq!(configmap.meta().name.as_deref(), Some("the-name"));
127 assert_eq!(configmap.meta().namespace.as_deref(), Some("the-namespace"));
128 assert_eq!(
129 configmap.0.unwrap().data,
130 Some([("foo".to_string(), "bar".to_string())].into())
131 )
132 }
133
134 #[test]
135 fn should_catch_invalid_objects() {
136 serde_json::from_value::<DeserializeGuard<Pod>>(json!({
137 "spec": {
138 "containers": "not-a-list"
139 }
140 }))
141 .unwrap()
142 .0
143 .unwrap_err();
144 }
145}