1use crate::error::{RestError, Result};
4use reqwest::{Client, Response};
5use serde::{Serialize, de::DeserializeOwned};
6use std::sync::Arc;
7use std::time::Duration;
8use tracing::{debug, trace};
9
10pub type RestConfig = EnterpriseClientBuilder;
12
13#[derive(Debug, Clone)]
15pub struct EnterpriseClientBuilder {
16 base_url: String,
17 username: Option<String>,
18 password: Option<String>,
19 timeout: Duration,
20 insecure: bool,
21}
22
23impl Default for EnterpriseClientBuilder {
24 fn default() -> Self {
25 Self {
26 base_url: "https://localhost:9443".to_string(),
27 username: None,
28 password: None,
29 timeout: Duration::from_secs(30),
30 insecure: false,
31 }
32 }
33}
34
35impl EnterpriseClientBuilder {
36 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub fn base_url(mut self, url: impl Into<String>) -> Self {
43 self.base_url = url.into();
44 self
45 }
46
47 pub fn username(mut self, username: impl Into<String>) -> Self {
49 self.username = Some(username.into());
50 self
51 }
52
53 pub fn password(mut self, password: impl Into<String>) -> Self {
55 self.password = Some(password.into());
56 self
57 }
58
59 pub fn timeout(mut self, timeout: Duration) -> Self {
61 self.timeout = timeout;
62 self
63 }
64
65 pub fn insecure(mut self, insecure: bool) -> Self {
67 self.insecure = insecure;
68 self
69 }
70
71 pub fn build(self) -> Result<EnterpriseClient> {
73 let username = self.username.unwrap_or_default();
74 let password = self.password.unwrap_or_default();
75
76 let client_builder = Client::builder()
77 .timeout(self.timeout)
78 .danger_accept_invalid_certs(self.insecure);
79
80 let client = client_builder
81 .build()
82 .map_err(|e| RestError::ConnectionError(e.to_string()))?;
83
84 Ok(EnterpriseClient {
85 base_url: self.base_url,
86 username,
87 password,
88 timeout: self.timeout,
89 client: Arc::new(client),
90 })
91 }
92}
93
94#[derive(Clone)]
96pub struct EnterpriseClient {
97 base_url: String,
98 username: String,
99 password: String,
100 timeout: Duration,
101 client: Arc<Client>,
102}
103
104pub type RestClient = EnterpriseClient;
106
107impl EnterpriseClient {
108 pub fn builder() -> EnterpriseClientBuilder {
110 EnterpriseClientBuilder::new()
111 }
112
113 pub fn from_env() -> Result<Self> {
121 use std::env;
122
123 let base_url = env::var("REDIS_ENTERPRISE_URL")
124 .unwrap_or_else(|_| "https://localhost:9443".to_string());
125 let username =
126 env::var("REDIS_ENTERPRISE_USER").unwrap_or_else(|_| "admin@redis.local".to_string());
127 let password =
128 env::var("REDIS_ENTERPRISE_PASSWORD").map_err(|_| RestError::AuthenticationFailed)?;
129 let insecure = env::var("REDIS_ENTERPRISE_INSECURE")
130 .unwrap_or_else(|_| "false".to_string())
131 .parse::<bool>()
132 .unwrap_or(false);
133
134 Self::builder()
135 .base_url(base_url)
136 .username(username)
137 .password(password)
138 .insecure(insecure)
139 .build()
140 }
141
142 pub async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T> {
144 let url = format!("{}{}", self.base_url, path);
145 debug!("GET {}", url);
146
147 let response = self
148 .client
149 .get(&url)
150 .basic_auth(&self.username, Some(&self.password))
151 .send()
152 .await
153 .map_err(|e| self.map_reqwest_error(e, &url))?;
154
155 trace!("Response status: {}", response.status());
156 self.handle_response(response).await
157 }
158
159 pub async fn get_text(&self, path: &str) -> Result<String> {
161 let url = format!("{}{}", self.base_url, path);
162 debug!("GET {} (text)", url);
163
164 let response = self
165 .client
166 .get(&url)
167 .basic_auth(&self.username, Some(&self.password))
168 .send()
169 .await
170 .map_err(|e| self.map_reqwest_error(e, &url))?;
171
172 trace!("Response status: {}", response.status());
173
174 if response.status().is_success() {
175 let text = response
176 .text()
177 .await
178 .map_err(crate::error::RestError::RequestFailed)?;
179 Ok(text)
180 } else {
181 let status = response.status();
182 let error_text = response
183 .text()
184 .await
185 .unwrap_or_else(|_| "Unknown error".to_string());
186 Err(crate::error::RestError::ApiError {
187 code: status.as_u16(),
188 message: error_text,
189 })
190 }
191 }
192
193 pub async fn post<B: Serialize, T: DeserializeOwned>(&self, path: &str, body: &B) -> Result<T> {
195 let url = format!("{}{}", self.base_url, path);
196 debug!("POST {}", url);
197 trace!("Request body: {:?}", serde_json::to_value(body).ok());
198
199 let response = self
200 .client
201 .post(&url)
202 .basic_auth(&self.username, Some(&self.password))
203 .json(body)
204 .send()
205 .await
206 .map_err(|e| self.map_reqwest_error(e, &url))?;
207
208 trace!("Response status: {}", response.status());
209 self.handle_response(response).await
210 }
211
212 pub async fn put<B: Serialize, T: DeserializeOwned>(&self, path: &str, body: &B) -> Result<T> {
214 let url = format!("{}{}", self.base_url, path);
215 debug!("PUT {}", url);
216 trace!("Request body: {:?}", serde_json::to_value(body).ok());
217
218 let response = self
219 .client
220 .put(&url)
221 .basic_auth(&self.username, Some(&self.password))
222 .json(body)
223 .send()
224 .await
225 .map_err(|e| self.map_reqwest_error(e, &url))?;
226
227 trace!("Response status: {}", response.status());
228 self.handle_response(response).await
229 }
230
231 pub async fn delete(&self, path: &str) -> Result<()> {
233 let url = format!("{}{}", self.base_url, path);
234 debug!("DELETE {}", url);
235
236 let response = self
237 .client
238 .delete(&url)
239 .basic_auth(&self.username, Some(&self.password))
240 .send()
241 .await
242 .map_err(|e| self.map_reqwest_error(e, &url))?;
243
244 trace!("Response status: {}", response.status());
245 if response.status().is_success() {
246 Ok(())
247 } else {
248 let status = response.status();
249 let text = response.text().await.unwrap_or_default();
250 Err(RestError::ApiError {
251 code: status.as_u16(),
252 message: text,
253 })
254 }
255 }
256
257 pub async fn get_raw(&self, path: &str) -> Result<serde_json::Value> {
259 self.get(path).await
260 }
261
262 pub async fn post_raw(&self, path: &str, body: serde_json::Value) -> Result<serde_json::Value> {
264 self.post(path, &body).await
265 }
266
267 pub async fn put_raw(&self, path: &str, body: serde_json::Value) -> Result<serde_json::Value> {
269 self.put(path, &body).await
270 }
271
272 pub async fn post_action<B: Serialize>(&self, path: &str, body: &B) -> Result<()> {
274 let url = format!("{}{}", self.base_url, path);
275 debug!("POST {}", url);
276 trace!("Request body: {:?}", serde_json::to_value(body).ok());
277
278 let response = self
279 .client
280 .post(&url)
281 .basic_auth(&self.username, Some(&self.password))
282 .json(body)
283 .send()
284 .await
285 .map_err(|e| self.map_reqwest_error(e, &url))?;
286
287 trace!("Response status: {}", response.status());
288 if response.status().is_success() {
289 Ok(())
290 } else {
291 let status = response.status();
292 let text = response.text().await.unwrap_or_default();
293 Err(RestError::ApiError {
294 code: status.as_u16(),
295 message: text,
296 })
297 }
298 }
299
300 pub fn rest_client(&self) -> Self {
302 self.clone()
303 }
304
305 pub async fn post_bootstrap<B: Serialize>(
307 &self,
308 path: &str,
309 body: &B,
310 ) -> Result<serde_json::Value> {
311 let url = format!("{}{}", self.base_url, path);
312
313 let response = self
314 .client
315 .post(&url)
316 .basic_auth(&self.username, Some(&self.password))
317 .json(body)
318 .send()
319 .await
320 .map_err(|e| self.map_reqwest_error(e, &url))?;
321
322 let status = response.status();
323 if status.is_success() {
324 let text = response.text().await.unwrap_or_default();
326 if text.is_empty() || text.trim().is_empty() {
327 Ok(serde_json::json!({"status": "success"}))
328 } else {
329 Ok(serde_json::from_str(&text)
330 .unwrap_or_else(|_| serde_json::json!({"status": "success", "response": text})))
331 }
332 } else {
333 let text = response.text().await.unwrap_or_default();
334 Err(RestError::ApiError {
335 code: status.as_u16(),
336 message: text,
337 })
338 }
339 }
340
341 pub async fn patch_raw(
343 &self,
344 path: &str,
345 body: serde_json::Value,
346 ) -> Result<serde_json::Value> {
347 let url = format!("{}{}", self.base_url, path);
348 let response = self
349 .client
350 .patch(&url)
351 .basic_auth(&self.username, Some(&self.password))
352 .json(&body)
353 .send()
354 .await
355 .map_err(|e| self.map_reqwest_error(e, &url))?;
356
357 if response.status().is_success() {
358 response
359 .json()
360 .await
361 .map_err(|e| RestError::ParseError(e.to_string()))
362 } else {
363 let status = response.status();
364 let text = response.text().await.unwrap_or_default();
365 Err(RestError::ApiError {
366 code: status.as_u16(),
367 message: text,
368 })
369 }
370 }
371
372 pub async fn delete_raw(&self, path: &str) -> Result<serde_json::Value> {
374 let url = format!("{}{}", self.base_url, path);
375 let response = self
376 .client
377 .delete(&url)
378 .basic_auth(&self.username, Some(&self.password))
379 .send()
380 .await
381 .map_err(|e| self.map_reqwest_error(e, &url))?;
382
383 if response.status().is_success() {
384 if response.content_length() == Some(0) {
385 Ok(serde_json::json!({"status": "deleted"}))
386 } else {
387 response
388 .json()
389 .await
390 .map_err(|e| RestError::ParseError(e.to_string()))
391 }
392 } else {
393 let status = response.status();
394 let text = response.text().await.unwrap_or_default();
395 Err(RestError::ApiError {
396 code: status.as_u16(),
397 message: text,
398 })
399 }
400 }
401
402 fn map_reqwest_error(&self, error: reqwest::Error, url: &str) -> RestError {
404 if error.is_connect() {
405 RestError::ConnectionError(format!(
406 "Failed to connect to {}: Connection refused or host unreachable. Check if the Redis Enterprise server is running and accessible.",
407 url
408 ))
409 } else if error.is_timeout() {
410 RestError::ConnectionError(format!(
411 "Request to {} timed out after {:?}. Check network connectivity or increase timeout.",
412 url, self.timeout
413 ))
414 } else if error.is_decode() {
415 RestError::ConnectionError(format!(
416 "Failed to decode JSON response from {}: {}. Server may have returned invalid JSON or HTML error page.",
417 url, error
418 ))
419 } else if let Some(status) = error.status() {
420 RestError::ApiError {
421 code: status.as_u16(),
422 message: format!("HTTP {} from {}: {}", status.as_u16(), url, error),
423 }
424 } else if error.is_request() {
425 RestError::ConnectionError(format!(
426 "Request to {} failed: {}. Check URL format and network settings.",
427 url, error
428 ))
429 } else {
430 RestError::RequestFailed(error)
431 }
432 }
433
434 async fn handle_response<T: DeserializeOwned>(&self, response: Response) -> Result<T> {
436 if response.status().is_success() {
437 response.json::<T>().await.map_err(Into::into)
438 } else {
439 let status = response.status();
440 let text = response.text().await.unwrap_or_default();
441
442 match status.as_u16() {
443 401 => Err(RestError::Unauthorized),
444 404 => Err(RestError::NotFound),
445 500..=599 => Err(RestError::ServerError(text)),
446 _ => Err(RestError::ApiError {
447 code: status.as_u16(),
448 message: text,
449 }),
450 }
451 }
452 }
453}