1use crate::{CloudError as RestError, Result};
10use reqwest::Client;
11use serde::Serialize;
12use std::sync::Arc;
13
14#[derive(Debug, Clone)]
40pub struct CloudClientBuilder {
41 api_key: Option<String>,
42 api_secret: Option<String>,
43 base_url: String,
44 timeout: std::time::Duration,
45}
46
47impl Default for CloudClientBuilder {
48 fn default() -> Self {
49 Self {
50 api_key: None,
51 api_secret: None,
52 base_url: "https://api.redislabs.com/v1".to_string(),
53 timeout: std::time::Duration::from_secs(30),
54 }
55 }
56}
57
58impl CloudClientBuilder {
59 pub fn new() -> Self {
61 Self::default()
62 }
63
64 pub fn api_key(mut self, key: impl Into<String>) -> Self {
66 self.api_key = Some(key.into());
67 self
68 }
69
70 pub fn api_secret(mut self, secret: impl Into<String>) -> Self {
72 self.api_secret = Some(secret.into());
73 self
74 }
75
76 pub fn base_url(mut self, url: impl Into<String>) -> Self {
78 self.base_url = url.into();
79 self
80 }
81
82 pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
84 self.timeout = timeout;
85 self
86 }
87
88 pub fn build(self) -> Result<CloudClient> {
90 let api_key = self
91 .api_key
92 .ok_or_else(|| RestError::ConnectionError("API key is required".to_string()))?;
93 let api_secret = self
94 .api_secret
95 .ok_or_else(|| RestError::ConnectionError("API secret is required".to_string()))?;
96
97 let client = Client::builder()
98 .timeout(self.timeout)
99 .build()
100 .map_err(|e| RestError::ConnectionError(e.to_string()))?;
101
102 Ok(CloudClient {
103 api_key,
104 api_secret,
105 base_url: self.base_url,
106 timeout: self.timeout,
107 client: Arc::new(client),
108 })
109 }
110}
111
112#[derive(Clone)]
114pub struct CloudClient {
115 pub(crate) api_key: String,
116 pub(crate) api_secret: String,
117 pub(crate) base_url: String,
118 #[allow(dead_code)]
119 pub(crate) timeout: std::time::Duration,
120 pub(crate) client: Arc<Client>,
121}
122
123impl CloudClient {
124 pub fn builder() -> CloudClientBuilder {
126 CloudClientBuilder::new()
127 }
128
129 fn normalize_url(&self, path: &str) -> String {
131 let base = self.base_url.trim_end_matches('/');
132 let path = path.trim_start_matches('/');
133 format!("{}/{}", base, path)
134 }
135
136 pub async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T> {
138 let url = self.normalize_url(path);
139
140 let response = self
142 .client
143 .get(&url)
144 .header("x-api-key", &self.api_key)
145 .header("x-api-secret-key", &self.api_secret)
146 .send()
147 .await?;
148
149 self.handle_response(response).await
150 }
151
152 pub async fn post<B: Serialize, T: serde::de::DeserializeOwned>(
154 &self,
155 path: &str,
156 body: &B,
157 ) -> Result<T> {
158 let url = self.normalize_url(path);
159
160 let response = self
162 .client
163 .post(&url)
164 .header("x-api-key", &self.api_key)
165 .header("x-api-secret-key", &self.api_secret)
166 .json(body)
167 .send()
168 .await?;
169
170 self.handle_response(response).await
171 }
172
173 pub async fn put<B: Serialize, T: serde::de::DeserializeOwned>(
175 &self,
176 path: &str,
177 body: &B,
178 ) -> Result<T> {
179 let url = self.normalize_url(path);
180
181 let response = self
183 .client
184 .put(&url)
185 .header("x-api-key", &self.api_key)
186 .header("x-api-secret-key", &self.api_secret)
187 .json(body)
188 .send()
189 .await?;
190
191 self.handle_response(response).await
192 }
193
194 pub async fn delete(&self, path: &str) -> Result<()> {
196 let url = self.normalize_url(path);
197
198 let response = self
200 .client
201 .delete(&url)
202 .header("x-api-key", &self.api_key)
203 .header("x-api-secret-key", &self.api_secret)
204 .send()
205 .await?;
206
207 if response.status().is_success() {
208 Ok(())
209 } else {
210 let status = response.status();
211 let text = response.text().await.unwrap_or_default();
212
213 match status.as_u16() {
214 400 => Err(RestError::BadRequest { message: text }),
215 401 => Err(RestError::AuthenticationFailed { message: text }),
216 403 => Err(RestError::Forbidden { message: text }),
217 404 => Err(RestError::NotFound { message: text }),
218 412 => Err(RestError::PreconditionFailed),
219 500 => Err(RestError::InternalServerError { message: text }),
220 503 => Err(RestError::ServiceUnavailable { message: text }),
221 _ => Err(RestError::ApiError {
222 code: status.as_u16(),
223 message: text,
224 }),
225 }
226 }
227 }
228
229 pub async fn get_raw(&self, path: &str) -> Result<serde_json::Value> {
231 self.get(path).await
232 }
233
234 pub async fn post_raw(&self, path: &str, body: serde_json::Value) -> Result<serde_json::Value> {
236 self.post(path, &body).await
237 }
238
239 pub async fn put_raw(&self, path: &str, body: serde_json::Value) -> Result<serde_json::Value> {
241 self.put(path, &body).await
242 }
243
244 pub async fn patch_raw(
246 &self,
247 path: &str,
248 body: serde_json::Value,
249 ) -> Result<serde_json::Value> {
250 let url = self.normalize_url(path);
251
252 let response = self
254 .client
255 .patch(&url)
256 .header("x-api-key", &self.api_key)
257 .header("x-api-secret-key", &self.api_secret)
258 .json(&body)
259 .send()
260 .await?;
261
262 self.handle_response(response).await
263 }
264
265 pub async fn delete_raw(&self, path: &str) -> Result<serde_json::Value> {
267 let url = self.normalize_url(path);
268
269 let response = self
271 .client
272 .delete(&url)
273 .header("x-api-key", &self.api_key)
274 .header("x-api-secret-key", &self.api_secret)
275 .send()
276 .await?;
277
278 if response.status().is_success() {
279 if response.content_length() == Some(0) {
280 Ok(serde_json::json!({"status": "deleted"}))
281 } else {
282 response.json().await.map_err(Into::into)
283 }
284 } else {
285 let status = response.status();
286 let text = response.text().await.unwrap_or_default();
287
288 match status.as_u16() {
289 400 => Err(RestError::BadRequest { message: text }),
290 401 => Err(RestError::AuthenticationFailed { message: text }),
291 403 => Err(RestError::Forbidden { message: text }),
292 404 => Err(RestError::NotFound { message: text }),
293 412 => Err(RestError::PreconditionFailed),
294 500 => Err(RestError::InternalServerError { message: text }),
295 503 => Err(RestError::ServiceUnavailable { message: text }),
296 _ => Err(RestError::ApiError {
297 code: status.as_u16(),
298 message: text,
299 }),
300 }
301 }
302 }
303
304 async fn handle_response<T: serde::de::DeserializeOwned>(
306 &self,
307 response: reqwest::Response,
308 ) -> Result<T> {
309 let status = response.status();
310
311 if status.is_success() {
312 let bytes = response.bytes().await.map_err(|e| {
314 RestError::ConnectionError(format!("Failed to read response: {}", e))
315 })?;
316
317 let deserializer = &mut serde_json::Deserializer::from_slice(&bytes);
319 serde_path_to_error::deserialize(deserializer).map_err(|err| {
320 let path = err.path().to_string();
321 RestError::ConnectionError(format!(
323 "Failed to deserialize field '{}': {}",
324 path,
325 err.inner()
326 ))
327 })
328 } else {
329 let text = response.text().await.unwrap_or_default();
330
331 match status.as_u16() {
332 400 => Err(RestError::BadRequest { message: text }),
333 401 => Err(RestError::AuthenticationFailed { message: text }),
334 403 => Err(RestError::Forbidden { message: text }),
335 404 => Err(RestError::NotFound { message: text }),
336 412 => Err(RestError::PreconditionFailed),
337 500 => Err(RestError::InternalServerError { message: text }),
338 503 => Err(RestError::ServiceUnavailable { message: text }),
339 _ => Err(RestError::ApiError {
340 code: status.as_u16(),
341 message: text,
342 }),
343 }
344 }
345 }
346}