Skip to content

Commit 0a518d8

Browse files
committed
feat: add builder patterns to request structs
- Add typed-builder v0.20 dependency to both redis-cloud and redis-enterprise - Implement builders for high-priority request structs: redis-cloud: - CreateDatabaseRequest, UpdateDatabaseRequest - CreateSubscriptionRequest, UpdateSubscriptionRequest - CloudProviderConfig, CloudRegionConfig - CreateBackupRequest, CreatePeeringRequest redis-enterprise: - CreateUserRequest, UpdateUserRequest - CreateRoleRequest with BdbRole - CreateCrdbRequest with CreateCrdbInstance - Builder benefits: - No more manual Some() wrapping for optional fields - No more .to_string() calls with setter(into) - strip_option allows natural API (no Option in setter) - Default values for optional fields - Compile-time validation - Update example to showcase cleaner builder API - Add comprehensive examples in doc comments Resolves #38
1 parent df282da commit 0a518d8

File tree

10 files changed

+226
-33
lines changed

10 files changed

+226
-33
lines changed

crates/redis-cloud/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ anyhow = { workspace = true }
2424
base64 = { workspace = true }
2525
chrono = { workspace = true }
2626
url = { workspace = true }
27+
typed-builder = "0.20"
2728

2829
[dev-dependencies]
2930
wiremock = { workspace = true }

