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