1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! Metadata structs used in traits, lists, and dynamic objects.
use std::{borrow::Cow, marker::PhantomData};

pub use k8s_openapi::apimachinery::pkg::apis::meta::v1::{ListMeta, ObjectMeta};
use serde::{Deserialize, Serialize};

use crate::{DynamicObject, Resource};

/// Type information that is flattened into every kubernetes object
#[derive(Deserialize, Serialize, Clone, Default, Debug, Eq, PartialEq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct TypeMeta {
    /// The version of the API
    pub api_version: String,

    /// The name of the API
    pub kind: String,
}

/// A generic representation of any object with `ObjectMeta`.
///
/// It allows clients to get access to a particular `ObjectMeta`
/// schema without knowing the details of the version.
///
/// See the [`PartialObjectMetaExt`] trait for how to construct one safely.
#[derive(Deserialize, Serialize, Clone, Default, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PartialObjectMeta<K = DynamicObject> {
    /// The type fields, not always present
    #[serde(flatten, default)]
    pub types: Option<TypeMeta>,
    /// Standard object's metadata
    #[serde(default)]
    pub metadata: ObjectMeta,
    /// Type information for static dispatch
    #[serde(skip, default)]
    pub _phantom: PhantomData<K>,
}

mod private {
    pub trait Sealed {}
    impl Sealed for super::ObjectMeta {}
}
/// Helper trait for converting `ObjectMeta` into useful `PartialObjectMeta` variants
pub trait PartialObjectMetaExt: private::Sealed {
    /// Convert `ObjectMeta` into a Patch-serializable `PartialObjectMeta`
    ///
    /// This object can be passed to `Patch::Apply` and used with `Api::patch_metadata`,
    /// for an `Api<K>` using the underlying types `TypeMeta`:
    ///
    /// ```
    /// # use k8s_openapi::api::core::v1::Pod;
    /// # use kube::core::{ObjectMeta, PartialObjectMetaExt, ResourceExt};
    /// let partial = ObjectMeta {
    ///     labels: Some([("key".to_string(), "value".to_string())].into()),
    ///     ..Default::default()
    /// }.into_request_partial::<Pod>();
    ///
    /// // request partials are generally closer to patches than fully valid resources:
    /// assert_eq!(partial.name_any(), "");
    ///
    /// // typemeta is re-used from K:
    /// assert_eq!(partial.types.unwrap().kind, "Pod");
    /// ```
    fn into_request_partial<K: Resource<DynamicType = ()>>(self) -> PartialObjectMeta<K>;

    /// Convert `ObjectMeta` into a response object for a specific `Resource`
    ///
    /// This object emulates a response object and **cannot** be used in request bodies
    /// because it contains erased `TypeMeta` (and the apiserver is doing the erasing).
    ///
    /// This method is **mostly useful for unit testing** behaviour.
    ///
    /// ```
    /// # use k8s_openapi::api::apps::v1::Deployment;
    /// # use kube::core::{ObjectMeta, PartialObjectMetaExt, ResourceExt};
    /// let partial = ObjectMeta {
    ///     name: Some("my-deploy".to_string()),
    ///     namespace: Some("default".to_string()),
    ///     ..Default::default()
    /// }.into_response_partial::<Deployment>();
    ///
    /// assert_eq!(partial.name_any(), "my-deploy");
    /// assert_eq!(partial.types.unwrap().kind, "PartialObjectMetadata"); // NB: Pod erased
    /// ```
    fn into_response_partial<K>(self) -> PartialObjectMeta<K>;
}

impl PartialObjectMetaExt for ObjectMeta {
    fn into_request_partial<K: Resource<DynamicType = ()>>(self) -> PartialObjectMeta<K> {
        PartialObjectMeta {
            types: Some(TypeMeta {
                api_version: K::api_version(&()).into(),
                kind: K::kind(&()).into(),
            }),
            metadata: self,
            _phantom: PhantomData,
        }
    }

    fn into_response_partial<K>(self) -> PartialObjectMeta<K> {
        PartialObjectMeta {
            types: Some(TypeMeta {
                api_version: "meta.k8s.io/v1".to_string(),
                kind: "PartialObjectMetadata".to_string(),
            }),
            metadata: self,
            _phantom: PhantomData,
        }
    }
}

impl<K: Resource> Resource for PartialObjectMeta<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.metadata
    }

    fn meta_mut(&mut self) -> &mut ObjectMeta {
        &mut self.metadata
    }
}

#[cfg(test)]
mod test {
    use super::{ObjectMeta, PartialObjectMeta, PartialObjectMetaExt};
    use crate::Resource;
    use k8s_openapi::api::core::v1::Pod;

    #[test]
    fn can_convert_and_derive_partial_metadata() {
        // can use generic type for static dispatch;
        assert_eq!(PartialObjectMeta::<Pod>::kind(&()), "Pod");
        assert_eq!(PartialObjectMeta::<Pod>::api_version(&()), "v1");

        // can convert from objectmeta to partials for different use cases:
        let meta = ObjectMeta {
            name: Some("mypod".into()),
            ..Default::default()
        };
        let request_pom = meta.clone().into_request_partial::<Pod>();
        let response_pom = meta.into_response_partial::<Pod>();

        // they both basically just inline the metadata;
        assert_eq!(request_pom.metadata.name, Some("mypod".to_string()));
        assert_eq!(response_pom.metadata.name, Some("mypod".to_string()));

        // the request_pom will use the TypeMeta from K to support POST/PUT requests
        assert_eq!(request_pom.types.as_ref().unwrap().api_version, "v1");
        assert_eq!(request_pom.types.as_ref().unwrap().kind, "Pod");

        // but the response_pom will use the type-erased kinds from the apiserver
        assert_eq!(response_pom.types.as_ref().unwrap().api_version, "meta.k8s.io/v1");
        assert_eq!(response_pom.types.as_ref().unwrap().kind, "PartialObjectMetadata");
    }
}