redis_cloud/flexible/subscriptions.rs
1//! Subscription management for Pro (Flexible) plans
2//!
3//! This module provides comprehensive management of Redis Cloud Pro subscriptions,
4//! which offer flexible, scalable Redis deployments with advanced features like
5//! auto-scaling, multi-region support, and Active-Active configurations.
6//!
7//! # Overview
8//!
9//! Pro subscriptions are Redis Cloud's most flexible offering, supporting everything
10//! from small development instances to large-scale production deployments with
11//! automatic scaling, clustering, and global distribution.
12//!
13//! # Key Features
14//!
15//! - **Flexible Scaling**: Auto-scaling based on usage patterns
16//! - **Multi-Region**: Deploy across multiple regions and cloud providers
17//! - **Active-Active**: Global database replication with local reads/writes
18//! - **Advanced Networking**: VPC peering, Transit Gateway, Private endpoints
19//! - **Maintenance Windows**: Configurable maintenance scheduling
20//! - **CIDR Management**: IP allowlist and security group configuration
21//! - **Custom Pricing**: Usage-based pricing with detailed cost tracking
22//!
23//! # Subscription Types
24//!
25//! - **Single-Region**: Standard deployment in one region
26//! - **Multi-Region**: Replicated across multiple regions
27//! - **Active-Active**: CRDB with conflict-free replicated data types
28//!
29//! # Example Usage
30//!
31//! ```no_run
32//! use redis_cloud::{CloudClient, SubscriptionHandler};
33//!
34//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
35//! let client = CloudClient::builder()
36//! .api_key("your-api-key")
37//! .api_secret("your-api-secret")
38//! .build()?;
39//!
40//! let handler = SubscriptionHandler::new(client);
41//!
42//! // List all Pro subscriptions
43//! let subscriptions = handler.get_all_subscriptions().await?;
44//!
45//! // Get subscription details (subscription ID 123)
46//! let subscription = handler.get_subscription_by_id(123).await?;
47//!
48//! // Manage maintenance windows
49//! let windows = handler.get_subscription_maintenance_windows(123).await?;
50//! # Ok(())
51//! # }
52//! ```
53
54use crate::types::{Link, ProcessorResponse};
55use crate::{CloudClient, Result};
56use serde::{Deserialize, Serialize};
57use serde_json::Value;
58use std::collections::HashMap;
59use typed_builder::TypedBuilder;
60
61// ============================================================================
62// Models
63// ============================================================================
64
65/// Subscription update request message
66#[derive(Debug, Clone, Serialize, Deserialize)]
67#[serde(rename_all = "camelCase")]
68pub struct BaseSubscriptionUpdateRequest {
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub subscription_id: Option<i32>,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub command_type: Option<String>,
74}
75
76/// Subscription update request message
77///
78/// # Example
79///
80/// ```
81/// use redis_cloud::flexible::subscriptions::SubscriptionUpdateRequest;
82///
83/// let request = SubscriptionUpdateRequest::builder()
84/// .name("updated-subscription")
85/// .build();
86/// ```
87#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
88#[serde(rename_all = "camelCase")]
89pub struct SubscriptionUpdateRequest {
90 #[serde(skip_serializing_if = "Option::is_none")]
91 #[builder(default, setter(strip_option))]
92 pub subscription_id: Option<i32>,
93
94 /// Optional. Updated subscription name.
95 #[serde(skip_serializing_if = "Option::is_none")]
96 #[builder(default, setter(strip_option, into))]
97 pub name: Option<String>,
98
99 /// Optional. The payment method ID you'd like to use for this subscription. Must be a valid payment method ID for this account. Use GET /payment-methods to get all payment methods for your account. This value is optional if 'paymentMethod' is 'marketplace', but required if 'paymentMethod' is 'credit-card'.
100 #[serde(skip_serializing_if = "Option::is_none")]
101 #[builder(default, setter(strip_option))]
102 pub payment_method_id: Option<i32>,
103
104 /// Optional. The payment method for the subscription. If set to 'credit-card' , 'paymentMethodId' must be defined.
105 #[serde(skip_serializing_if = "Option::is_none")]
106 #[builder(default, setter(strip_option, into))]
107 pub payment_method: Option<String>,
108
109 #[serde(skip_serializing_if = "Option::is_none")]
110 #[builder(default, setter(strip_option, into))]
111 pub command_type: Option<String>,
112}
113
114/// Cloud provider, region, and networking details.
115#[derive(Debug, Clone, Serialize, Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub struct SubscriptionSpec {
118 /// Optional. Cloud provider. Default: 'AWS'
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub provider: Option<String>,
121
122 /// Optional. Cloud account identifier. Default: Redis internal cloud account (Cloud Account ID = 1). Use GET /cloud-accounts to list all available cloud accounts. Note: A subscription on Google Cloud can be created only with Redis internal cloud account.
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub cloud_account_id: Option<i32>,
125
126 /// The cloud provider region or list of regions (Active-Active only) and networking details.
127 pub regions: Vec<SubscriptionRegionSpec>,
128}
129
130/// Object representing a customer managed key (CMK), along with the region it is associated to.
131#[derive(Debug, Clone, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct CustomerManagedKey {
134 /// Required. Resource name of the customer managed key as defined by the cloud provider.
135 pub resource_name: String,
136
137 /// Name of region to for the customer managed key as defined by the cloud provider. Required for active-active subscriptions.
138 #[serde(skip_serializing_if = "Option::is_none")]
139 pub region: Option<String>,
140}
141
142/// Optional. Expected read and write throughput for this region.
143#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct LocalThroughput {
146 /// Specify one of the selected cloud provider regions for the subscription.
147 #[serde(skip_serializing_if = "Option::is_none")]
148 pub region: Option<String>,
149
150 /// Write operations for this region per second. Default: 1000 ops/sec
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub write_operations_per_second: Option<i64>,
153
154 /// Read operations for this region per second. Default: 1000 ops/sec
155 #[serde(skip_serializing_if = "Option::is_none")]
156 pub read_operations_per_second: Option<i64>,
157}
158
159/// List of databases in the subscription with local throughput details. Default: 1000 read and write ops/sec for each database
160#[derive(Debug, Clone, Serialize, Deserialize)]
161#[serde(rename_all = "camelCase")]
162pub struct CrdbRegionSpec {
163 /// Database name.
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub name: Option<String>,
166
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub local_throughput_measurement: Option<LocalThroughput>,
169}
170
171/// Subscription update request message
172#[derive(Debug, Clone, Serialize, Deserialize)]
173#[serde(rename_all = "camelCase")]
174pub struct SubscriptionUpdateCMKRequest {
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub subscription_id: Option<i32>,
177
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub command_type: Option<String>,
180
181 /// Optional. The grace period for deleting the subscription. If not set, will default to immediate deletion grace period.
182 #[serde(skip_serializing_if = "Option::is_none")]
183 pub deletion_grace_period: Option<String>,
184
185 /// The customer managed keys (CMK) to use for this subscription. If is active-active subscription, must set a key for each region.
186 pub customer_managed_keys: Vec<CustomerManagedKey>,
187}
188
189/// `SubscriptionPricings`
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct SubscriptionPricings {
192 #[serde(skip_serializing_if = "Option::is_none")]
193 pub pricing: Option<Vec<SubscriptionPricing>>,
194}
195
196/// Optional. Throughput measurement method.
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct DatabaseThroughputSpec {
199 /// Throughput measurement method. Use 'operations-per-second' for all new databases.
200 pub by: String,
201
202 /// Throughput value in the selected measurement method.
203 pub value: i64,
204}
205
206/// Optional. Redis advanced capabilities (also known as modules) to be provisioned in the database. Use GET /database-modules to get a list of available advanced capabilities.
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct DatabaseModuleSpec {
209 /// Redis advanced capability name. Use GET /database-modules for a list of available capabilities.
210 pub name: String,
211
212 /// Optional. Redis advanced capability parameters. Use GET /database-modules to get the available capabilities and their parameters.
213 #[serde(skip_serializing_if = "Option::is_none")]
214 pub parameters: Option<HashMap<String, Value>>,
215}
216
217/// Update Pro subscription
218#[derive(Debug, Clone, Serialize, Deserialize)]
219#[serde(rename_all = "camelCase")]
220pub struct CidrAllowlistUpdateRequest {
221 #[serde(skip_serializing_if = "Option::is_none")]
222 pub subscription_id: Option<i32>,
223
224 /// List of CIDR values. Example: ['10.1.1.0/32']
225 #[serde(skip_serializing_if = "Option::is_none")]
226 pub cidr_ips: Option<Vec<String>>,
227
228 /// List of AWS Security group IDs.
229 #[serde(skip_serializing_if = "Option::is_none")]
230 pub security_group_ids: Option<Vec<String>>,
231
232 #[serde(skip_serializing_if = "Option::is_none")]
233 pub command_type: Option<String>,
234}
235
236/// `SubscriptionMaintenanceWindowsSpec`
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct SubscriptionMaintenanceWindowsSpec {
239 /// Maintenance window mode: either 'manual' or 'automatic'. Must provide 'windows' if manual.
240 pub mode: String,
241
242 /// Maintenance window timeframes if mode is set to 'manual'. Up to 7 maintenance windows can be provided.
243 #[serde(skip_serializing_if = "Option::is_none")]
244 pub windows: Option<Vec<MaintenanceWindowSpec>>,
245}
246
247/// `MaintenanceWindowSkipStatus`
248#[derive(Debug, Clone, Serialize, Deserialize)]
249#[serde(rename_all = "camelCase")]
250pub struct MaintenanceWindowSkipStatus {
251 #[serde(skip_serializing_if = "Option::is_none")]
252 pub remaining_skips: Option<i32>,
253
254 #[serde(skip_serializing_if = "Option::is_none")]
255 pub current_skip_end: Option<String>,
256}
257
258/// List of active-active subscription regions
259#[derive(Debug, Clone, Serialize, Deserialize)]
260#[serde(rename_all = "camelCase")]
261pub struct ActiveActiveSubscriptionRegions {
262 #[serde(skip_serializing_if = "Option::is_none")]
263 pub subscription_id: Option<i32>,
264
265 /// HATEOAS links
266 #[serde(skip_serializing_if = "Option::is_none")]
267 pub links: Option<Vec<Link>>,
268}
269
270/// `SubscriptionPricing`
271#[derive(Debug, Clone, Serialize, Deserialize)]
272#[serde(rename_all = "camelCase")]
273pub struct SubscriptionPricing {
274 /// Database name this pricing applies to
275 #[serde(skip_serializing_if = "Option::is_none")]
276 pub database_name: Option<String>,
277
278 #[serde(skip_serializing_if = "Option::is_none")]
279 pub r#type: Option<String>,
280
281 #[serde(skip_serializing_if = "Option::is_none")]
282 pub type_details: Option<String>,
283
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub quantity: Option<i32>,
286
287 #[serde(skip_serializing_if = "Option::is_none")]
288 pub quantity_measurement: Option<String>,
289
290 #[serde(skip_serializing_if = "Option::is_none")]
291 pub price_per_unit: Option<f64>,
292
293 #[serde(skip_serializing_if = "Option::is_none")]
294 pub price_currency: Option<String>,
295
296 #[serde(skip_serializing_if = "Option::is_none")]
297 pub price_period: Option<String>,
298
299 #[serde(skip_serializing_if = "Option::is_none")]
300 pub region: Option<String>,
301}
302
303/// Request structure for creating a new Pro subscription
304///
305/// Defines configuration for flexible subscriptions including cloud providers,
306/// regions, deployment type, and initial database specifications.
307///
308/// # Example
309///
310/// ```
311/// use redis_cloud::flexible::subscriptions::{SubscriptionCreateRequest, SubscriptionSpec, SubscriptionDatabaseSpec, SubscriptionRegionSpec};
312///
313/// let request = SubscriptionCreateRequest::builder()
314/// .name("my-subscription")
315/// .cloud_providers(vec![
316/// SubscriptionSpec {
317/// provider: Some("AWS".to_string()),
318/// cloud_account_id: Some(1),
319/// regions: vec![SubscriptionRegionSpec {
320/// region: "us-east-1".to_string(),
321/// multiple_availability_zones: None,
322/// preferred_availability_zones: None,
323/// networking: None,
324/// }],
325/// }
326/// ])
327/// .databases(vec![
328/// SubscriptionDatabaseSpec {
329/// name: "my-database".to_string(),
330/// protocol: "redis".to_string(),
331/// memory_limit_in_gb: Some(1.0),
332/// dataset_size_in_gb: None,
333/// support_oss_cluster_api: None,
334/// data_persistence: None,
335/// replication: None,
336/// throughput_measurement: None,
337/// local_throughput_measurement: None,
338/// modules: None,
339/// quantity: None,
340/// average_item_size_in_bytes: None,
341/// resp_version: None,
342/// redis_version: None,
343/// sharding_type: None,
344/// query_performance_factor: None,
345/// }
346/// ])
347/// .build();
348/// ```
349#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
350#[serde(rename_all = "camelCase")]
351pub struct SubscriptionCreateRequest {
352 /// Optional. New subscription name.
353 #[serde(skip_serializing_if = "Option::is_none")]
354 #[builder(default, setter(strip_option, into))]
355 pub name: Option<String>,
356
357 /// Optional. When 'false': Creates a deployment plan and deploys it, creating any resources required by the plan. When 'true': creates a read-only deployment plan and does not create any resources. Default: 'false'
358 #[serde(skip_serializing_if = "Option::is_none")]
359 #[builder(default, setter(strip_option))]
360 pub dry_run: Option<bool>,
361
362 /// Optional. When 'single-region' or not set: Creates a single region subscription. When 'active-active': creates an Active-Active (multi-region) subscription.
363 #[serde(skip_serializing_if = "Option::is_none")]
364 #[builder(default, setter(strip_option, into))]
365 pub deployment_type: Option<String>,
366
367 /// Optional. The payment method for the subscription. If set to 'credit-card', 'paymentMethodId' must be defined. Default: 'credit-card'
368 #[serde(skip_serializing_if = "Option::is_none")]
369 #[builder(default, setter(strip_option, into))]
370 pub payment_method: Option<String>,
371
372 /// Optional. A valid payment method ID for this account. Use GET /payment-methods to get a list of all payment methods for your account. This value is optional if 'paymentMethod' is 'marketplace', but required for all other account types.
373 #[serde(skip_serializing_if = "Option::is_none")]
374 #[builder(default, setter(strip_option))]
375 pub payment_method_id: Option<i32>,
376
377 /// Optional. Memory storage preference: either 'ram' or a combination of 'ram-and-flash' (also known as Auto Tiering). Default: 'ram'
378 #[serde(skip_serializing_if = "Option::is_none")]
379 #[builder(default, setter(strip_option, into))]
380 pub memory_storage: Option<String>,
381
382 /// Optional. Persistent storage encryption secures data-at-rest for database persistence. You can use 'cloud-provider-managed-key' or 'customer-managed-key'. Default: 'cloud-provider-managed-key'
383 #[serde(skip_serializing_if = "Option::is_none")]
384 #[builder(default, setter(strip_option, into))]
385 pub persistent_storage_encryption_type: Option<String>,
386
387 /// Cloud provider, region, and networking details.
388 pub cloud_providers: Vec<SubscriptionSpec>,
389
390 /// One or more database specification(s) to create in this subscription.
391 pub databases: Vec<SubscriptionDatabaseSpec>,
392
393 /// Optional. Defines the Redis version of the databases created in this specific request. It doesn't determine future databases associated with this subscription. If not set, databases will use the default Redis version. This field is deprecated and will be removed in a future API version - use the database-level redisVersion property instead.
394 #[serde(skip_serializing_if = "Option::is_none")]
395 #[builder(default, setter(strip_option, into))]
396 pub redis_version: Option<String>,
397
398 #[serde(skip_serializing_if = "Option::is_none")]
399 #[builder(default, setter(strip_option, into))]
400 pub command_type: Option<String>,
401}
402
403/// Configuration regarding customer managed persistent storage encryption
404#[derive(Debug, Clone, Serialize, Deserialize)]
405#[serde(rename_all = "camelCase")]
406pub struct CustomerManagedKeyAccessDetails {
407 #[serde(skip_serializing_if = "Option::is_none")]
408 pub redis_service_account: Option<String>,
409
410 #[serde(skip_serializing_if = "Option::is_none")]
411 pub google_predefined_roles: Option<Vec<String>>,
412
413 #[serde(skip_serializing_if = "Option::is_none")]
414 pub google_custom_permissions: Option<Vec<String>>,
415
416 #[serde(skip_serializing_if = "Option::is_none")]
417 pub redis_iam_role: Option<String>,
418
419 #[serde(skip_serializing_if = "Option::is_none")]
420 pub required_key_policy_statements: Option<HashMap<String, Value>>,
421
422 #[serde(skip_serializing_if = "Option::is_none")]
423 pub deletion_grace_period_options: Option<Vec<String>>,
424}
425
426/// One or more database specification(s) to create in this subscription.
427#[derive(Debug, Clone, Serialize, Deserialize)]
428#[serde(rename_all = "camelCase")]
429pub struct SubscriptionDatabaseSpec {
430 /// Name of the database. Database name is limited to 40 characters or less and must include only letters, digits, and hyphens ('-'). It must start with a letter and end with a letter or digit.
431 pub name: String,
432
433 /// Optional. Database protocol. Only set to 'memcached' if you have a legacy application. Default: 'redis'
434 pub protocol: String,
435
436 /// Optional. Total memory in GB, including replication and other overhead. You cannot set both datasetSizeInGb and totalMemoryInGb.
437 #[serde(skip_serializing_if = "Option::is_none")]
438 pub memory_limit_in_gb: Option<f64>,
439
440 /// Optional. The maximum amount of data in the dataset for this database in GB. You cannot set both datasetSizeInGb and totalMemoryInGb. If ‘replication’ is 'true', the database’s total memory will be twice as large as the datasetSizeInGb.If ‘replication’ is false, the database’s total memory will be the datasetSizeInGb value.
441 #[serde(skip_serializing_if = "Option::is_none")]
442 pub dataset_size_in_gb: Option<f64>,
443
444 /// Optional. Support Redis [OSS Cluster API](https://redis.io/docs/latest/operate/rc/databases/configuration/clustering/#oss-cluster-api). Default: 'false'
445 #[serde(skip_serializing_if = "Option::is_none")]
446 pub support_oss_cluster_api: Option<bool>,
447
448 /// Optional. Type and rate of data persistence in persistent storage. Default: 'none'
449 #[serde(skip_serializing_if = "Option::is_none")]
450 pub data_persistence: Option<String>,
451
452 /// Optional. Databases replication. Default: 'true'
453 #[serde(skip_serializing_if = "Option::is_none")]
454 pub replication: Option<bool>,
455
456 #[serde(skip_serializing_if = "Option::is_none")]
457 pub throughput_measurement: Option<DatabaseThroughputSpec>,
458
459 /// Optional. Expected throughput per region for an Active-Active database. Default: 1000 read and write ops/sec for each region
460 #[serde(skip_serializing_if = "Option::is_none")]
461 pub local_throughput_measurement: Option<Vec<LocalThroughput>>,
462
463 /// Optional. Redis advanced capabilities (also known as modules) to be provisioned in the database. Use GET /database-modules to get a list of available advanced capabilities.
464 #[serde(skip_serializing_if = "Option::is_none")]
465 pub modules: Option<Vec<DatabaseModuleSpec>>,
466
467 /// Optional. Number of databases that will be created with these settings. Default: 1
468 #[serde(skip_serializing_if = "Option::is_none")]
469 pub quantity: Option<i32>,
470
471 /// Optional. Relevant only to ram-and-flash (also known as Auto Tiering) subscriptions. Estimated average size in bytes of the items stored in the database. Default: 1000
472 #[serde(skip_serializing_if = "Option::is_none")]
473 pub average_item_size_in_bytes: Option<i64>,
474
475 /// Optional. Redis Serialization Protocol version. Must be compatible with Redis version.
476 #[serde(skip_serializing_if = "Option::is_none")]
477 pub resp_version: Option<String>,
478
479 /// Optional. If specified, redisVersion defines the Redis database version. If omitted, the Redis version will be set to the default version (available in 'GET /subscriptions/redis-versions')
480 #[serde(skip_serializing_if = "Option::is_none")]
481 pub redis_version: Option<String>,
482
483 /// Optional. Database [Hashing policy](https://redis.io/docs/latest/operate/rc/databases/configuration/clustering/#manage-the-hashing-policy).
484 #[serde(skip_serializing_if = "Option::is_none")]
485 pub sharding_type: Option<String>,
486
487 /// Optional. The query performance factor adds extra compute power specifically for search and query databases. You can increase your queries per second by the selected factor.
488 #[serde(skip_serializing_if = "Option::is_none")]
489 pub query_performance_factor: Option<String>,
490}
491
492/// Optional. Cloud networking details, per region. Required if creating an Active-Active subscription.
493#[derive(Debug, Clone, Serialize, Deserialize)]
494#[serde(rename_all = "camelCase")]
495pub struct SubscriptionRegionNetworkingSpec {
496 /// Optional. Deployment CIDR mask. Must be a valid CIDR format with a range of 256 IP addresses. Default for single-region subscriptions: If using Redis internal cloud account, 192.168.0.0/24
497 #[serde(skip_serializing_if = "Option::is_none")]
498 pub deployment_cidr: Option<String>,
499
500 /// Optional. Enter a VPC identifier that exists in the hosted AWS account. Creates a new VPC if not set. VPC Identifier must be in a valid format (for example: 'vpc-0125be68a4625884ad') and must exist within the hosting account.
501 #[serde(skip_serializing_if = "Option::is_none")]
502 pub vpc_id: Option<String>,
503
504 /// Optional. Enter a list of subnets identifiers that exists in the hosted AWS account. Subnet Identifier must exist within the hosting account.
505 #[serde(skip_serializing_if = "Option::is_none")]
506 pub subnet_ids: Option<Vec<String>>,
507
508 /// Optional. Enter a security group identifier that exists in the hosted AWS account. Security group Identifier must be in a valid format (for example: 'sg-0125be68a4625884ad') and must exist within the hosting account.
509 #[serde(skip_serializing_if = "Option::is_none")]
510 pub security_group_id: Option<String>,
511}
512
513/// `RedisVersion`
514#[derive(Debug, Clone, Serialize, Deserialize)]
515#[serde(rename_all = "camelCase")]
516pub struct RedisVersion {
517 #[serde(skip_serializing_if = "Option::is_none")]
518 pub version: Option<String>,
519
520 #[serde(skip_serializing_if = "Option::is_none")]
521 pub eol_date: Option<String>,
522
523 #[serde(skip_serializing_if = "Option::is_none")]
524 pub is_preview: Option<bool>,
525
526 #[serde(skip_serializing_if = "Option::is_none")]
527 pub is_default: Option<bool>,
528}
529
530/// `MaintenanceWindow`
531#[derive(Debug, Clone, Serialize, Deserialize)]
532#[serde(rename_all = "camelCase")]
533pub struct MaintenanceWindow {
534 #[serde(skip_serializing_if = "Option::is_none")]
535 pub days: Option<Vec<String>>,
536
537 #[serde(skip_serializing_if = "Option::is_none")]
538 pub start_hour: Option<i32>,
539
540 #[serde(skip_serializing_if = "Option::is_none")]
541 pub duration_in_hours: Option<i32>,
542}
543
544/// Cloud provider details for a subscription
545#[derive(Debug, Clone, Serialize, Deserialize)]
546#[serde(rename_all = "camelCase")]
547pub struct CloudDetail {
548 /// Cloud provider (e.g., "AWS", "GCP", "Azure")
549 #[serde(skip_serializing_if = "Option::is_none")]
550 pub provider: Option<String>,
551
552 /// Cloud account ID (Redis Cloud internal or BYOA)
553 #[serde(skip_serializing_if = "Option::is_none")]
554 pub cloud_account_id: Option<i32>,
555
556 /// AWS account ID (for AWS deployments)
557 #[serde(skip_serializing_if = "Option::is_none")]
558 pub aws_account_id: Option<String>,
559
560 /// Total size of the subscription in GB
561 #[serde(skip_serializing_if = "Option::is_none")]
562 pub total_size_in_gb: Option<f64>,
563
564 /// Regions configured for this cloud provider
565 #[serde(skip_serializing_if = "Option::is_none")]
566 pub regions: Option<Vec<SubscriptionRegion>>,
567}
568
569/// Region details in a subscription response
570#[derive(Debug, Clone, Serialize, Deserialize)]
571#[serde(rename_all = "camelCase")]
572pub struct SubscriptionRegion {
573 /// Region name (e.g., "us-east-1")
574 #[serde(skip_serializing_if = "Option::is_none")]
575 pub region: Option<String>,
576
577 /// Networking configuration for this region
578 #[serde(skip_serializing_if = "Option::is_none")]
579 pub networking: Option<Vec<SubscriptionNetworking>>,
580
581 /// Preferred availability zones
582 #[serde(skip_serializing_if = "Option::is_none")]
583 pub preferred_availability_zones: Option<Vec<String>>,
584
585 /// Whether multiple availability zones are enabled
586 #[serde(skip_serializing_if = "Option::is_none")]
587 pub multiple_availability_zones: Option<bool>,
588}
589
590/// Networking configuration in a subscription region
591#[derive(Debug, Clone, Serialize, Deserialize)]
592#[serde(rename_all = "camelCase")]
593pub struct SubscriptionNetworking {
594 /// Deployment CIDR
595 #[serde(skip_serializing_if = "Option::is_none")]
596 pub deployment_cidr: Option<String>,
597
598 /// VPC ID
599 #[serde(skip_serializing_if = "Option::is_none")]
600 pub vpc_id: Option<String>,
601
602 /// Subnet ID
603 #[serde(skip_serializing_if = "Option::is_none")]
604 pub subnet_id: Option<String>,
605}
606
607/// `RedisLabs` Subscription information
608#[derive(Debug, Clone, Serialize, Deserialize)]
609#[serde(rename_all = "camelCase")]
610/// Subscription
611///
612/// Represents a Redis Cloud subscription with all known API fields as first-class struct members.
613/// The `extra` field is reserved only for truly unknown/future fields that may be added to the API.
614pub struct Subscription {
615 /// Subscription ID
616 #[serde(skip_serializing_if = "Option::is_none")]
617 pub id: Option<i32>,
618
619 /// Subscription name
620 #[serde(skip_serializing_if = "Option::is_none")]
621 pub name: Option<String>,
622
623 /// Subscription status (e.g., "active", "pending", "error")
624 #[serde(skip_serializing_if = "Option::is_none")]
625 pub status: Option<String>,
626
627 /// Payment method ID
628 #[serde(skip_serializing_if = "Option::is_none")]
629 pub payment_method_id: Option<i32>,
630
631 /// Payment method type (e.g., "credit-card", "marketplace")
632 #[serde(skip_serializing_if = "Option::is_none")]
633 pub payment_method_type: Option<String>,
634
635 /// Payment method (e.g., "credit-card", "marketplace")
636 #[serde(skip_serializing_if = "Option::is_none")]
637 pub payment_method: Option<String>,
638
639 /// Memory storage type: "ram" or "ram-and-flash" (Auto Tiering)
640 #[serde(skip_serializing_if = "Option::is_none")]
641 pub memory_storage: Option<String>,
642
643 /// Persistent storage encryption type
644 #[serde(skip_serializing_if = "Option::is_none")]
645 pub persistent_storage_encryption_type: Option<String>,
646
647 /// Deployment type: "single-region" or "active-active"
648 #[serde(skip_serializing_if = "Option::is_none")]
649 pub deployment_type: Option<String>,
650
651 /// Number of databases in this subscription
652 #[serde(skip_serializing_if = "Option::is_none")]
653 pub number_of_databases: Option<i32>,
654
655 /// Cloud provider details (AWS, GCP, Azure configurations)
656 #[serde(skip_serializing_if = "Option::is_none")]
657 pub cloud_details: Option<Vec<CloudDetail>>,
658
659 /// Pricing details for the subscription
660 #[serde(skip_serializing_if = "Option::is_none")]
661 pub pricing: Option<Vec<SubscriptionPricing>>,
662
663 /// Redis version for databases created in this subscription (deprecated)
664 #[serde(skip_serializing_if = "Option::is_none")]
665 pub redis_version: Option<String>,
666
667 /// Deletion grace period for customer-managed keys
668 #[serde(skip_serializing_if = "Option::is_none")]
669 pub deletion_grace_period: Option<String>,
670
671 /// Customer-managed key access details for encryption
672 #[serde(skip_serializing_if = "Option::is_none")]
673 pub customer_managed_key_access_details: Option<CustomerManagedKeyAccessDetails>,
674
675 /// Whether storage encryption is enabled
676 #[serde(skip_serializing_if = "Option::is_none")]
677 pub storage_encryption: Option<bool>,
678
679 /// Whether public endpoint access is enabled
680 #[serde(skip_serializing_if = "Option::is_none")]
681 pub public_endpoint_access: Option<bool>,
682
683 /// Timestamp when subscription was created
684 #[serde(skip_serializing_if = "Option::is_none")]
685 pub created_timestamp: Option<String>,
686
687 /// HATEOAS links for API navigation
688 #[serde(skip_serializing_if = "Option::is_none")]
689 pub links: Option<Vec<Link>>,
690}
691
692/// Maintenance window timeframes if mode is set to 'manual'. Up to 7 maintenance windows can be provided.
693#[derive(Debug, Clone, Serialize, Deserialize)]
694#[serde(rename_all = "camelCase")]
695pub struct MaintenanceWindowSpec {
696 /// Starting hour of the maintenance window. Can be between '0' (12 AM in the deployment region's local time) and '23' (11 PM in the deployment region's local time).
697 pub start_hour: i32,
698
699 /// The duration of the maintenance window in hours. Can be between 4-24 hours (or 8-24 hours if using 'ram-and-flash').
700 pub duration_in_hours: i32,
701
702 /// Days where this maintenance window applies. Can contain one or more of: "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", or "Sunday".
703 pub days: Vec<String>,
704}
705
706/// `RedisLabs` list of subscriptions in current account
707///
708/// Response from GET /subscriptions
709#[derive(Debug, Clone, Serialize, Deserialize)]
710#[serde(rename_all = "camelCase")]
711pub struct AccountSubscriptions {
712 /// Account ID
713 #[serde(skip_serializing_if = "Option::is_none")]
714 pub account_id: Option<i32>,
715
716 /// List of subscriptions (typically in extra as 'subscriptions' array)
717 #[serde(skip_serializing_if = "Option::is_none")]
718 pub subscriptions: Option<Vec<Subscription>>,
719
720 /// HATEOAS links for API navigation
721 #[serde(skip_serializing_if = "Option::is_none")]
722 pub links: Option<Vec<Link>>,
723}
724
725/// Active active region creation request message
726#[derive(Debug, Clone, Serialize, Deserialize)]
727#[serde(rename_all = "camelCase")]
728pub struct ActiveActiveRegionCreateRequest {
729 #[serde(skip_serializing_if = "Option::is_none")]
730 pub subscription_id: Option<i32>,
731
732 /// Name of region to add as defined by the cloud provider.
733 #[serde(skip_serializing_if = "Option::is_none")]
734 pub region: Option<String>,
735
736 /// Optional. Enter a VPC identifier that exists in the hosted AWS account. Creates a new VPC if not set. VPC Identifier must be in a valid format and must exist within the hosting account.
737 #[serde(skip_serializing_if = "Option::is_none")]
738 pub vpc_id: Option<String>,
739
740 /// Deployment CIDR mask. Must be a valid CIDR format with a range of 256 IP addresses.
741 pub deployment_cidr: String,
742
743 /// Optional. When 'false': Creates a deployment plan and deploys it, creating any resources required by the plan. When 'true': creates a read-only deployment plan, and does not create any resources. Default: 'false'
744 #[serde(skip_serializing_if = "Option::is_none")]
745 pub dry_run: Option<bool>,
746
747 /// List of databases in the subscription with local throughput details. Default: 1000 read and write ops/sec for each database
748 #[serde(skip_serializing_if = "Option::is_none")]
749 pub databases: Option<Vec<CrdbRegionSpec>>,
750
751 /// Optional. RESP version must be compatible with Redis version.
752 #[serde(skip_serializing_if = "Option::is_none")]
753 pub resp_version: Option<String>,
754
755 /// Optional. Resource name of the customer managed key as defined by the cloud provider for customer managed subscriptions.
756 #[serde(skip_serializing_if = "Option::is_none")]
757 pub customer_managed_key_resource_name: Option<String>,
758
759 #[serde(skip_serializing_if = "Option::is_none")]
760 pub command_type: Option<String>,
761}
762
763/// `RedisVersions`
764#[derive(Debug, Clone, Serialize, Deserialize)]
765#[serde(rename_all = "camelCase")]
766pub struct RedisVersions {
767 #[serde(skip_serializing_if = "Option::is_none")]
768 pub redis_versions: Option<Vec<RedisVersion>>,
769}
770
771/// Active active region deletion request message
772#[derive(Debug, Clone, Serialize, Deserialize)]
773#[serde(rename_all = "camelCase")]
774pub struct ActiveActiveRegionDeleteRequest {
775 #[serde(skip_serializing_if = "Option::is_none")]
776 pub subscription_id: Option<i32>,
777
778 /// The names of the regions to delete.
779 #[serde(skip_serializing_if = "Option::is_none")]
780 pub regions: Option<Vec<ActiveActiveRegionToDelete>>,
781
782 /// Optional. When 'false': Creates a deployment plan and deploys it, deleting any resources required by the plan. When 'true': creates a read-only deployment plan and does not delete or modify any resources. Default: 'false'
783 #[serde(skip_serializing_if = "Option::is_none")]
784 pub dry_run: Option<bool>,
785
786 #[serde(skip_serializing_if = "Option::is_none")]
787 pub command_type: Option<String>,
788}
789
790/// The names of the regions to delete.
791#[derive(Debug, Clone, Serialize, Deserialize)]
792pub struct ActiveActiveRegionToDelete {
793 /// Name of the cloud provider region to delete.
794 #[serde(skip_serializing_if = "Option::is_none")]
795 pub region: Option<String>,
796}
797
798/// `TaskStateUpdate`
799#[derive(Debug, Clone, Serialize, Deserialize)]
800#[serde(rename_all = "camelCase")]
801pub struct TaskStateUpdate {
802 #[serde(skip_serializing_if = "Option::is_none")]
803 pub task_id: Option<String>,
804
805 #[serde(skip_serializing_if = "Option::is_none")]
806 pub command_type: Option<String>,
807
808 #[serde(skip_serializing_if = "Option::is_none")]
809 pub status: Option<String>,
810
811 #[serde(skip_serializing_if = "Option::is_none")]
812 pub description: Option<String>,
813
814 #[serde(skip_serializing_if = "Option::is_none")]
815 pub timestamp: Option<String>,
816
817 #[serde(skip_serializing_if = "Option::is_none")]
818 pub response: Option<ProcessorResponse>,
819
820 /// HATEOAS links
821 #[serde(skip_serializing_if = "Option::is_none")]
822 pub links: Option<Vec<Link>>,
823}
824
825/// The cloud provider region or list of regions (Active-Active only) and networking details.
826#[derive(Debug, Clone, Serialize, Deserialize)]
827#[serde(rename_all = "camelCase")]
828pub struct SubscriptionRegionSpec {
829 /// Deployment region as defined by the cloud provider.
830 pub region: String,
831
832 /// Optional. Support deployment on multiple availability zones within the selected region. Default: 'false'
833 #[serde(skip_serializing_if = "Option::is_none")]
834 pub multiple_availability_zones: Option<bool>,
835
836 /// Optional. List the zone ID(s) for your preferred availability zone(s) for the cloud provider and region. If ‘multipleAvailabilityZones’ is set to 'true', you must list three availability zones. Otherwise, list one availability zone.
837 #[serde(skip_serializing_if = "Option::is_none")]
838 pub preferred_availability_zones: Option<Vec<String>>,
839
840 #[serde(skip_serializing_if = "Option::is_none")]
841 pub networking: Option<SubscriptionRegionNetworkingSpec>,
842}
843
844/// `SubscriptionMaintenanceWindows`
845#[derive(Debug, Clone, Serialize, Deserialize)]
846#[serde(rename_all = "camelCase")]
847pub struct SubscriptionMaintenanceWindows {
848 #[serde(skip_serializing_if = "Option::is_none")]
849 pub mode: Option<String>,
850
851 #[serde(skip_serializing_if = "Option::is_none")]
852 pub time_zone: Option<String>,
853
854 #[serde(skip_serializing_if = "Option::is_none")]
855 pub windows: Option<Vec<MaintenanceWindow>>,
856
857 #[serde(skip_serializing_if = "Option::is_none")]
858 pub skip_status: Option<MaintenanceWindowSkipStatus>,
859}
860
861// ============================================================================
862// Handler
863// ============================================================================
864
865/// Handler for Pro subscription operations
866///
867/// Manages flexible subscriptions with auto-scaling, multi-region support,
868/// Active-Active configurations, and advanced networking features.
869pub struct SubscriptionHandler {
870 client: CloudClient,
871}
872
873impl SubscriptionHandler {
874 /// Create a new handler
875 #[must_use]
876 pub fn new(client: CloudClient) -> Self {
877 Self { client }
878 }
879
880 /// Get Pro subscriptions
881 ///
882 /// Gets a list of all Pro subscriptions in the current account.
883 ///
884 /// GET /subscriptions
885 ///
886 /// # Example
887 ///
888 /// ```no_run
889 /// use redis_cloud::CloudClient;
890 ///
891 /// # async fn example() -> redis_cloud::Result<()> {
892 /// let client = CloudClient::builder()
893 /// .api_key("your-api-key")
894 /// .api_secret("your-api-secret")
895 /// .build()?;
896 ///
897 /// let subscriptions = client.subscriptions().get_all_subscriptions().await?;
898 ///
899 /// // Access subscription data
900 /// if let Some(subs) = &subscriptions.subscriptions {
901 /// println!("Found {} subscriptions", subs.len());
902 /// }
903 /// # Ok(())
904 /// # }
905 /// ```
906 pub async fn get_all_subscriptions(&self) -> Result<AccountSubscriptions> {
907 self.client.get("/subscriptions").await
908 }
909
910 /// Create Pro subscription
911 /// Creates a new Redis Cloud Pro subscription.
912 ///
913 /// POST /subscriptions
914 pub async fn create_subscription(
915 &self,
916 request: &SubscriptionCreateRequest,
917 ) -> Result<TaskStateUpdate> {
918 self.client.post("/subscriptions", request).await
919 }
920
921 /// Get available Redis database versions
922 /// Gets a list of all available Redis database versions for Pro subscriptions.
923 ///
924 /// GET /subscriptions/redis-versions
925 pub async fn get_redis_versions(&self, subscription_id: Option<i32>) -> Result<RedisVersions> {
926 let mut query = Vec::new();
927 if let Some(v) = subscription_id {
928 query.push(format!("subscriptionId={v}"));
929 }
930 let query_string = if query.is_empty() {
931 String::new()
932 } else {
933 format!("?{}", query.join("&"))
934 };
935 self.client
936 .get(&format!("/subscriptions/redis-versions{query_string}"))
937 .await
938 }
939
940 /// Delete Pro subscription
941 /// Delete the specified Pro subscription. All databases in the subscription must be deleted before deleting it.
942 ///
943 /// DELETE /subscriptions/{subscriptionId}
944 pub async fn delete_subscription_by_id(&self, subscription_id: i32) -> Result<TaskStateUpdate> {
945 let response = self
946 .client
947 .delete_raw(&format!("/subscriptions/{subscription_id}"))
948 .await?;
949 serde_json::from_value(response).map_err(Into::into)
950 }
951
952 /// Get a single Pro subscription
953 ///
954 /// Gets information on the specified Pro subscription.
955 ///
956 /// GET /subscriptions/{subscriptionId}
957 ///
958 /// # Example
959 ///
960 /// ```no_run
961 /// use redis_cloud::CloudClient;
962 ///
963 /// # async fn example() -> redis_cloud::Result<()> {
964 /// let client = CloudClient::builder()
965 /// .api_key("your-api-key")
966 /// .api_secret("your-api-secret")
967 /// .build()?;
968 ///
969 /// let subscription = client.subscriptions().get_subscription_by_id(123).await?;
970 ///
971 /// println!("Subscription: {} (status: {:?})",
972 /// subscription.name.unwrap_or_default(),
973 /// subscription.status);
974 /// # Ok(())
975 /// # }
976 /// ```
977 pub async fn get_subscription_by_id(&self, subscription_id: i32) -> Result<Subscription> {
978 self.client
979 .get(&format!("/subscriptions/{subscription_id}"))
980 .await
981 }
982
983 /// Update Pro subscription
984 /// Updates the specified Pro subscription.
985 ///
986 /// PUT /subscriptions/{subscriptionId}
987 pub async fn update_subscription(
988 &self,
989 subscription_id: i32,
990 request: &BaseSubscriptionUpdateRequest,
991 ) -> Result<TaskStateUpdate> {
992 self.client
993 .put(&format!("/subscriptions/{subscription_id}"), request)
994 .await
995 }
996
997 /// Get Pro subscription CIDR allowlist
998 /// (Self-hosted AWS subscriptions only) Gets a Pro subscription's CIDR allowlist.
999 ///
1000 /// GET /subscriptions/{subscriptionId}/cidr
1001 pub async fn get_cidr_allowlist(&self, subscription_id: i32) -> Result<TaskStateUpdate> {
1002 self.client
1003 .get(&format!("/subscriptions/{subscription_id}/cidr"))
1004 .await
1005 }
1006
1007 /// Update Pro subscription CIDR allowlist
1008 /// (Self-hosted AWS subscriptions only) Updates a Pro subscription's CIDR allowlist.
1009 ///
1010 /// PUT /subscriptions/{subscriptionId}/cidr
1011 pub async fn update_subscription_cidr_allowlist(
1012 &self,
1013 subscription_id: i32,
1014 request: &CidrAllowlistUpdateRequest,
1015 ) -> Result<TaskStateUpdate> {
1016 self.client
1017 .put(&format!("/subscriptions/{subscription_id}/cidr"), request)
1018 .await
1019 }
1020
1021 /// Get Pro subscription maintenance windows
1022 /// Gets maintenance windows for the specified Pro subscription.
1023 ///
1024 /// GET /subscriptions/{subscriptionId}/maintenance-windows
1025 pub async fn get_subscription_maintenance_windows(
1026 &self,
1027 subscription_id: i32,
1028 ) -> Result<SubscriptionMaintenanceWindows> {
1029 self.client
1030 .get(&format!(
1031 "/subscriptions/{subscription_id}/maintenance-windows"
1032 ))
1033 .await
1034 }
1035
1036 /// Update Pro subscription maintenance windows
1037 /// Updates maintenance windows for the specified Pro subscription.
1038 ///
1039 /// PUT /subscriptions/{subscriptionId}/maintenance-windows
1040 pub async fn update_subscription_maintenance_windows(
1041 &self,
1042 subscription_id: i32,
1043 request: &SubscriptionMaintenanceWindowsSpec,
1044 ) -> Result<TaskStateUpdate> {
1045 self.client
1046 .put(
1047 &format!("/subscriptions/{subscription_id}/maintenance-windows"),
1048 request,
1049 )
1050 .await
1051 }
1052
1053 /// Get Pro subscription pricing
1054 /// Gets pricing details for the specified Pro subscription.
1055 ///
1056 /// GET /subscriptions/{subscriptionId}/pricing
1057 pub async fn get_subscription_pricing(
1058 &self,
1059 subscription_id: i32,
1060 ) -> Result<SubscriptionPricings> {
1061 self.client
1062 .get(&format!("/subscriptions/{subscription_id}/pricing"))
1063 .await
1064 }
1065
1066 /// Delete regions from an Active-Active subscription
1067 /// (Active-Active subscriptions only) Deletes one or more regions from the specified Active-Active subscription.
1068 ///
1069 /// DELETE /subscriptions/{subscriptionId}/regions
1070 pub async fn delete_regions_from_active_active_subscription(
1071 &self,
1072 subscription_id: i32,
1073 request: &ActiveActiveRegionDeleteRequest,
1074 ) -> Result<TaskStateUpdate> {
1075 // TODO: DELETE with body not yet supported by client
1076 let _ = request; // Suppress unused variable warning
1077 let response = self
1078 .client
1079 .delete_raw(&format!("/subscriptions/{subscription_id}/regions"))
1080 .await?;
1081 serde_json::from_value(response).map_err(Into::into)
1082 }
1083
1084 /// Get regions in an Active-Active subscription
1085 /// (Active-Active subscriptions only) Gets a list of regions in the specified Active-Active subscription.
1086 ///
1087 /// GET /subscriptions/{subscriptionId}/regions
1088 pub async fn get_regions_from_active_active_subscription(
1089 &self,
1090 subscription_id: i32,
1091 ) -> Result<ActiveActiveSubscriptionRegions> {
1092 self.client
1093 .get(&format!("/subscriptions/{subscription_id}/regions"))
1094 .await
1095 }
1096
1097 /// Add region to Active-Active subscription
1098 /// Adds a new region to an Active-Active subscription.
1099 ///
1100 /// POST /subscriptions/{subscriptionId}/regions
1101 pub async fn add_new_region_to_active_active_subscription(
1102 &self,
1103 subscription_id: i32,
1104 request: &ActiveActiveRegionCreateRequest,
1105 ) -> Result<TaskStateUpdate> {
1106 self.client
1107 .post(
1108 &format!("/subscriptions/{subscription_id}/regions"),
1109 request,
1110 )
1111 .await
1112 }
1113}