1use derive_builder::Builder;
29use http::{HeaderMap, HeaderName, HeaderValue};
30
31use crate::api::rest_endpoint_prelude::*;
32
33use crate::api::common::serialize_sensitive_optional_string;
34use crate::api::common::serialize_sensitive_string;
35use secrecy::SecretString;
36use serde::Deserialize;
37use serde::Serialize;
38use std::borrow::Cow;
39
40#[derive(Debug, Deserialize, Clone, Serialize)]
41pub enum Methods {
42 #[serde(rename = "application_credential")]
43 ApplicationCredential,
44 #[serde(rename = "password")]
45 Password,
46 #[serde(rename = "token")]
47 Token,
48 #[serde(rename = "totp")]
49 Totp,
50}
51
52#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
54#[builder(setter(strip_option))]
55pub struct Domain<'a> {
56 #[serde(skip_serializing_if = "Option::is_none")]
58 #[builder(default, setter(into))]
59 pub(crate) id: Option<Cow<'a, str>>,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
63 #[builder(default, setter(into))]
64 pub(crate) name: Option<Cow<'a, str>>,
65}
66
67#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
69#[builder(setter(strip_option))]
70pub struct User<'a> {
71 #[serde(skip_serializing_if = "Option::is_none")]
73 #[builder(default, setter(into))]
74 pub(crate) domain: Option<Domain<'a>>,
75
76 #[serde(skip_serializing_if = "Option::is_none")]
78 #[builder(default, setter(into))]
79 pub(crate) id: Option<Cow<'a, str>>,
80
81 #[serde(skip_serializing_if = "Option::is_none")]
85 #[builder(default, setter(into))]
86 pub(crate) name: Option<Cow<'a, str>>,
87
88 #[serde(
90 serialize_with = "serialize_sensitive_optional_string",
91 skip_serializing_if = "Option::is_none"
92 )]
93 #[builder(default, setter(into))]
94 pub(crate) password: Option<SecretString>,
95}
96
97#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
99#[builder(setter(strip_option))]
100pub struct Password<'a> {
101 #[serde(skip_serializing_if = "Option::is_none")]
103 #[builder(default, setter(into))]
104 pub(crate) user: Option<User<'a>>,
105}
106
107#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
109#[builder(setter(strip_option))]
110pub struct Token {
111 #[serde(serialize_with = "serialize_sensitive_string")]
113 #[builder(setter(into))]
114 pub(crate) id: SecretString,
115}
116
117#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
118#[builder(setter(strip_option))]
119pub struct TotpUser<'a> {
120 #[serde(skip_serializing_if = "Option::is_none")]
122 #[builder(default, setter(into))]
123 pub(crate) domain: Option<Domain<'a>>,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
127 #[builder(default, setter(into))]
128 pub(crate) id: Option<Cow<'a, str>>,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
132 #[builder(default, setter(into))]
133 pub(crate) name: Option<Cow<'a, str>>,
134
135 #[serde(serialize_with = "serialize_sensitive_string")]
137 #[builder(setter(into))]
138 pub(crate) passcode: SecretString,
139}
140
141#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
143#[builder(setter(strip_option))]
144pub struct Totp<'a> {
145 #[serde()]
146 #[builder(setter(into))]
147 pub(crate) user: TotpUser<'a>,
148}
149
150#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
153#[builder(setter(strip_option))]
154pub struct ApplicationCredentialUser<'a> {
155 #[serde(skip_serializing_if = "Option::is_none")]
157 #[builder(default, setter(into))]
158 pub(crate) domain: Option<Domain<'a>>,
159
160 #[serde(skip_serializing_if = "Option::is_none")]
162 #[builder(default, setter(into))]
163 pub(crate) id: Option<Cow<'a, str>>,
164
165 #[serde(skip_serializing_if = "Option::is_none")]
167 #[builder(default, setter(into))]
168 pub(crate) name: Option<Cow<'a, str>>,
169}
170
171#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
173#[builder(setter(strip_option))]
174pub struct ApplicationCredential<'a> {
175 #[serde(skip_serializing_if = "Option::is_none")]
176 #[builder(default, setter(into))]
177 pub(crate) id: Option<Cow<'a, str>>,
178
179 #[serde(skip_serializing_if = "Option::is_none")]
180 #[builder(default, setter(into))]
181 pub(crate) name: Option<Cow<'a, str>>,
182
183 #[serde(serialize_with = "serialize_sensitive_string")]
185 #[builder(setter(into))]
186 pub(crate) secret: SecretString,
187
188 #[serde(skip_serializing_if = "Option::is_none")]
191 #[builder(default, setter(into))]
192 pub(crate) user: Option<ApplicationCredentialUser<'a>>,
193}
194
195#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
197#[builder(setter(strip_option))]
198pub struct Identity<'a> {
199 #[serde(skip_serializing_if = "Option::is_none")]
201 #[builder(default, setter(into))]
202 pub(crate) application_credential: Option<ApplicationCredential<'a>>,
203
204 #[serde()]
207 #[builder(setter(into))]
208 pub(crate) methods: Vec<Methods>,
209
210 #[serde(skip_serializing_if = "Option::is_none")]
212 #[builder(default, setter(into))]
213 pub(crate) password: Option<Password<'a>>,
214
215 #[serde(skip_serializing_if = "Option::is_none")]
217 #[builder(default, setter(into))]
218 pub(crate) token: Option<Token>,
219
220 #[serde(skip_serializing_if = "Option::is_none")]
222 #[builder(default, setter(into))]
223 pub(crate) totp: Option<Totp<'a>>,
224}
225
226#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
227#[builder(setter(strip_option))]
228pub struct ProjectDomain<'a> {
229 #[serde(skip_serializing_if = "Option::is_none")]
231 #[builder(default, setter(into))]
232 pub(crate) id: Option<Cow<'a, str>>,
233
234 #[serde(skip_serializing_if = "Option::is_none")]
236 #[builder(default, setter(into))]
237 pub(crate) name: Option<Cow<'a, str>>,
238}
239
240#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
241#[builder(setter(strip_option))]
242pub struct Project<'a> {
243 #[serde(skip_serializing_if = "Option::is_none")]
244 #[builder(default, setter(into))]
245 pub(crate) domain: Option<ProjectDomain<'a>>,
246
247 #[serde(skip_serializing_if = "Option::is_none")]
249 #[builder(default, setter(into))]
250 pub(crate) id: Option<Cow<'a, str>>,
251
252 #[serde(skip_serializing_if = "Option::is_none")]
254 #[builder(default, setter(into))]
255 pub(crate) name: Option<Cow<'a, str>>,
256}
257
258#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
259#[builder(setter(strip_option))]
260pub struct ScopeDomain<'a> {
261 #[serde(skip_serializing_if = "Option::is_none")]
263 #[builder(default, setter(into))]
264 pub(crate) id: Option<Cow<'a, str>>,
265
266 #[serde(skip_serializing_if = "Option::is_none")]
268 #[builder(default, setter(into))]
269 pub(crate) name: Option<Cow<'a, str>>,
270}
271
272#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
273#[builder(setter(strip_option))]
274pub struct OsTrustTrust<'a> {
275 #[serde(skip_serializing_if = "Option::is_none")]
276 #[builder(default, setter(into))]
277 pub(crate) id: Option<Cow<'a, str>>,
278}
279
280#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
281#[builder(setter(strip_option))]
282pub struct System {
283 #[serde(skip_serializing_if = "Option::is_none")]
284 #[builder(default, setter(into))]
285 pub(crate) all: Option<bool>,
286}
287
288#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
298#[builder(setter(strip_option))]
299pub struct Scope<'a> {
300 #[serde(skip_serializing_if = "Option::is_none")]
301 #[builder(default, setter(into))]
302 pub(crate) domain: Option<ScopeDomain<'a>>,
303
304 #[serde(rename = "OS-TRUST:trust", skip_serializing_if = "Option::is_none")]
305 #[builder(default, setter(into))]
306 pub(crate) os_trust_trust: Option<OsTrustTrust<'a>>,
307
308 #[serde(skip_serializing_if = "Option::is_none")]
309 #[builder(default, setter(into))]
310 pub(crate) project: Option<Project<'a>>,
311
312 #[serde(skip_serializing_if = "Option::is_none")]
313 #[builder(default, setter(into))]
314 pub(crate) system: Option<System>,
315}
316
317#[derive(Builder, Debug, Deserialize, Clone, Serialize)]
319#[builder(setter(strip_option))]
320pub struct Auth<'a> {
321 #[serde()]
323 #[builder(setter(into))]
324 pub(crate) identity: Identity<'a>,
325
326 #[serde(skip_serializing_if = "Option::is_none")]
336 #[builder(default, setter(into))]
337 pub(crate) scope: Option<Scope<'a>>,
338}
339
340#[derive(Builder, Debug, Clone)]
341#[builder(setter(strip_option))]
342pub struct Request<'a> {
343 #[builder(setter(into))]
345 pub(crate) auth: Auth<'a>,
346
347 #[builder(setter(name = "_headers"), default, private)]
348 _headers: Option<HeaderMap>,
349}
350impl<'a> Request<'a> {
351 pub fn builder() -> RequestBuilder<'a> {
353 RequestBuilder::default()
354 }
355}
356
357impl RequestBuilder<'_> {
358 pub fn header(&mut self, header_name: &'static str, header_value: &'static str) -> &mut Self
360where {
361 self._headers
362 .get_or_insert(None)
363 .get_or_insert_with(HeaderMap::new)
364 .insert(header_name, HeaderValue::from_static(header_value));
365 self
366 }
367
368 pub fn headers<I, T>(&mut self, iter: I) -> &mut Self
370 where
371 I: Iterator<Item = T>,
372 T: Into<(Option<HeaderName>, HeaderValue)>,
373 {
374 self._headers
375 .get_or_insert(None)
376 .get_or_insert_with(HeaderMap::new)
377 .extend(iter.map(Into::into));
378 self
379 }
380}
381
382impl RestEndpoint for Request<'_> {
383 fn method(&self) -> http::Method {
384 http::Method::POST
385 }
386
387 fn endpoint(&self) -> Cow<'static, str> {
388 "auth/tokens".to_string().into()
389 }
390
391 fn parameters(&self) -> QueryParams {
392 QueryParams::default()
393 }
394
395 fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
396 let mut params = JsonBodyParams::default();
397
398 params.push("auth", serde_json::to_value(&self.auth)?);
399
400 params.into_body()
401 }
402
403 fn service_type(&self) -> ServiceType {
404 ServiceType::Identity
405 }
406
407 fn response_key(&self) -> Option<Cow<'static, str>> {
408 Some("token".into())
409 }
410
411 fn request_headers(&self) -> Option<&HeaderMap> {
413 self._headers.as_ref()
414 }
415
416 fn api_version(&self) -> Option<ApiVersion> {
418 Some(ApiVersion::new(3, 0))
419 }
420}
421
422#[cfg(test)]
423mod tests {
424 use super::*;
425 #[cfg(feature = "sync")]
426 use crate::api::Query;
427 use crate::test::client::FakeOpenStackClient;
428 use crate::types::ServiceType;
429 use http::{HeaderName, HeaderValue};
430 use httpmock::MockServer;
431 use serde_json::json;
432
433 #[test]
434 fn test_service_type() {
435 assert_eq!(
436 Request::builder()
437 .auth(
438 AuthBuilder::default()
439 .identity(
440 IdentityBuilder::default()
441 .methods(Vec::from([Methods::ApplicationCredential]))
442 .build()
443 .unwrap()
444 )
445 .build()
446 .unwrap()
447 )
448 .build()
449 .unwrap()
450 .service_type(),
451 ServiceType::Identity
452 );
453 }
454
455 #[test]
456 fn test_response_key() {
457 assert_eq!(
458 Request::builder()
459 .auth(
460 AuthBuilder::default()
461 .identity(
462 IdentityBuilder::default()
463 .methods(Vec::from([Methods::ApplicationCredential]))
464 .build()
465 .unwrap()
466 )
467 .build()
468 .unwrap()
469 )
470 .build()
471 .unwrap()
472 .response_key()
473 .unwrap(),
474 "token"
475 );
476 }
477
478 #[cfg(feature = "sync")]
479 #[test]
480 fn endpoint() {
481 let server = MockServer::start();
482 let client = FakeOpenStackClient::new(server.base_url());
483 let mock = server.mock(|when, then| {
484 when.method(httpmock::Method::POST)
485 .path("/auth/tokens".to_string());
486
487 then.status(200)
488 .header("content-type", "application/json")
489 .json_body(json!({ "token": {} }));
490 });
491
492 let endpoint = Request::builder()
493 .auth(
494 AuthBuilder::default()
495 .identity(
496 IdentityBuilder::default()
497 .methods(Vec::from([Methods::ApplicationCredential]))
498 .build()
499 .unwrap(),
500 )
501 .build()
502 .unwrap(),
503 )
504 .build()
505 .unwrap();
506 let _: serde_json::Value = endpoint.query(&client).unwrap();
507 mock.assert();
508 }
509
510 #[cfg(feature = "sync")]
511 #[test]
512 fn endpoint_headers() {
513 let server = MockServer::start();
514 let client = FakeOpenStackClient::new(server.base_url());
515 let mock = server.mock(|when, then| {
516 when.method(httpmock::Method::POST)
517 .path("/auth/tokens".to_string())
518 .header("foo", "bar")
519 .header("not_foo", "not_bar");
520 then.status(200)
521 .header("content-type", "application/json")
522 .json_body(json!({ "token": {} }));
523 });
524
525 let endpoint = Request::builder()
526 .auth(
527 AuthBuilder::default()
528 .identity(
529 IdentityBuilder::default()
530 .methods(Vec::from([Methods::ApplicationCredential]))
531 .build()
532 .unwrap(),
533 )
534 .build()
535 .unwrap(),
536 )
537 .headers(
538 [(
539 Some(HeaderName::from_static("foo")),
540 HeaderValue::from_static("bar"),
541 )]
542 .into_iter(),
543 )
544 .header("not_foo", "not_bar")
545 .build()
546 .unwrap();
547 let _: serde_json::Value = endpoint.query(&client).unwrap();
548 mock.assert();
549 }
550}