Skip to content

Commit 58d7052

Browse files
Merge pull request #11 from joshrotenberg/feat/cloud-api-100-percent
feat: achieve 100% Redis Cloud REST API coverage
2 parents 73dc611 + f7e27ae commit 58d7052

32 files changed

+1035
-450
lines changed

crates/redis-cloud/src/client.rs

Lines changed: 119 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,141 @@
11
//! Redis Cloud API client core implementation
2+
//!
3+
//! This module contains the core HTTP client for interacting with the Redis Cloud REST API.
4+
//! It provides authentication handling, request/response processing, and error management.
5+
//!
6+
//! The client is designed around a builder pattern for flexible configuration and supports
7+
//! both typed and untyped API interactions.
28
39
use crate::{CloudError as RestError, Result};
410
use reqwest::Client;
511
use serde::Serialize;
612
use std::sync::Arc;
713

8-
/// Redis Cloud API configuration
14+
/// Builder for constructing a CloudClient with custom configuration
15+
///
16+
/// Provides a fluent interface for configuring API credentials, base URL, timeouts,
17+
/// and other client settings before creating the final CloudClient instance.
18+
///
19+
/// # Examples
20+
///
21+
/// ```rust,no_run
22+
/// use redis_cloud::CloudClient;
23+
///
24+
/// // Basic configuration
25+
/// let client = CloudClient::builder()
26+
/// .api_key("your-api-key")
27+
/// .api_secret("your-api-secret")
28+
/// .build()?;
29+
///
30+
/// // Advanced configuration
31+
/// let client = CloudClient::builder()
32+
/// .api_key("your-api-key")
33+
/// .api_secret("your-api-secret")
34+
/// .base_url("https://api.redislabs.com/v1".to_string())
35+
/// .timeout(std::time::Duration::from_secs(120))
36+
/// .build()?;
37+
/// # Ok::<(), Box<dyn std::error::Error>>(())
38+
/// ```
939
#[derive(Debug, Clone)]
10-
pub struct CloudConfig {
11-
pub api_key: String,
12-
pub api_secret: String,
13-
pub base_url: String,
14-
pub timeout: std::time::Duration,
40+
pub struct CloudClientBuilder {
41+
api_key: Option<String>,
42+
api_secret: Option<String>,
43+
base_url: String,
44+
timeout: std::time::Duration,
1545
}
1646

17-
impl Default for CloudConfig {
47+
impl Default for CloudClientBuilder {
1848
fn default() -> Self {
19-
CloudConfig {
20-
api_key: String::new(),
21-
api_secret: String::new(),
49+
Self {
50+
api_key: None,
51+
api_secret: None,
2252
base_url: "https://api.redislabs.com/v1".to_string(),
2353
timeout: std::time::Duration::from_secs(30),
2454
}
2555
}
2656
}
2757

28-
/// Redis Cloud API client
29-
#[derive(Clone)]
30-
pub struct CloudClient {
31-
pub(crate) config: CloudConfig,
32-
pub(crate) client: Arc<Client>,
33-
}
58+
impl CloudClientBuilder {
59+
/// Create a new builder
60+
pub fn new() -> Self {
61+
Self::default()
62+
}
63+
64+
/// Set the API key
65+
pub fn api_key(mut self, key: impl Into<String>) -> Self {
66+
self.api_key = Some(key.into());
67+
self
68+
}
69+
70+
/// Set the API secret
71+
pub fn api_secret(mut self, secret: impl Into<String>) -> Self {
72+
self.api_secret = Some(secret.into());
73+
self
74+
}
75+
76+
/// Set the base URL
77+
pub fn base_url(mut self, url: impl Into<String>) -> Self {
78+
self.base_url = url.into();
79+
self
80+
}
81+
82+
/// Set the timeout
83+
pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
84+
self.timeout = timeout;
85+
self
86+
}
87+
88+
/// Build the client
89+
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()))?;
3496

35-
impl CloudClient {
36-
/// Create a new Cloud API client
37-
pub fn new(config: CloudConfig) -> Result<Self> {
3897
let client = Client::builder()
39-
.timeout(config.timeout)
98+
.timeout(self.timeout)
4099
.build()
41100
.map_err(|e| RestError::ConnectionError(e.to_string()))?;
42101

43102
Ok(CloudClient {
44-
config,
103+
api_key,
104+
api_secret,
105+
base_url: self.base_url,
106+
timeout: self.timeout,
45107
client: Arc::new(client),
46108
})
47109
}
110+
}
111+
112+
/// Redis Cloud API client
113+
#[derive(Clone)]
114+
pub 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+
123+
impl CloudClient {
124+
/// Create a new builder for the client
125+
pub fn builder() -> CloudClientBuilder {
126+
CloudClientBuilder::new()
127+
}
48128

49129
/// Make a GET request with API key authentication
50130
pub async fn get<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T> {
51-
let url = format!("{}{}", self.config.base_url, path);
131+
let url = format!("{}{}", self.base_url, path);
52132

53133
// Redis Cloud API uses these headers for authentication
54134
let response = self
55135
.client
56136
.get(&url)
57-
.header("x-api-key", &self.config.api_key)
58-
.header("x-api-secret-key", &self.config.api_secret)
137+
.header("x-api-key", &self.api_key)
138+
.header("x-api-secret-key", &self.api_secret)
59139
.send()
60140
.await?;
61141

@@ -68,14 +148,14 @@ impl CloudClient {
68148
path: &str,
69149
body: &B,
70150
) -> Result<T> {
71-
let url = format!("{}{}", self.config.base_url, path);
151+
let url = format!("{}{}", self.base_url, path);
72152

73153
// Same backwards header naming as GET
74154
let response = self
75155
.client
76156
.post(&url)
77-
.header("x-api-key", &self.config.api_key)
78-
.header("x-api-secret-key", &self.config.api_secret)
157+
.header("x-api-key", &self.api_key)
158+
.header("x-api-secret-key", &self.api_secret)
79159
.json(body)
80160
.send()
81161
.await?;
@@ -89,14 +169,14 @@ impl CloudClient {
89169
path: &str,
90170
body: &B,
91171
) -> Result<T> {
92-
let url = format!("{}{}", self.config.base_url, path);
172+
let url = format!("{}{}", self.base_url, path);
93173

94174
// Same backwards header naming as GET
95175
let response = self
96176
.client
97177
.put(&url)
98-
.header("x-api-key", &self.config.api_key)
99-
.header("x-api-secret-key", &self.config.api_secret)
178+
.header("x-api-key", &self.api_key)
179+
.header("x-api-secret-key", &self.api_secret)
100180
.json(body)
101181
.send()
102182
.await?;
@@ -106,14 +186,14 @@ impl CloudClient {
106186

107187
/// Make a DELETE request
108188
pub async fn delete(&self, path: &str) -> Result<()> {
109-
let url = format!("{}{}", self.config.base_url, path);
189+
let url = format!("{}{}", self.base_url, path);
110190

111191
// Same backwards header naming as GET
112192
let response = self
113193
.client
114194
.delete(&url)
115-
.header("x-api-key", &self.config.api_key)
116-
.header("x-api-secret-key", &self.config.api_secret)
195+
.header("x-api-key", &self.api_key)
196+
.header("x-api-secret-key", &self.api_secret)
117197
.send()
118198
.await?;
119199

@@ -150,14 +230,14 @@ impl CloudClient {
150230
path: &str,
151231
body: serde_json::Value,
152232
) -> Result<serde_json::Value> {
153-
let url = format!("{}{}", self.config.base_url, path);
233+
let url = format!("{}{}", self.base_url, path);
154234

155235
// Use backwards header names for compatibility
156236
let response = self
157237
.client
158238
.patch(&url)
159-
.header("x-api-key", &self.config.api_key)
160-
.header("x-api-secret-key", &self.config.api_secret)
239+
.header("x-api-key", &self.api_key)
240+
.header("x-api-secret-key", &self.api_secret)
161241
.json(&body)
162242
.send()
163243
.await?;
@@ -167,14 +247,14 @@ impl CloudClient {
167247

168248
/// Execute raw DELETE request returning any response body
169249
pub async fn delete_raw(&self, path: &str) -> Result<serde_json::Value> {
170-
let url = format!("{}{}", self.config.base_url, path);
250+
let url = format!("{}{}", self.base_url, path);
171251

172252
// Use backwards header names for compatibility
173253
let response = self
174254
.client
175255
.delete(&url)
176-
.header("x-api-key", &self.config.api_key)
177-
.header("x-api-secret-key", &self.config.api_secret)
256+
.header("x-api-key", &self.api_key)
257+
.header("x-api-secret-key", &self.api_secret)
178258
.send()
179259
.await?;
180260

crates/redis-cloud/src/handlers/billing.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,46 @@
11
//! Billing and payment operations handler
2+
//!
3+
//! This module provides comprehensive billing and payment management for Redis Cloud,
4+
//! including invoice management, payment method handling, cost analysis, and usage reporting.
5+
//!
6+
//! # Examples
7+
//!
8+
//! ```rust,no_run
9+
//! use redis_cloud::{CloudClient, CloudBillingHandler};
10+
//! use serde_json::json;
11+
//!
12+
//! # #[tokio::main]
13+
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
14+
//! let client = CloudClient::builder()
15+
//! .api_key("your-api-key")
16+
//! .api_secret("your-api-secret")
17+
//! .build()?;
18+
//!
19+
//! let billing_handler = CloudBillingHandler::new(client);
20+
//!
21+
//! // Get current billing information
22+
//! let billing_info = billing_handler.get_info().await?;
23+
//!
24+
//! // List all invoices
25+
//! let invoices = billing_handler.list_invoices().await?;
26+
//!
27+
//! // Get usage report for date range
28+
//! let usage = billing_handler.get_usage("2024-01-01", "2024-01-31").await?;
29+
//!
30+
//! // List payment methods
31+
//! let payment_methods = billing_handler.list_payment_methods().await?;
32+
//! # Ok(())
33+
//! # }
34+
//! ```
235
336
use crate::{Result, client::CloudClient};
437
use serde_json::Value;
538

639
/// Handler for Cloud billing and payment operations
40+
///
41+
/// Provides access to billing information, invoice management, payment methods,
42+
/// cost analysis, and usage reporting. Essential for monitoring and managing
43+
/// Redis Cloud costs and payment configuration.
744
pub struct CloudBillingHandler {
845
client: CloudClient,
946
}

0 commit comments

Comments
 (0)