kube_core/
metadata.rs

1//! Metadata structs used in traits, lists, and dynamic objects.
2use std::{borrow::Cow, marker::PhantomData};
3
4pub use k8s_openapi::apimachinery::pkg::apis::meta::v1::{ListMeta, ObjectMeta};
5use serde::{Deserialize, Serialize};
6
7use crate::{DynamicObject, Resource};
8
9/// Type information that is flattened into every kubernetes object
10#[derive(Deserialize, Serialize, Clone, Default, Debug, Eq, PartialEq, Hash)]
11#[serde(rename_all = "camelCase")]
12pub struct TypeMeta {
13    /// The version of the API
14    pub api_version: String,
15
16    /// The name of the API
17    pub kind: String,
18}
19
20impl TypeMeta {
21    /// Construct a new `TypeMeta` for the object list from the given resource.
22    ///
23    /// ```
24    /// # use k8s_openapi::api::core::v1::Pod;
25    /// # use kube_core::TypeMeta;
26    ///
27    /// let type_meta = TypeMeta::list::<Pod>();
28    /// assert_eq!(type_meta.kind, "PodList");
29    /// assert_eq!(type_meta.api_version, "v1");
30    /// ```
31    pub fn list<K: Resource<DynamicType = ()>>() -> Self {
32        TypeMeta {
33            api_version: K::api_version(&()).into(),
34            kind: K::kind(&()).to_string() + "List",
35        }
36    }
37
38    /// Construct a new `TypeMeta` for the object from the given resource.
39    ///
40    /// ```
41    /// # use k8s_openapi::api::core::v1::Pod;
42    /// # use kube_core::TypeMeta;
43    ///
44    /// let type_meta = TypeMeta::resource::<Pod>();
45    /// assert_eq!(type_meta.kind, "Pod");
46    /// assert_eq!(type_meta.api_version, "v1");
47    /// ```
48    pub fn resource<K: Resource<DynamicType = ()>>() -> Self {
49        TypeMeta {
50            api_version: K::api_version(&()).into(),
51            kind: K::kind(&()).into(),
52        }
53    }
54}
55
56/// A generic representation of any object with `ObjectMeta`.
57///
58/// It allows clients to get access to a particular `ObjectMeta`
59/// schema without knowing the details of the version.
60///
61/// See the [`PartialObjectMetaExt`] trait for how to construct one safely.
62#[derive(Deserialize, Serialize, Clone, Default, Debug)]
63#[serde(rename_all = "camelCase")]
64pub struct PartialObjectMeta<K = DynamicObject> {
65    /// The type fields, not always present
66    #[serde(flatten, default)]
67    pub types: Option<TypeMeta>,
68    /// Standard object's metadata
69    #[serde(default)]
70    pub metadata: ObjectMeta,
71    /// Type information for static dispatch
72    #[serde(skip, default)]
73    pub _phantom: PhantomData<K>,
74}
75
76mod private {
77    pub trait Sealed {}
78    impl Sealed for super::ObjectMeta {}
79}
80/// Helper trait for converting `ObjectMeta` into useful `PartialObjectMeta` variants
81pub trait PartialObjectMetaExt: private::Sealed {
82    /// Convert `ObjectMeta` into a Patch-serializable `PartialObjectMeta`
83    ///
84    /// This object can be passed to `Patch::Apply` and used with `Api::patch_metadata`,
85    /// for an `Api<K>` using the underlying types `TypeMeta`:
86    ///
87    /// ```
88    /// # use k8s_openapi::api::core::v1::Pod;
89    /// # use kube::core::{ObjectMeta, PartialObjectMetaExt, ResourceExt};
90    /// let partial = ObjectMeta {
91    ///     labels: Some([("key".to_string(), "value".to_string())].into()),
92    ///     ..Default::default()
93    /// }.into_request_partial::<Pod>();
94    ///
95    /// // request partials are generally closer to patches than fully valid resources:
96    /// assert_eq!(partial.name_any(), "");
97    ///
98    /// // typemeta is re-used from K:
99    /// assert_eq!(partial.types.unwrap().kind, "Pod");
100    /// ```
101    fn into_request_partial<K: Resource<DynamicType = ()>>(self) -> PartialObjectMeta<K>;
102
103    /// Convert `ObjectMeta` into a response object for a specific `Resource`
104    ///
105    /// This object emulates a response object and **cannot** be used in request bodies
106    /// because it contains erased `TypeMeta` (and the apiserver is doing the erasing).
107    ///
108    /// This method is **mostly useful for unit testing** behaviour.
109    ///
110    /// ```
111    /// # use k8s_openapi::api::apps::v1::Deployment;
112    /// # use kube::core::{ObjectMeta, PartialObjectMetaExt, ResourceExt};
113    /// let partial = ObjectMeta {
114    ///     name: Some("my-deploy".to_string()),
115    ///     namespace: Some("default".to_string()),
116    ///     ..Default::default()
117    /// }.into_response_partial::<Deployment>();
118    ///
119    /// assert_eq!(partial.name_any(), "my-deploy");
120    /// assert_eq!(partial.types.unwrap().kind, "PartialObjectMetadata"); // NB: Pod erased
121    /// ```
122    fn into_response_partial<K>(self) -> PartialObjectMeta<K>;
123}
124
125impl PartialObjectMetaExt for ObjectMeta {
126    fn into_request_partial<K: Resource<DynamicType = ()>>(self) -> PartialObjectMeta<K> {
127        PartialObjectMeta {
128            types: Some(TypeMeta {
129                api_version: K::api_version(&()).into(),
130                kind: K::kind(&()).into(),
131            }),
132            metadata: self,
133            _phantom: PhantomData,
134        }
135    }
136
137    fn into_response_partial<K>(self) -> PartialObjectMeta<K> {
138        PartialObjectMeta {
139            types: Some(TypeMeta {
140                api_version: "meta.k8s.io/v1".to_string(),
141                kind: "PartialObjectMetadata".to_string(),
142            }),
143            metadata: self,
144            _phantom: PhantomData,
145        }
146    }
147}
148
149impl<K: Resource> Resource for PartialObjectMeta<K> {
150    type DynamicType = K::DynamicType;
151    type Scope = K::Scope;
152
153    fn kind(dt: &Self::DynamicType) -> Cow<'_, str> {
154        K::kind(dt)
155    }
156
157    fn group(dt: &Self::DynamicType) -> Cow<'_, str> {
158        K::group(dt)
159    }
160
161    fn version(dt: &Self::DynamicType) -> Cow<'_, str> {
162        K::version(dt)
163    }
164
165    fn plural(dt: &Self::DynamicType) -> Cow<'_, str> {
166        K::plural(dt)
167    }
168
169    fn meta(&self) -> &ObjectMeta {
170        &self.metadata
171    }
172
173    fn meta_mut(&mut self) -> &mut ObjectMeta {
174        &mut self.metadata
175    }
176}
177
178#[cfg(test)]
179mod test {
180    use super::{ObjectMeta, PartialObjectMeta, PartialObjectMetaExt};
181    use crate::Resource;
182    use k8s_openapi::api::core::v1::Pod;
183
184    #[test]
185    fn can_convert_and_derive_partial_metadata() {
186        // can use generic type for static dispatch;
187        assert_eq!(PartialObjectMeta::<Pod>::kind(&()), "Pod");
188        assert_eq!(PartialObjectMeta::<Pod>::api_version(&()), "v1");
189
190        // can convert from objectmeta to partials for different use cases:
191        let meta = ObjectMeta {
192            name: Some("mypod".into()),
193            ..Default::default()
194        };
195        let request_pom = meta.clone().into_request_partial::<Pod>();
196        let response_pom = meta.into_response_partial::<Pod>();
197
198        // they both basically just inline the metadata;
199        assert_eq!(request_pom.metadata.name, Some("mypod".to_string()));
200        assert_eq!(response_pom.metadata.name, Some("mypod".to_string()));
201
202        // the request_pom will use the TypeMeta from K to support POST/PUT requests
203        assert_eq!(request_pom.types.as_ref().unwrap().api_version, "v1");
204        assert_eq!(request_pom.types.as_ref().unwrap().kind, "Pod");
205
206        // but the response_pom will use the type-erased kinds from the apiserver
207        assert_eq!(response_pom.types.as_ref().unwrap().api_version, "meta.k8s.io/v1");
208        assert_eq!(response_pom.types.as_ref().unwrap().kind, "PartialObjectMetadata");
209    }
210}