crates/redis-cloud/examples/database_management.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
7171
// Uncomment and modify as needed
7272
/*
7373
println!("\nCreating a new database...");
74-
let new_database = CreateDatabaseRequest {
75-
name: "example-db".to_string(),
76-
memory_limit_in_gb: 0.1, // 100 MB
77-
data_persistence: "none".to_string(),
78-
replication: false,
79-
data_eviction: Some("volatile-lru".to_string()),
80-
password: None,
81-
support_oss_cluster_api: Some(false),
82-
use_external_endpoint_for_oss_cluster_api: None,
83-
};
74+
// Using the new builder pattern for cleaner API
75+
let new_database = CreateDatabaseRequest::builder()
76+
.name("example-db")
77+
.memory_limit_in_gb(0.1) // 100 MB
78+
.data_persistence("none")
79+
.replication(false)
80+
.data_eviction("volatile-lru")
81+
.support_oss_cluster_api(false)
82+
.build();
8483
8584
let created_db = db_handler
8685
.create(subscription_id, new_database)

crates/redis-cloud/src/models/backup.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use serde::{Deserialize, Serialize};
44
use serde_json::Value;
5+
use typed_builder::TypedBuilder;
56

67
/// Backup information
78
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -17,9 +18,10 @@ pub struct CloudBackup {
1718
}
1819

1920
/// Create backup request
20-
#[derive(Debug, Serialize)]
21+
#[derive(Debug, Serialize, TypedBuilder)]
2122
pub struct CreateBackupRequest {
2223
pub database_id: u32,
2324
#[serde(skip_serializing_if = "Option::is_none")]
25+
#[builder(default, setter(into, strip_option))]
2426
pub description: Option<String>,
2527
}

crates/redis-cloud/src/models/database.rs

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use serde::{Deserialize, Serialize};
77
use serde_json::Value;
8+
use typed_builder::TypedBuilder;
89

910
/// Represents a Redis Cloud database instance
1011
///
@@ -92,46 +93,71 @@ pub struct ThroughputMeasurement {
9293
///
9394
/// ```rust,no_run
9495
/// use redis_cloud::CreateDatabaseRequest;
95-
/// use serde_json::json;
9696
///
97-
/// let request = json!({
98-
/// "name": "production-cache",
99-
/// "memory_limit_in_gb": 5.0,
100-
/// "data_persistence": "aof-every-1-sec",
101-
/// "replication": true,
102-
/// "password": "secure-password-123",
103-
/// "support_oss_cluster_api": false
104-
/// });
97+
/// let request = CreateDatabaseRequest::builder()
98+
/// .name("production-cache")
99+
/// .memory_limit_in_gb(5.0)
100+
/// .data_persistence("aof-every-1-sec")
101+
/// .replication(true)
102+
/// .password("secure-password-123")
103+
/// .support_oss_cluster_api(false)
104+
/// .build();
105105
/// ```
106-
#[derive(Debug, Serialize)]
106+
#[derive(Debug, Serialize, TypedBuilder)]
107107
pub struct CreateDatabaseRequest {
108+
#[builder(setter(into))]
108109
pub name: String,
109110
pub memory_limit_in_gb: f64,
111+
#[builder(setter(into))]
110112
pub data_persistence: String,
113+
#[builder(default)]
111114
pub replication: bool,
112115
#[serde(skip_serializing_if = "Option::is_none")]
116+
#[builder(default, setter(into, strip_option))]
113117
pub data_eviction: Option<String>,
114118
#[serde(skip_serializing_if = "Option::is_none")]
119+
#[builder(default, setter(into, strip_option))]
115120
pub password: Option<String>,
116121
#[serde(skip_serializing_if = "Option::is_none")]
122+
#[builder(default, setter(strip_option))]
117123
pub support_oss_cluster_api: Option<bool>,
118124
#[serde(skip_serializing_if = "Option::is_none")]
125+
#[builder(default, setter(strip_option))]
119126
pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
120127
}
121128

122129
/// Update database request
123-
#[derive(Debug, Serialize)]
130+
///
131+
/// All fields are optional - only provide the fields you want to update.
132+
///
133+
/// # Examples
134+
///
135+
/// ```rust,no_run
136+
/// use redis_cloud::UpdateDatabaseRequest;
137+
///
138+
/// let request = UpdateDatabaseRequest::builder()
139+
/// .memory_limit_in_gb(10.0)
140+
/// .replication(true)
141+
/// .build();
142+
/// ```
143+
#[derive(Debug, Serialize, TypedBuilder)]
124144
pub struct UpdateDatabaseRequest {
125145
#[serde(skip_serializing_if = "Option::is_none")]
146+
#[builder(default, setter(into, strip_option))]
126147
pub name: Option<String>,
127148
#[serde(skip_serializing_if = "Option::is_none")]
149+
#[builder(default, setter(strip_option))]
128150
pub memory_limit_in_gb: Option<f64>,
129151
#[serde(skip_serializing_if = "Option::is_none")]
152+
#[builder(default, setter(into, strip_option))]
130153
pub data_persistence: Option<String>,
131154
#[serde(skip_serializing_if = "Option::is_none")]
155+
#[builder(default, setter(strip_option))]
132156
pub replication: Option<bool>,
133157
#[serde(skip_serializing_if = "Option::is_none")]
158+
#[builder(default, setter(into, strip_option))]
134159
pub data_eviction: Option<String>,
135160
#[serde(skip_serializing_if = "Option::is_none")]
161+
#[builder(default, setter(into, strip_option))]
136162
pub password: Option<String>,
137163
}

crates/redis-cloud/src/models/peering.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use serde::{Deserialize, Serialize};
44
use serde_json::Value;
5+
use typed_builder::TypedBuilder;
56

67
/// VPC Peering
78
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -18,12 +19,32 @@ pub struct CloudPeering {
1819
}
1920

2021
/// Create peering request
21-
#[derive(Debug, Serialize)]
22+
///
23+
/// # Examples
24+
///
25+
/// ```rust,no_run
26+
/// use redis_cloud::CreatePeeringRequest;
27+
///
28+
/// let request = CreatePeeringRequest::builder()
29+
/// .subscription_id(123)
30+
/// .provider("AWS")
31+
/// .aws_account_id("123456789012")
32+
/// .vpc_id("vpc-12345678")
33+
/// .vpc_cidr("10.0.0.0/16")
34+
/// .region("us-east-1")
35+
/// .build();
36+
/// ```
37+
#[derive(Debug, Serialize, TypedBuilder)]
2238
pub struct CreatePeeringRequest {
2339
pub subscription_id: u32,
40+
#[builder(setter(into))]
2441
pub provider: String,
42+
#[builder(default, setter(into, strip_option))]
2543
pub aws_account_id: Option<String>,
44+
#[builder(setter(into))]
2645
pub vpc_id: String,
46+
#[builder(setter(into))]
2747
pub vpc_cidr: String,
48+
#[builder(setter(into))]
2849
pub region: String,
2950
}

crates/redis-cloud/src/models/subscription.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use serde::{Deserialize, Serialize};
44
use serde_json::Value;
5+
use typed_builder::TypedBuilder;
56

67
/// Cloud subscription
78
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -35,42 +36,88 @@ pub struct CloudRegion {
3536
}
3637

3738
/// Create subscription request
38-
#[derive(Debug, Serialize)]
39+
///
40+
/// # Examples
41+
///
42+
/// ```rust,no_run
43+
/// use redis_cloud::{CreateSubscriptionRequest, CloudProviderConfig, CloudRegionConfig};
44+
///
45+
/// let request = CreateSubscriptionRequest::builder()
46+
/// .name("production")
47+
/// .payment_method_id(12345)
48+
/// .memory_storage("ram")
49+
/// .cloud_provider(
50+
/// CloudProviderConfig::builder()
51+
/// .provider("AWS")
52+
/// .regions(vec![
53+
/// CloudRegionConfig::builder()
54+
/// .region("us-east-1")
55+
/// .multiple_availability_zones(true)
56+
/// .build()
57+
/// ])
58+
/// .build()
59+
/// )
60+
/// .build();
61+
/// ```
62+
#[derive(Debug, Serialize, TypedBuilder)]
3963
pub struct CreateSubscriptionRequest {
64+
#[builder(setter(into))]
4065
pub name: String,
4166
pub payment_method_id: u32,
67+
#[builder(setter(into))]
4268
pub memory_storage: String,
4369
#[serde(skip_serializing_if = "Option::is_none")]
70+
#[builder(default, setter(strip_option))]
4471
pub persistent_storage_encryption: Option<bool>,
4572
pub cloud_provider: CloudProviderConfig,
4673
}
4774

48-
#[derive(Debug, Serialize)]
75+
#[derive(Debug, Serialize, TypedBuilder)]
4976
pub struct CloudProviderConfig {
77+
#[builder(setter(into))]
5078
pub provider: String,
5179
#[serde(skip_serializing_if = "Option::is_none")]
80+
#[builder(default, setter(strip_option))]
5281
pub cloud_account_id: Option<u32>,
5382
pub regions: Vec<CloudRegionConfig>,
5483
}
5584

56-
#[derive(Debug, Serialize)]
85+
#[derive(Debug, Serialize, TypedBuilder)]
5786
pub struct CloudRegionConfig {
87+
#[builder(setter(into))]
5888
pub region: String,
5989
#[serde(skip_serializing_if = "Option::is_none")]
90+
#[builder(default, setter(into, strip_option))]
6091
pub networking_deployment_cidr: Option<String>,
6192
#[serde(skip_serializing_if = "Option::is_none")]
93+
#[builder(default, setter(strip_option))]
6294
pub preferred_availability_zones: Option<Vec<String>>,
6395
#[serde(skip_serializing_if = "Option::is_none")]
96+
#[builder(default, setter(strip_option))]
6497
pub multiple_availability_zones: Option<bool>,
6598
}
6699

67100
/// Update subscription request
68-
#[derive(Debug, Serialize)]
101+
///
102+
/// # Examples
103+
///
104+
/// ```rust,no_run
105+
/// use redis_cloud::UpdateSubscriptionRequest;
106+
///
107+
/// let request = UpdateSubscriptionRequest::builder()
108+
/// .name("production-updated")
109+
/// .payment_method_id(54321)
110+
/// .build();
111+
/// ```
112+
#[derive(Debug, Serialize, TypedBuilder)]
69113
pub struct UpdateSubscriptionRequest {
70114
#[serde(skip_serializing_if = "Option::is_none")]
115+
#[builder(default, setter(into, strip_option))]
71116
pub name: Option<String>,
72117
#[serde(skip_serializing_if = "Option::is_none")]
118+
#[builder(default, setter(strip_option))]
73119
pub payment_method_id: Option<u32>,
74120
#[serde(skip_serializing_if = "Option::is_none")]
121+
#[builder(default, setter(into, strip_option))]
75122
pub memory_storage: Option<String>,
76123
}

crates/redis-enterprise/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ anyhow = { workspace = true }
2525
base64 = { workspace = true }
2626
chrono = { workspace = true }
2727
url = { workspace = true }
28+
typed-builder = "0.20"
2829

2930
[dev-dependencies]
3031
wiremock = { workspace = true }

crates/redis-enterprise/src/crdb.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::client::RestClient;
44
use crate::error::Result;
55
use serde::{Deserialize, Serialize};
66
use serde_json::Value;
7+
use typed_builder::TypedBuilder;
78

89
/// CRDB (Active-Active Database) information
910
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -36,28 +37,63 @@ pub struct CrdbInstance {
3637
}
3738

3839
/// Create CRDB request
39-
#[derive(Debug, Serialize)]
40+
///
41+
/// # Examples
42+
///
43+
/// ```rust,no_run
44+
/// use redis_enterprise::{CreateCrdbRequest, CreateCrdbInstance};
45+
///
46+
/// let request = CreateCrdbRequest::builder()
47+
/// .name("global-cache")
48+
/// .memory_size(1024 * 1024 * 1024) // 1GB
49+
/// .instances(vec![
50+
/// CreateCrdbInstance::builder()
51+
/// .cluster("cluster1.example.com")
52+
/// .cluster_url("https://cluster1.example.com:9443")
53+
/// .username("admin")
54+
/// .password("password")
55+
/// .build(),
56+
/// CreateCrdbInstance::builder()
57+
/// .cluster("cluster2.example.com")
58+
/// .cluster_url("https://cluster2.example.com:9443")
59+
/// .username("admin")
60+
/// .password("password")
61+
/// .build()
62+
/// ])
63+
/// .encryption(true)
64+
/// .data_persistence("aof")
65+
/// .build();
66+
/// ```
67+
#[derive(Debug, Serialize, TypedBuilder)]
4068
pub struct CreateCrdbRequest {
69+
#[builder(setter(into))]
4170
pub name: String,
4271
pub memory_size: u64,
4372
pub instances: Vec<CreateCrdbInstance>,
4473
#[serde(skip_serializing_if = "Option::is_none")]
74+
#[builder(default, setter(strip_option))]
4575
pub encryption: Option<bool>,
4676
#[serde(skip_serializing_if = "Option::is_none")]
77+
#[builder(default, setter(into, strip_option))]
4778
pub data_persistence: Option<String>,
4879
#[serde(skip_serializing_if = "Option::is_none")]
80+
#[builder(default, setter(into, strip_option))]
4981
pub eviction_policy: Option<String>,
5082
}
5183

5284
/// Create CRDB instance
53-
#[derive(Debug, Serialize)]
85+
#[derive(Debug, Serialize, TypedBuilder)]
5486
pub struct CreateCrdbInstance {
87+
#[builder(setter(into))]
5588
pub cluster: String,
5689
#[serde(skip_serializing_if = "Option::is_none")]
90+
#[builder(default, setter(into, strip_option))]
5791
pub cluster_url: Option<String>,
5892
#[serde(skip_serializing_if = "Option::is_none")]
93+
#[builder(default, setter(into, strip_option))]
5994
pub username: Option<String>,
6095
#[serde(skip_serializing_if = "Option::is_none")]
96+
#[builder(default, setter(into, strip_option))]
6197
pub password: Option<String>,
6298
}
6399

0 commit comments

Comments
 (0)