Skip to main content

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}