1use derive_builder::Builder;
19use http::{HeaderMap, HeaderName, HeaderValue};
20
21use crate::api::rest_endpoint_prelude::*;
22
23use serde::Deserialize;
24use serde::Serialize;
25use serde_json::Value;
26use std::borrow::Cow;
27use std::collections::BTreeMap;
28
29#[derive(Debug, Deserialize, Clone, Serialize)]
30pub enum Visibility {
31 #[serde(rename = "private")]
32 Private,
33 #[serde(rename = "public")]
34 Public,
35}
36
37#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
38#[builder(setter(strip_option))]
39pub struct ResourceTypeAssociations<'a> {
40 #[serde(skip_serializing_if = "Option::is_none")]
41 #[builder(default, setter(into))]
42 pub(crate) name: Option<Cow<'a, str>>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
45 #[builder(default, setter(into))]
46 pub(crate) prefix: Option<Cow<'a, str>>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
49 #[builder(default, setter(into))]
50 pub(crate) properties_target: Option<Cow<'a, str>>,
51}
52
53#[derive(Debug, Deserialize, Clone, Serialize)]
54pub enum Type {
55 #[serde(rename = "array")]
56 Array,
57 #[serde(rename = "boolean")]
58 Boolean,
59 #[serde(rename = "integer")]
60 Integer,
61 #[serde(rename = "number")]
62 Number,
63 #[serde(rename = "object")]
64 Object,
65 #[serde(rename = "string")]
66 String,
67}
68
69#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
70#[builder(setter(strip_option))]
71pub struct Items<'a> {
72 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
73 #[builder(default, setter(into))]
74 pub(crate) _enum: Option<Vec<Cow<'a, str>>>,
75
76 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
77 #[builder(default)]
78 pub(crate) _type: Option<Type>,
79}
80
81#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
82#[builder(setter(strip_option))]
83pub struct Properties<'a> {
84 #[serde(rename = "additionalItems", skip_serializing_if = "Option::is_none")]
85 #[builder(default, setter(into))]
86 pub(crate) additional_items: Option<bool>,
87
88 #[serde(rename = "default", skip_serializing_if = "Option::is_none")]
89 #[builder(default, setter(into))]
90 pub(crate) _default: Option<Value>,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
93 #[builder(default, setter(into))]
94 pub(crate) description: Option<Cow<'a, str>>,
95
96 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
97 #[builder(default, setter(into))]
98 pub(crate) _enum: Option<Vec<Cow<'a, str>>>,
99
100 #[serde(skip_serializing_if = "Option::is_none")]
101 #[builder(default, setter(into))]
102 pub(crate) items: Option<Items<'a>>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
105 #[builder(default, setter(into))]
106 pub(crate) maximum: Option<f32>,
107
108 #[serde(rename = "maxItems", skip_serializing_if = "Option::is_none")]
109 #[builder(default, setter(into))]
110 pub(crate) max_items: Option<i32>,
111
112 #[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
113 #[builder(default, setter(into))]
114 pub(crate) max_length: Option<i32>,
115
116 #[serde(skip_serializing_if = "Option::is_none")]
117 #[builder(default, setter(into))]
118 pub(crate) minimum: Option<f32>,
119
120 #[serde(rename = "minItems", skip_serializing_if = "Option::is_none")]
121 #[builder(default, setter(into))]
122 pub(crate) min_items: Option<i32>,
123
124 #[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
125 #[builder(default, setter(into))]
126 pub(crate) min_length: Option<i32>,
127
128 #[serde(skip_serializing_if = "Option::is_none")]
129 #[builder(default, setter(into))]
130 pub(crate) name: Option<Cow<'a, str>>,
131
132 #[serde(skip_serializing_if = "Option::is_none")]
133 #[builder(default, setter(into))]
134 pub(crate) operators: Option<Vec<Cow<'a, str>>>,
135
136 #[serde(skip_serializing_if = "Option::is_none")]
137 #[builder(default, setter(into))]
138 pub(crate) pattern: Option<Cow<'a, str>>,
139
140 #[serde(skip_serializing_if = "Option::is_none")]
141 #[builder(default, setter(into))]
142 pub(crate) readonly: Option<bool>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
145 #[builder(default, setter(into))]
146 pub(crate) required: Option<Vec<Cow<'a, str>>>,
147
148 #[serde()]
149 #[builder(setter(into))]
150 pub(crate) title: Cow<'a, str>,
151
152 #[serde(rename = "type")]
153 #[builder()]
154 pub(crate) _type: Type,
155
156 #[serde(rename = "uniqueItems", skip_serializing_if = "Option::is_none")]
157 #[builder(default, setter(into))]
158 pub(crate) unique_items: Option<bool>,
159}
160
161#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
162#[builder(setter(strip_option))]
163pub struct Objects<'a> {
164 #[serde(skip_serializing_if = "Option::is_none")]
165 #[builder(default, setter(into))]
166 pub(crate) description: Option<Cow<'a, str>>,
167
168 #[serde(skip_serializing_if = "Option::is_none")]
169 #[builder(default, setter(into))]
170 pub(crate) name: Option<Cow<'a, str>>,
171
172 #[serde(skip_serializing_if = "Option::is_none")]
173 #[builder(default, private, setter(into, name = "_properties"))]
174 pub(crate) properties: Option<BTreeMap<Cow<'a, str>, Properties<'a>>>,
175
176 #[serde(skip_serializing_if = "Option::is_none")]
177 #[builder(default, setter(into))]
178 pub(crate) required: Option<Vec<Cow<'a, str>>>,
179}
180
181impl<'a> ObjectsBuilder<'a> {
182 pub fn properties<I, K, V>(&mut self, iter: I) -> &mut Self
183 where
184 I: Iterator<Item = (K, V)>,
185 K: Into<Cow<'a, str>>,
186 V: Into<Properties<'a>>,
187 {
188 self.properties
189 .get_or_insert(None)
190 .get_or_insert_with(BTreeMap::new)
191 .extend(iter.map(|(k, v)| (k.into(), v.into())));
192 self
193 }
194}
195
196#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
197#[builder(setter(strip_option))]
198pub struct Tags<'a> {
199 #[serde(skip_serializing_if = "Option::is_none")]
200 #[builder(default, setter(into))]
201 pub(crate) name: Option<Cow<'a, str>>,
202}
203
204#[derive(Builder, Debug, Clone)]
205#[builder(setter(strip_option))]
206pub struct Request<'a> {
207 #[builder(default, setter(into))]
209 pub(crate) description: Option<Cow<'a, str>>,
210
211 #[builder(default, setter(into))]
213 pub(crate) display_name: Option<Cow<'a, str>>,
214
215 #[builder(setter(into))]
217 pub(crate) namespace: Cow<'a, str>,
218
219 #[builder(default, setter(into))]
220 pub(crate) objects: Option<Vec<Objects<'a>>>,
221
222 #[builder(default, setter(into))]
224 pub(crate) owner: Option<Cow<'a, str>>,
225
226 #[builder(default, private, setter(into, name = "_properties"))]
227 pub(crate) properties: Option<BTreeMap<Cow<'a, str>, Properties<'a>>>,
228
229 #[builder(default, setter(into))]
231 pub(crate) protected: Option<bool>,
232
233 #[builder(default, setter(into))]
234 pub(crate) resource_type_associations: Option<Vec<ResourceTypeAssociations<'a>>>,
235
236 #[builder(default, setter(into))]
237 pub(crate) tags: Option<Vec<Tags<'a>>>,
238
239 #[builder(default)]
241 pub(crate) visibility: Option<Visibility>,
242
243 #[builder(setter(name = "_headers"), default, private)]
244 _headers: Option<HeaderMap>,
245}
246impl<'a> Request<'a> {
247 pub fn builder() -> RequestBuilder<'a> {
249 RequestBuilder::default()
250 }
251}
252
253impl<'a> RequestBuilder<'a> {
254 pub fn properties<I, K, V>(&mut self, iter: I) -> &mut Self
255 where
256 I: Iterator<Item = (K, V)>,
257 K: Into<Cow<'a, str>>,
258 V: Into<Properties<'a>>,
259 {
260 self.properties
261 .get_or_insert(None)
262 .get_or_insert_with(BTreeMap::new)
263 .extend(iter.map(|(k, v)| (k.into(), v.into())));
264 self
265 }
266
267 pub fn header(&mut self, header_name: &'static str, header_value: &'static str) -> &mut Self
269where {
270 self._headers
271 .get_or_insert(None)
272 .get_or_insert_with(HeaderMap::new)
273 .insert(header_name, HeaderValue::from_static(header_value));
274 self
275 }
276
277 pub fn headers<I, T>(&mut self, iter: I) -> &mut Self
279 where
280 I: Iterator<Item = T>,
281 T: Into<(Option<HeaderName>, HeaderValue)>,
282 {
283 self._headers
284 .get_or_insert(None)
285 .get_or_insert_with(HeaderMap::new)
286 .extend(iter.map(Into::into));
287 self
288 }
289}
290
291impl RestEndpoint for Request<'_> {
292 fn method(&self) -> http::Method {
293 http::Method::POST
294 }
295
296 fn endpoint(&self) -> Cow<'static, str> {
297 "metadefs/namespaces".to_string().into()
298 }
299
300 fn parameters(&self) -> QueryParams {
301 QueryParams::default()
302 }
303
304 fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
305 let mut params = JsonBodyParams::default();
306
307 params.push("namespace", serde_json::to_value(&self.namespace)?);
308 if let Some(val) = &self.display_name {
309 params.push("display_name", serde_json::to_value(val)?);
310 }
311 if let Some(val) = &self.description {
312 params.push("description", serde_json::to_value(val)?);
313 }
314 if let Some(val) = &self.visibility {
315 params.push("visibility", serde_json::to_value(val)?);
316 }
317 if let Some(val) = &self.protected {
318 params.push("protected", serde_json::to_value(val)?);
319 }
320 if let Some(val) = &self.owner {
321 params.push("owner", serde_json::to_value(val)?);
322 }
323 if let Some(val) = &self.resource_type_associations {
324 params.push("resource_type_associations", serde_json::to_value(val)?);
325 }
326 if let Some(val) = &self.properties {
327 params.push("properties", serde_json::to_value(val)?);
328 }
329 if let Some(val) = &self.objects {
330 params.push("objects", serde_json::to_value(val)?);
331 }
332 if let Some(val) = &self.tags {
333 params.push("tags", serde_json::to_value(val)?);
334 }
335
336 params.into_body()
337 }
338
339 fn service_type(&self) -> ServiceType {
340 ServiceType::Image
341 }
342
343 fn response_key(&self) -> Option<Cow<'static, str>> {
344 None
345 }
346
347 fn request_headers(&self) -> Option<&HeaderMap> {
349 self._headers.as_ref()
350 }
351
352 fn api_version(&self) -> Option<ApiVersion> {
354 Some(ApiVersion::new(2, 0))
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 #[cfg(feature = "sync")]
362 use crate::api::Query;
363 use crate::test::client::FakeOpenStackClient;
364 use crate::types::ServiceType;
365 use http::{HeaderName, HeaderValue};
366 use httpmock::MockServer;
367 use serde_json::json;
368
369 #[test]
370 fn test_service_type() {
371 assert_eq!(
372 Request::builder()
373 .namespace("foo")
374 .build()
375 .unwrap()
376 .service_type(),
377 ServiceType::Image
378 );
379 }
380
381 #[test]
382 fn test_response_key() {
383 assert!(Request::builder()
384 .namespace("foo")
385 .build()
386 .unwrap()
387 .response_key()
388 .is_none())
389 }
390
391 #[cfg(feature = "sync")]
392 #[test]
393 fn endpoint() {
394 let server = MockServer::start();
395 let client = FakeOpenStackClient::new(server.base_url());
396 let mock = server.mock(|when, then| {
397 when.method(httpmock::Method::POST)
398 .path("/metadefs/namespaces".to_string());
399
400 then.status(200)
401 .header("content-type", "application/json")
402 .json_body(json!({ "dummy": {} }));
403 });
404
405 let endpoint = Request::builder().namespace("foo").build().unwrap();
406 let _: serde_json::Value = endpoint.query(&client).unwrap();
407 mock.assert();
408 }
409
410 #[cfg(feature = "sync")]
411 #[test]
412 fn endpoint_headers() {
413 let server = MockServer::start();
414 let client = FakeOpenStackClient::new(server.base_url());
415 let mock = server.mock(|when, then| {
416 when.method(httpmock::Method::POST)
417 .path("/metadefs/namespaces".to_string())
418 .header("foo", "bar")
419 .header("not_foo", "not_bar");
420 then.status(200)
421 .header("content-type", "application/json")
422 .json_body(json!({ "dummy": {} }));
423 });
424
425 let endpoint = Request::builder()
426 .namespace("foo")
427 .headers(
428 [(
429 Some(HeaderName::from_static("foo")),
430 HeaderValue::from_static("bar"),
431 )]
432 .into_iter(),
433 )
434 .header("not_foo", "not_bar")
435 .build()
436 .unwrap();
437 let _: serde_json::Value = endpoint.query(&client).unwrap();
438 mock.assert();
439 }
440}