1pub use crate::discovery::ApiResource;
5use crate::{
6 metadata::TypeMeta,
7 resource::{DynamicResourceScope, Resource},
8};
9
10use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
11use std::borrow::Cow;
12use thiserror::Error;
13
14#[derive(Debug, Error)]
15#[error("failed to parse this DynamicObject into a Resource: {source}")]
16pub struct ParseDynamicObjectError {
18 #[from]
19 source: serde_json::Error,
20}
21
22#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq)]
26pub struct DynamicObject {
27 #[serde(flatten, default)]
29 pub types: Option<TypeMeta>,
30 #[serde(default)]
32 pub metadata: ObjectMeta,
33
34 #[serde(flatten)]
36 pub data: serde_json::Value,
37}
38
39impl DynamicObject {
40 #[must_use]
42 pub fn new(name: &str, resource: &ApiResource) -> Self {
43 Self {
44 types: Some(TypeMeta {
45 api_version: resource.api_version.to_string(),
46 kind: resource.kind.to_string(),
47 }),
48 metadata: ObjectMeta {
49 name: Some(name.to_string()),
50 ..Default::default()
51 },
52 data: Default::default(),
53 }
54 }
55
56 #[must_use]
58 pub fn data(mut self, data: serde_json::Value) -> Self {
59 self.data = data;
60 self
61 }
62
63 #[must_use]
65 pub fn within(mut self, ns: &str) -> Self {
66 self.metadata.namespace = Some(ns.into());
67 self
68 }
69
70 pub fn try_parse<K: Resource + for<'a> serde::Deserialize<'a>>(
72 self,
73 ) -> Result<K, ParseDynamicObjectError> {
74 Ok(serde_json::from_value(serde_json::to_value(self)?)?)
75 }
76}
77
78impl Resource for DynamicObject {
79 type DynamicType = ApiResource;
80 type Scope = DynamicResourceScope;
81
82 fn group(dt: &ApiResource) -> Cow<'_, str> {
83 dt.group.as_str().into()
84 }
85
86 fn version(dt: &ApiResource) -> Cow<'_, str> {
87 dt.version.as_str().into()
88 }
89
90 fn kind(dt: &ApiResource) -> Cow<'_, str> {
91 dt.kind.as_str().into()
92 }
93
94 fn api_version(dt: &ApiResource) -> Cow<'_, str> {
95 dt.api_version.as_str().into()
96 }
97
98 fn plural(dt: &ApiResource) -> Cow<'_, str> {
99 dt.plural.as_str().into()
100 }
101
102 fn meta(&self) -> &ObjectMeta {
103 &self.metadata
104 }
105
106 fn meta_mut(&mut self) -> &mut ObjectMeta {
107 &mut self.metadata
108 }
109}
110
111#[cfg(test)]
112mod test {
113 use crate::{
114 dynamic::{ApiResource, DynamicObject},
115 gvk::GroupVersionKind,
116 params::{Patch, PatchParams, PostParams},
117 request::Request,
118 resource::Resource,
119 };
120 use k8s_openapi::api::core::v1::Pod;
121
122 #[test]
123 fn raw_custom_resource() {
124 let gvk = GroupVersionKind::gvk("clux.dev", "v1", "Foo");
125 let res = ApiResource::from_gvk(&gvk);
126 let url = DynamicObject::url_path(&res, Some("myns"));
127
128 let pp = PostParams::default();
129 let req = Request::new(&url).create(&pp, vec![]).unwrap();
130 assert_eq!(req.uri(), "/apis/clux.dev/v1/namespaces/myns/foos?");
131 let patch_params = PatchParams::default();
132 let req = Request::new(url)
133 .patch("baz", &patch_params, &Patch::Merge(()))
134 .unwrap();
135 assert_eq!(req.uri(), "/apis/clux.dev/v1/namespaces/myns/foos/baz?");
136 assert_eq!(req.method(), "PATCH");
137 }
138
139 #[test]
140 fn raw_resource_in_default_group() {
141 let gvk = GroupVersionKind::gvk("", "v1", "Service");
142 let api_resource = ApiResource::from_gvk(&gvk);
143 let url = DynamicObject::url_path(&api_resource, None);
144 let pp = PostParams::default();
145 let req = Request::new(url).create(&pp, vec![]).unwrap();
146 assert_eq!(req.uri(), "/api/v1/services?");
147 }
148
149 #[test]
150 fn can_parse_dynamic_object_into_pod() -> Result<(), serde_json::Error> {
151 let original_pod: Pod = serde_json::from_value(serde_json::json!({
152 "apiVersion": "v1",
153 "kind": "Pod",
154 "metadata": { "name": "example" },
155 "spec": {
156 "containers": [{
157 "name": "example",
158 "image": "alpine",
159 "command": ["tail", "-f", "/dev/null"],
161 }],
162 }
163 }))?;
164 let dynamic_pod: DynamicObject = serde_json::from_str(&serde_json::to_string(&original_pod)?)?;
165 let parsed_pod: Pod = dynamic_pod.try_parse().unwrap();
166
167 assert_eq!(parsed_pod, original_pod);
168
169 Ok(())
170 }
171}