mz_cloud_api/
client.rs
1use std::sync::Arc;
19
20use reqwest::{Method, RequestBuilder, StatusCode, Url, header::HeaderMap};
21use serde::Deserialize;
22use serde::de::DeserializeOwned;
23
24use crate::config::API_VERSION_HEADER;
25use crate::error::{ApiError, Error};
26
27use self::cloud_provider::CloudProvider;
28
29pub struct Client {
31 pub(crate) inner: reqwest::Client,
32 pub(crate) auth_client: Arc<mz_frontegg_client::client::Client>,
33 pub(crate) endpoint: Url,
34}
35
36pub mod cloud_provider;
37pub mod region;
38
39impl Client {
51 async fn build_global_request<P>(
53 &self,
54 method: Method,
55 path: P,
56 query: Option<&[(&str, &str)]>,
57 ) -> Result<RequestBuilder, Error>
58 where
59 P: IntoIterator,
60 P::Item: AsRef<str>,
61 {
62 self.build_request(method, path, query, self.endpoint.clone(), None)
63 .await
64 }
65
66 async fn build_region_request<P>(
74 &self,
75 method: Method,
76 path: P,
77 query: Option<&[(&str, &str)]>,
78 cloud_provider: &CloudProvider,
79 api_version: Option<u16>,
80 ) -> Result<RequestBuilder, Error>
81 where
82 P: IntoIterator,
83 P::Item: AsRef<str>,
84 {
85 self.build_request(
86 method,
87 path,
88 query,
89 cloud_provider.url.clone(),
90 api_version.and_then(|api_ver| {
91 let mut headers = HeaderMap::with_capacity(1);
92 headers.insert(API_VERSION_HEADER, api_ver.into());
93 Some(headers)
94 }),
95 )
96 .await
97 }
98
99 async fn build_request<P>(
101 &self,
102 method: Method,
103 path: P,
104 query: Option<&[(&str, &str)]>,
105 mut domain: Url,
106 headers: Option<HeaderMap>,
107 ) -> Result<RequestBuilder, Error>
108 where
109 P: IntoIterator,
110 P::Item: AsRef<str>,
111 {
112 domain
113 .path_segments_mut()
114 .or(Err(Error::UrlBaseError))?
115 .clear()
116 .extend(path);
117
118 let mut req = self.inner.request(method, domain);
119 if let Some(header_map) = headers {
120 req = req.headers(header_map);
121 }
122 if let Some(query_params) = query {
123 req = req.query(&query_params);
124 }
125 let token = self.auth_client.auth().await?;
126
127 Ok(req.bearer_auth(token))
128 }
129
130 async fn send_request<T>(&self, req: RequestBuilder) -> Result<T, Error>
131 where
132 T: DeserializeOwned,
133 {
134 #[derive(Deserialize)]
135 #[serde(rename_all = "camelCase")]
136 struct ErrorResponse {
137 #[serde(default)]
138 message: Option<String>,
139 #[serde(default)]
140 errors: Vec<String>,
141 }
142
143 let res = req.send().await?;
144 let status_code = res.status();
145 if status_code.is_success() {
146 if status_code == StatusCode::NO_CONTENT {
147 Err(Error::SuccesfullButNoContent)
148 } else {
149 Ok(res.json().await?)
150 }
151 } else {
152 match res.json::<ErrorResponse>().await {
153 Ok(e) => {
154 let mut messages = e.errors;
155 messages.extend(e.message);
156 Err(Error::Api(ApiError {
157 status_code,
158 messages,
159 }))
160 }
161 Err(_) => Err(Error::Api(ApiError {
162 status_code,
163 messages: vec!["unable to decode error details".into()],
164 })),
165 }
166 }
167 }
168}