1use crate::{
3 discovery::ApiResource,
4 metadata::{ListMeta, ObjectMeta, TypeMeta},
5 resource::{DynamicResourceScope, Resource},
6};
7use serde::{Deserialize, Deserializer, Serialize};
8use std::borrow::Cow;
9
10#[derive(Serialize, Deserialize, Debug, Clone)]
20pub struct ObjectList<T>
21where
22 T: Clone,
23{
24 #[serde(flatten, deserialize_with = "deserialize_v1_list_as_default")]
26 pub types: TypeMeta,
27
28 #[serde(default)]
32 pub metadata: ListMeta,
33
34 #[serde(
36 deserialize_with = "deserialize_null_as_default",
37 bound(deserialize = "Vec<T>: Deserialize<'de>")
38 )]
39 pub items: Vec<T>,
40}
41
42fn deserialize_v1_list_as_default<'de, D>(deserializer: D) -> Result<TypeMeta, D::Error>
43where
44 D: Deserializer<'de>,
45{
46 let meta = Option::<TypeMeta>::deserialize(deserializer)?;
47 Ok(meta.unwrap_or(TypeMeta {
48 api_version: "v1".to_owned(),
49 kind: "List".to_owned(),
50 }))
51}
52
53fn deserialize_null_as_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
54where
55 T: Default + Deserialize<'de>,
56 D: Deserializer<'de>,
57{
58 let opt = Option::deserialize(deserializer)?;
59 Ok(opt.unwrap_or_default())
60}
61
62impl<T: Clone> ObjectList<T> {
63 pub fn iter(&self) -> impl Iterator<Item = &T> {
80 self.items.iter()
81 }
82
83 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
104 self.items.iter_mut()
105 }
106}
107
108impl<T: Clone> IntoIterator for ObjectList<T> {
109 type IntoIter = ::std::vec::IntoIter<Self::Item>;
110 type Item = T;
111
112 fn into_iter(self) -> Self::IntoIter {
113 self.items.into_iter()
114 }
115}
116
117impl<'a, T: Clone> IntoIterator for &'a ObjectList<T> {
118 type IntoIter = ::std::slice::Iter<'a, T>;
119 type Item = &'a T;
120
121 fn into_iter(self) -> Self::IntoIter {
122 self.items.iter()
123 }
124}
125
126impl<'a, T: Clone> IntoIterator for &'a mut ObjectList<T> {
127 type IntoIter = ::std::slice::IterMut<'a, T>;
128 type Item = &'a mut T;
129
130 fn into_iter(self) -> Self::IntoIter {
131 self.items.iter_mut()
132 }
133}
134
135pub trait HasSpec {
145 type Spec;
147
148 fn spec(&self) -> &Self::Spec;
150
151 fn spec_mut(&mut self) -> &mut Self::Spec;
153}
154
155pub trait HasStatus {
165 type Status;
167
168 fn status(&self) -> Option<&Self::Status>;
170
171 fn status_mut(&mut self) -> &mut Option<Self::Status>;
173}
174
175#[derive(Deserialize, Serialize, Clone, Debug)]
186pub struct Object<P, U>
187where
188 P: Clone,
189 U: Clone,
190{
191 #[serde(flatten, default)]
193 pub types: Option<TypeMeta>,
194
195 pub metadata: ObjectMeta,
200
201 pub spec: P,
205
206 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub status: Option<U>,
212}
213
214impl<P, U> Object<P, U>
215where
216 P: Clone,
217 U: Clone,
218{
219 pub fn new(name: &str, ar: &ApiResource, spec: P) -> Self {
221 Self {
222 types: Some(TypeMeta {
223 api_version: ar.api_version.clone(),
224 kind: ar.kind.clone(),
225 }),
226 metadata: ObjectMeta {
227 name: Some(name.to_string()),
228 ..Default::default()
229 },
230 spec,
231 status: None,
232 }
233 }
234
235 #[must_use]
237 pub fn within(mut self, ns: &str) -> Self {
238 self.metadata.namespace = Some(ns.into());
239 self
240 }
241}
242
243impl<P, U> Resource for Object<P, U>
244where
245 P: Clone,
246 U: Clone,
247{
248 type DynamicType = ApiResource;
249 type Scope = DynamicResourceScope;
250
251 fn group(dt: &ApiResource) -> Cow<'_, str> {
252 dt.group.as_str().into()
253 }
254
255 fn version(dt: &ApiResource) -> Cow<'_, str> {
256 dt.version.as_str().into()
257 }
258
259 fn kind(dt: &ApiResource) -> Cow<'_, str> {
260 dt.kind.as_str().into()
261 }
262
263 fn plural(dt: &ApiResource) -> Cow<'_, str> {
264 dt.plural.as_str().into()
265 }
266
267 fn api_version(dt: &ApiResource) -> Cow<'_, str> {
268 dt.api_version.as_str().into()
269 }
270
271 fn meta(&self) -> &ObjectMeta {
272 &self.metadata
273 }
274
275 fn meta_mut(&mut self) -> &mut ObjectMeta {
276 &mut self.metadata
277 }
278}
279
280impl<P, U> HasSpec for Object<P, U>
281where
282 P: Clone,
283 U: Clone,
284{
285 type Spec = P;
286
287 fn spec(&self) -> &Self::Spec {
288 &self.spec
289 }
290
291 fn spec_mut(&mut self) -> &mut Self::Spec {
292 &mut self.spec
293 }
294}
295
296impl<P, U> HasStatus for Object<P, U>
297where
298 P: Clone,
299 U: Clone,
300{
301 type Status = U;
302
303 fn status(&self) -> Option<&Self::Status> {
304 self.status.as_ref()
305 }
306
307 fn status_mut(&mut self) -> &mut Option<Self::Status> {
308 &mut self.status
309 }
310}
311
312#[derive(Clone, Deserialize, Serialize, Default, Debug)]
317pub struct NotUsed {}
318
319#[cfg(test)]
320mod test {
321 use k8s_openapi::apimachinery::pkg::apis::meta::v1::{ListMeta, ObjectMeta};
322
323 use super::{ApiResource, HasSpec, HasStatus, NotUsed, Object, ObjectList, Resource, TypeMeta};
324 use crate::resource::ResourceExt;
325
326 #[test]
327 fn simplified_k8s_object() {
328 use k8s_openapi::api::core::v1::Pod;
329 #[derive(Clone)]
331 struct PodSpecSimple {
332 #[allow(dead_code)]
333 containers: Vec<ContainerSimple>,
334 }
335 #[derive(Clone, Debug, PartialEq)]
336 struct ContainerSimple {
337 #[allow(dead_code)]
338 image: String,
339 }
340 type PodSimple = Object<PodSpecSimple, NotUsed>;
341 let ar = ApiResource::erase::<Pod>(&());
343 assert_eq!(ar.group, "");
344 assert_eq!(ar.kind, "Pod");
345 let data = PodSpecSimple {
346 containers: vec![ContainerSimple { image: "blog".into() }],
347 };
348 let mypod = PodSimple::new("blog", &ar, data).within("dev");
349
350 let meta = mypod.meta();
351 assert_eq!(&mypod.metadata, meta);
352 assert_eq!(meta.namespace.as_ref().unwrap(), "dev");
353 assert_eq!(meta.name.as_ref().unwrap(), "blog");
354 assert_eq!(mypod.types.as_ref().unwrap().kind, "Pod");
355 assert_eq!(mypod.types.as_ref().unwrap().api_version, "v1");
356
357 assert_eq!(mypod.namespace().unwrap(), "dev");
358 assert_eq!(mypod.name_unchecked(), "blog");
359 assert!(mypod.status().is_none());
360 assert_eq!(mypod.spec().containers[0], ContainerSimple {
361 image: "blog".into()
362 });
363
364 assert_eq!(PodSimple::api_version(&ar), "v1");
365 assert_eq!(PodSimple::version(&ar), "v1");
366 assert_eq!(PodSimple::plural(&ar), "pods");
367 assert_eq!(PodSimple::kind(&ar), "Pod");
368 assert_eq!(PodSimple::group(&ar), "");
369 }
370
371 #[test]
372 fn k8s_object_list() {
373 use k8s_openapi::api::core::v1::Pod;
374 let ar = ApiResource::erase::<Pod>(&());
376 assert_eq!(ar.group, "");
377 assert_eq!(ar.kind, "Pod");
378 let podlist: ObjectList<Pod> = ObjectList {
379 types: TypeMeta {
380 api_version: ar.api_version,
381 kind: ar.kind + "List",
382 },
383 metadata: ListMeta { ..Default::default() },
384 items: vec![Pod {
385 metadata: ObjectMeta {
386 name: Some("test".into()),
387 namespace: Some("dev".into()),
388 ..ObjectMeta::default()
389 },
390 spec: None,
391 status: None,
392 }],
393 };
394
395 assert_eq!(&podlist.types.kind, "PodList");
396 assert_eq!(&podlist.types.api_version, "v1");
397
398 let mypod = &podlist.items[0];
399 let meta = mypod.meta();
400 assert_eq!(&mypod.metadata, meta);
401 assert_eq!(meta.namespace.as_ref().unwrap(), "dev");
402 assert_eq!(meta.name.as_ref().unwrap(), "test");
403 assert_eq!(mypod.namespace().unwrap(), "dev");
404 assert_eq!(mypod.name_unchecked(), "test");
405 assert!(mypod.status.is_none());
406 assert!(mypod.spec.is_none());
407 }
408
409 #[test]
410 fn k8s_object_list_default_types() {
411 use k8s_openapi::api::core::v1::Pod;
412
413 let raw_value = serde_json::json!({
414 "metadata": {
415 "resourceVersion": ""
416 },
417 "items": []
418 });
419 let pod_list: ObjectList<Pod> = serde_json::from_value(raw_value).unwrap();
420 assert_eq!(
421 TypeMeta {
422 api_version: "v1".to_owned(),
423 kind: "List".to_owned(),
424 },
425 pod_list.types,
426 );
427 }
428}