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