Skip to main content

redis_cloud/flexible/
databases.rs

1//! Database management operations for Pro subscriptions
2//!
3//! This module provides comprehensive database management functionality for Redis Cloud
4//! Pro subscriptions, including creation, configuration, backup, import/export, and
5//! monitoring capabilities.
6//!
7//! # Overview
8//!
9//! Pro databases offer the full range of Redis Cloud features including high availability,
10//! auto-scaling, clustering, modules, and advanced data persistence options. They can be
11//! deployed across multiple cloud providers and regions.
12//!
13//! # Key Features
14//!
15//! - **Database Lifecycle**: Create, update, delete, and manage databases
16//! - **Backup & Restore**: Automated and on-demand backup operations
17//! - **Import/Export**: Import data from RDB files or other Redis instances
18//! - **Modules**: Support for `RedisJSON`, `RediSearch`, `RedisGraph`, `RedisTimeSeries`, `RedisBloom`
19//! - **High Availability**: Replication, auto-failover, and clustering support
20//! - **Monitoring**: Metrics, alerts, and performance insights
21//! - **Security**: TLS, password protection, and ACL support
22//!
23//! # Database Configuration Options
24//!
25//! - Memory limits from 250MB to 500GB+
26//! - Support for Redis OSS Cluster API
27//! - Data persistence: AOF, snapshot, or both
28//! - Data eviction policies
29//! - Replication and clustering
30//! - Custom Redis versions
31//!
32//! # Example Usage
33//!
34//! ```no_run
35//! use redis_cloud::{CloudClient, DatabaseHandler};
36//!
37//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
38//! let client = CloudClient::builder()
39//!     .api_key("your-api-key")
40//!     .api_secret("your-api-secret")
41//!     .build()?;
42//!
43//! let handler = DatabaseHandler::new(client);
44//!
45//! // List all databases in a subscription (subscription ID 123)
46//! let databases = handler.get_subscription_databases(123, None, None).await?;
47//!
48//! // Get specific database details
49//! let database = handler.get_subscription_database_by_id(123, 456).await?;
50//! # Ok(())
51//! # }
52//! ```
53
54use crate::types::Link;
55pub use crate::types::{CloudTag, CloudTags, DatabaseTrafficStateResponse, Tag, TaskStateUpdate};
56use crate::{CloudClient, Result};
57use async_stream::try_stream;
58use futures_core::Stream;
59use serde::{Deserialize, Deserializer, Serialize};
60use serde_json::Value;
61use std::collections::HashMap;
62use typed_builder::TypedBuilder;
63
64// ============================================================================
65// Models
66// ============================================================================
67
68/// `RedisLabs` Account Subscription Databases information
69///
70/// Response from GET /subscriptions/{subscriptionId}/databases
71#[derive(Debug, Clone, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase")]
73pub struct AccountSubscriptionDatabases {
74    /// Account ID
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub account_id: Option<i32>,
77
78    /// Subscription information with nested databases array.
79    /// The API returns this as an array, each element containing subscriptionId, databases, and links.
80    #[serde(default, deserialize_with = "deserialize_subscription_info")]
81    pub subscription: Vec<SubscriptionDatabasesInfo>,
82
83    /// HATEOAS links for API navigation
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub links: Option<Vec<Link>>,
86}
87
88/// Subscription databases info returned within `AccountSubscriptionDatabases`
89#[derive(Debug, Clone, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct SubscriptionDatabasesInfo {
92    /// Subscription ID
93    pub subscription_id: i32,
94
95    /// Number of databases (may not always be present)
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub number_of_databases: Option<i32>,
98
99    /// List of databases in this subscription
100    #[serde(default)]
101    pub databases: Vec<Database>,
102
103    /// HATEOAS links
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub links: Option<Vec<Link>>,
106}
107
108/// Custom deserializer that handles both object and array formats for subscription field.
109/// The API returns an array, but some test mocks use an object format.
110fn deserialize_subscription_info<'de, D>(
111    deserializer: D,
112) -> std::result::Result<Vec<SubscriptionDatabasesInfo>, D::Error>
113where
114    D: Deserializer<'de>,
115{
116    let value: Option<Value> = Option::deserialize(deserializer)?;
117
118    match value {
119        None => Ok(Vec::new()),
120        Some(Value::Array(arr)) => {
121            serde_json::from_value(Value::Array(arr)).map_err(serde::de::Error::custom)
122        }
123        Some(Value::Object(obj)) => {
124            // Single object - wrap in array
125            let item: SubscriptionDatabasesInfo =
126                serde_json::from_value(Value::Object(obj)).map_err(serde::de::Error::custom)?;
127            Ok(vec![item])
128        }
129        Some(other) => Err(serde::de::Error::custom(format!(
130            "expected array or object for subscription, got {other:?}"
131        ))),
132    }
133}
134
135/// Optional. Expected read and write throughput for this region.
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct LocalThroughput {
139    /// Specify one of the selected cloud provider regions for the subscription.
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub region: Option<String>,
142
143    /// Write operations for this region per second. Default: 1000 ops/sec
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub write_operations_per_second: Option<i64>,
146
147    /// Read operations for this region per second. Default: 1000 ops/sec
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub read_operations_per_second: Option<i64>,
150}
151
152/// Database tag update request message
153#[derive(Debug, Clone, Serialize, Deserialize)]
154#[serde(rename_all = "camelCase")]
155pub struct DatabaseTagUpdateRequest {
156    /// Subscription ID being updated. Server-populated from the path.
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub subscription_id: Option<i32>,
159
160    /// Database ID being updated. Server-populated from the path.
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub database_id: Option<i32>,
163
164    /// Tag key being updated. Server-populated from the path.
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub key: Option<String>,
167
168    /// Database tag value
169    pub value: String,
170
171    /// Read-only on the response; populated by the server with the
172    /// operation type (e.g. `"UPDATE_DATABASE_TAG"`).
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub command_type: Option<String>,
175}
176
177/// Active-Active database flush request message
178#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct CrdbFlushRequest {
181    /// Subscription ID being updated. Server-populated from the path.
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub subscription_id: Option<i32>,
184
185    /// Database ID being updated. Server-populated from the path.
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub database_id: Option<i32>,
188
189    /// Read-only on the response; populated by the server with the
190    /// operation type (e.g. `"FLUSH_CRDB"`).
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub command_type: Option<String>,
193}
194
195/// Database certificate
196#[derive(Debug, Clone, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct DatabaseCertificate {
199    /// An X.509 PEM (base64) encoded server certificate with new line characters replaced by '\n'.
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub public_certificate_pem_string: Option<String>,
202}
203
204/// Database tags update request message
205#[derive(Debug, Clone, Serialize, Deserialize)]
206#[serde(rename_all = "camelCase")]
207pub struct DatabaseTagsUpdateRequest {
208    /// Subscription ID being updated. Server-populated from the path.
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub subscription_id: Option<i32>,
211
212    /// Database ID being updated. Server-populated from the path.
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub database_id: Option<i32>,
215
216    /// List of database tags.
217    pub tags: Vec<Tag>,
218
219    /// Read-only on the response; populated by the server with the
220    /// operation type (e.g. `"UPDATE_DATABASE_TAGS"`).
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub command_type: Option<String>,
223}
224
225/// Optional. This database will be a replica of the specified Redis databases, provided as a list of objects with endpoint and certificate details.
226#[derive(Debug, Clone, Serialize, Deserialize)]
227#[serde(rename_all = "camelCase")]
228pub struct DatabaseSyncSourceSpec {
229    /// Redis URI of a source database. Example format: 'redis://user:password@host:port'. If the URI provided is a Redis Cloud database, only host and port should be provided. Example: '<redis://endpoint1:6379>'.
230    pub endpoint: String,
231
232    /// Defines if encryption should be used to connect to the sync source. If not set the source is a Redis Cloud database, it will automatically detect if the source uses encryption.
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub encryption: Option<bool>,
235
236    /// TLS/SSL certificate chain of the sync source. If not set and the source is a Redis Cloud database, it will automatically detect the certificate to use.
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub server_cert: Option<String>,
239}
240
241/// Optional. A list of client TLS/SSL certificates. If specified, mTLS authentication will be required to authenticate user connections. If set to an empty list, TLS client certificates will be removed and mTLS will not be required. TLS connection may still apply, depending on the value of 'enableTls'.
242#[derive(Debug, Clone, Serialize, Deserialize)]
243#[serde(rename_all = "camelCase")]
244pub struct DatabaseCertificateSpec {
245    /// Client certificate public key in PEM format, with new line characters replaced with '\n'.
246    pub public_certificate_pem_string: String,
247}
248
249/// `BdbVersionUpgradeStatus`
250#[derive(Debug, Clone, Serialize, Deserialize)]
251#[serde(rename_all = "camelCase")]
252pub struct BdbVersionUpgradeStatus {
253    /// Database identifier.
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub database_id: Option<i32>,
256
257    /// Target Redis version of the upgrade.
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub target_redis_version: Option<String>,
260
261    /// Upgrade progress (0.0 - 100.0).
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub progress: Option<f64>,
264
265    /// Current upgrade status (e.g. `"in-progress"`, `"completed"`, `"failed"`).
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub upgrade_status: Option<String>,
268}
269
270/// Active-Active database update local properties request message
271#[derive(Debug, Clone, Serialize, Deserialize)]
272#[serde(rename_all = "camelCase")]
273pub struct CrdbUpdatePropertiesRequest {
274    /// Subscription ID being updated. Server-populated from the path.
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub subscription_id: Option<i32>,
277
278    /// Database ID being updated. Server-populated from the path.
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub database_id: Option<i32>,
281
282    /// Optional. Updated database name. 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.
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub name: Option<String>,
285
286    /// Optional. When 'false': Creates a deployment plan and deploys it, updating any resources required by the plan. When 'true': creates a read-only deployment plan and does not update any resources. Default: 'false'
287    #[serde(skip_serializing_if = "Option::is_none")]
288    pub dry_run: Option<bool>,
289
290    /// Optional. Total memory in GB, including replication and other overhead. You cannot set both datasetSizeInGb and totalMemoryInGb.
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub memory_limit_in_gb: Option<f64>,
293
294    /// 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.
295    #[serde(skip_serializing_if = "Option::is_none")]
296    pub dataset_size_in_gb: Option<f64>,
297
298    /// Optional. Support Redis [OSS Cluster API](https://redis.io/docs/latest/operate/rc/databases/configuration/clustering/#oss-cluster-api). Default: 'false'
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub support_oss_cluster_api: Option<bool>,
301
302    /// Optional. If set to 'true', the database will use the external endpoint for OSS Cluster API. This setting blocks the database's private endpoint. Can only be set if 'supportOSSClusterAPI' is 'true'.
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
305
306    /// Optional. A public key client TLS/SSL certificate with new line characters replaced with '\n'. If specified, mTLS authentication will be required to authenticate user connections if it is not already required. If set to an empty string, TLS client certificates will be removed and mTLS will not be required. TLS connection may still apply, depending on the value of 'enableTls'.
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub client_ssl_certificate: Option<String>,
309
310    /// Optional. A list of client TLS/SSL certificates. If specified, mTLS authentication will be required to authenticate user connections. If set to an empty list, TLS client certificates will be removed and mTLS will not be required. TLS connection may still apply, depending on the value of 'enableTls'.
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub client_tls_certificates: Option<Vec<DatabaseCertificateSpec>>,
313
314    /// Optional. When 'true', requires TLS authentication for all connections - mTLS with valid clientTlsCertificates, regular TLS when clientTlsCertificates is not provided. If enableTls is set to 'false' while mTLS is required, it will remove the mTLS requirement and erase previously provided clientTlsCertificates.
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub enable_tls: Option<bool>,
317
318    /// Optional. Type and rate of data persistence in all regions that don't set local 'dataPersistence'.
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub global_data_persistence: Option<String>,
321
322    /// Optional. Changes the password used to access the database in all regions that don't set a local 'password'.
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub global_password: Option<String>,
325
326    /// Optional. List of source IP addresses or subnet masks to allow in all regions that don't set local 'sourceIp' settings. If set, Redis clients will be able to connect to this database only from within the specified source IP addresses ranges. Example: ['192.168.10.0/32', '192.168.12.0/24']
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub global_source_ip: Option<Vec<String>>,
329
330    /// Optional. Redis database alert settings in all regions that don't set local 'alerts'.
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub global_alerts: Option<Vec<DatabaseAlertSpec>>,
333
334    /// Optional. A list of regions and local settings to update.
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub regions: Option<Vec<LocalRegionProperties>>,
337
338    /// Optional. Data eviction policy.
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub data_eviction_policy: Option<String>,
341
342    /// Read-only on the response; populated by the server with the
343    /// operation type (e.g. `"UPDATE_CRDB"`).
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub command_type: Option<String>,
346}
347
348/// Database slowlog entry
349#[derive(Debug, Clone, Serialize, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct DatabaseSlowLogEntry {
352    /// Slowlog entry identifier.
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub id: Option<i32>,
355
356    /// Timestamp when the command began executing.
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub start_time: Option<String>,
359
360    /// Execution duration in microseconds.
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub duration: Option<i32>,
363
364    /// Command arguments captured for this slowlog entry.
365    #[serde(skip_serializing_if = "Option::is_none")]
366    pub arguments: Option<String>,
367}
368
369/// Database tag
370#[derive(Debug, Clone, Serialize, Deserialize)]
371#[serde(rename_all = "camelCase")]
372pub struct DatabaseTagCreateRequest {
373    /// Database tag key.
374    pub key: String,
375
376    /// Database tag value.
377    pub value: String,
378
379    /// Subscription ID being updated. Server-populated from the path.
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub subscription_id: Option<i32>,
382
383    /// Database ID being updated. Server-populated from the path.
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub database_id: Option<i32>,
386
387    /// Read-only on the response; populated by the server with the
388    /// operation type (e.g. `"CREATE_DATABASE_TAG"`).
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub command_type: Option<String>,
391}
392
393/// Optional. Throughput measurement method.
394#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct DatabaseThroughputSpec {
396    /// Throughput measurement method. Use 'operations-per-second' for all new databases.
397    pub by: String,
398
399    /// Throughput value in the selected measurement method.
400    pub value: i64,
401}
402
403/// Optional. Changes Remote backup configuration details.
404#[derive(Debug, Clone, Serialize, Deserialize)]
405#[serde(rename_all = "camelCase")]
406pub struct DatabaseBackupConfig {
407    /// Optional. Determine if backup should be active. Default: null
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub active: Option<bool>,
410
411    /// Required when active is 'true'. Defines the interval between backups. Format: 'every-x-hours', where x is one of 24, 12, 6, 4, 2, or 1. Example: "every-4-hours"
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub interval: Option<String>,
414
415    /// Alternate request field name for [`Self::interval`] that the API also
416    /// accepts (`backupInterval`). Prefer `interval`, which is the documented
417    /// form in the OpenAPI spec.
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub backup_interval: Option<String>,
420
421    /// Optional. Hour when the backup starts. Available only for "every-12-hours" and "every-24-hours" backup intervals. Specified as an hour in 24-hour UTC time. Example: "14:00" is 2 PM UTC.
422    #[serde(rename = "timeUTC", skip_serializing_if = "Option::is_none")]
423    pub time_utc: Option<String>,
424
425    /// Alternate request field name for [`Self::time_utc`] that the API also
426    /// accepts (`databaseBackupTimeUTC`). Prefer `time_utc`, which is the
427    /// documented form in the OpenAPI spec.
428    #[serde(
429        rename = "databaseBackupTimeUTC",
430        skip_serializing_if = "Option::is_none"
431    )]
432    pub database_backup_time_utc: Option<String>,
433
434    /// Required when active is 'true'. Type of storage to host backup files. Can be "aws-s3", "google-blob-storage", "azure-blob-storage", or "ftp". See [Set up backup storage locations](https://redis.io/docs/latest/operate/rc/databases/back-up-data/#set-up-backup-storage-locations) to learn how to set up backup storage locations.
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub storage_type: Option<String>,
437
438    /// Alternate request field name for [`Self::storage_type`] that the API
439    /// also accepts (`backupStorageType`). Prefer `storage_type`, which is the
440    /// documented form in the OpenAPI spec.
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub backup_storage_type: Option<String>,
443
444    /// Required when active is 'true'. Path to the backup storage location.
445    #[serde(skip_serializing_if = "Option::is_none")]
446    pub storage_path: Option<String>,
447}
448
449/// 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.
450#[derive(Debug, Clone, Serialize, Deserialize)]
451pub struct DatabaseModuleSpec {
452    /// Redis advanced capability name. Use GET /database-modules for a list of available capabilities.
453    pub name: String,
454
455    /// Optional. Redis advanced capability parameters. Use GET /database-modules to get the available capabilities and their parameters.
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub parameters: Option<HashMap<String, Value>>,
458}
459
460/// Optional. Changes Replica Of (also known as Active-Passive) configuration details.
461#[derive(Debug, Clone, Serialize, Deserialize)]
462#[serde(rename_all = "camelCase")]
463pub struct ReplicaOfSpec {
464    /// Optional. This database will be a replica of the specified Redis databases, provided as a list of objects with endpoint and certificate details.
465    pub sync_sources: Vec<DatabaseSyncSourceSpec>,
466}
467
468/// Regex rule for custom hashing policy
469#[derive(Debug, Clone, Serialize, Deserialize)]
470#[serde(rename_all = "camelCase")]
471pub struct RegexRule {
472    /// The ordinal/order of this rule
473    pub ordinal: i32,
474
475    /// The regex pattern for this rule
476    pub pattern: String,
477}
478
479/// Backup configuration status (response)
480#[derive(Debug, Clone, Serialize, Deserialize)]
481#[serde(rename_all = "camelCase")]
482pub struct Backup {
483    /// Whether remote backup is enabled
484    #[serde(skip_serializing_if = "Option::is_none")]
485    pub enable_remote_backup: Option<bool>,
486
487    /// Backup time in UTC
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub time_utc: Option<String>,
490
491    /// Backup interval (e.g., "every-24-hours", "every-12-hours")
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub interval: Option<String>,
494
495    /// Backup destination path
496    #[serde(skip_serializing_if = "Option::is_none")]
497    pub destination: Option<String>,
498}
499
500/// Security configuration (response)
501#[derive(Debug, Clone, Serialize, Deserialize)]
502#[serde(rename_all = "camelCase")]
503pub struct Security {
504    /// Whether default Redis user is enabled
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub enable_default_user: Option<bool>,
507
508    /// Whether SSL client authentication is enabled
509    #[serde(skip_serializing_if = "Option::is_none")]
510    pub ssl_client_authentication: Option<bool>,
511
512    /// Whether TLS client authentication is enabled
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub tls_client_authentication: Option<bool>,
515
516    /// List of source IP addresses allowed to connect
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub source_ips: Option<Vec<String>>,
519
520    /// Database password (masked in responses)
521    #[serde(skip_serializing_if = "Option::is_none")]
522    pub password: Option<String>,
523
524    /// Whether TLS is enabled
525    #[serde(skip_serializing_if = "Option::is_none")]
526    pub enable_tls: Option<bool>,
527}
528
529/// Clustering configuration (response)
530#[derive(Debug, Clone, Serialize, Deserialize)]
531#[serde(rename_all = "camelCase")]
532pub struct Clustering {
533    /// Number of shards
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub number_of_shards: Option<i32>,
536
537    /// Regex rules for custom hashing
538    #[serde(skip_serializing_if = "Option::is_none")]
539    pub regex_rules: Option<Vec<RegexRule>>,
540
541    /// Hashing policy
542    #[serde(skip_serializing_if = "Option::is_none")]
543    pub hashing_policy: Option<String>,
544}
545
546/// Active-Active (CRDB) database information
547///
548/// Represents an Active-Active database with global settings and per-region configurations.
549#[derive(Debug, Clone, Serialize, Deserialize)]
550#[serde(rename_all = "camelCase")]
551pub struct ActiveActiveDatabase {
552    /// Database ID
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub database_id: Option<i32>,
555
556    /// Database name
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub name: Option<String>,
559
560    /// Database protocol
561    #[serde(skip_serializing_if = "Option::is_none")]
562    pub protocol: Option<String>,
563
564    /// Database status
565    #[serde(skip_serializing_if = "Option::is_none")]
566    pub status: Option<String>,
567
568    /// Redis version
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub redis_version: Option<String>,
571
572    /// Memory storage type
573    #[serde(skip_serializing_if = "Option::is_none")]
574    pub memory_storage: Option<String>,
575
576    /// Whether this is an Active-Active database
577    #[serde(skip_serializing_if = "Option::is_none")]
578    pub active_active_redis: Option<bool>,
579
580    /// Timestamp when database was activated
581    #[serde(skip_serializing_if = "Option::is_none")]
582    pub activated_on: Option<String>,
583
584    /// Timestamp of last modification
585    #[serde(skip_serializing_if = "Option::is_none")]
586    pub last_modified: Option<String>,
587
588    /// Support for OSS Cluster API
589    #[serde(skip_serializing_if = "Option::is_none")]
590    pub support_oss_cluster_api: Option<bool>,
591
592    /// Use external endpoint for OSS Cluster API
593    #[serde(skip_serializing_if = "Option::is_none")]
594    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
595
596    /// Whether replication is enabled
597    #[serde(skip_serializing_if = "Option::is_none")]
598    pub replication: Option<bool>,
599
600    /// Data eviction policy
601    #[serde(skip_serializing_if = "Option::is_none")]
602    pub data_eviction_policy: Option<String>,
603
604    /// Security configuration
605    #[serde(skip_serializing_if = "Option::is_none")]
606    pub security: Option<Security>,
607
608    /// Redis modules enabled
609    #[serde(skip_serializing_if = "Option::is_none")]
610    pub modules: Option<Vec<DatabaseModuleSpec>>,
611
612    /// Global data persistence setting
613    #[serde(skip_serializing_if = "Option::is_none")]
614    pub global_data_persistence: Option<String>,
615
616    /// Global source IP allowlist
617    #[serde(skip_serializing_if = "Option::is_none")]
618    pub global_source_ip: Option<Vec<String>>,
619
620    /// Global password
621    #[serde(skip_serializing_if = "Option::is_none")]
622    pub global_password: Option<String>,
623
624    /// Global alert configurations
625    #[serde(skip_serializing_if = "Option::is_none")]
626    pub global_alerts: Option<Vec<DatabaseAlertSpec>>,
627
628    /// Global enable default user setting
629    #[serde(skip_serializing_if = "Option::is_none")]
630    pub global_enable_default_user: Option<bool>,
631
632    /// Per-region CRDB database configurations
633    #[serde(skip_serializing_if = "Option::is_none")]
634    pub crdb_databases: Option<Vec<CrdbDatabase>>,
635
636    /// Whether automatic minor version upgrades are enabled
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub auto_minor_version_upgrade: Option<bool>,
639}
640
641/// Per-region configuration for an Active-Active (CRDB) database
642#[derive(Debug, Clone, Serialize, Deserialize)]
643#[serde(rename_all = "camelCase")]
644pub struct CrdbDatabase {
645    /// Cloud provider
646    #[serde(skip_serializing_if = "Option::is_none")]
647    pub provider: Option<String>,
648
649    /// Cloud region
650    #[serde(skip_serializing_if = "Option::is_none")]
651    pub region: Option<String>,
652
653    /// Redis version compliance
654    #[serde(skip_serializing_if = "Option::is_none")]
655    pub redis_version_compliance: Option<String>,
656
657    /// Public endpoint
658    #[serde(skip_serializing_if = "Option::is_none")]
659    pub public_endpoint: Option<String>,
660
661    /// Private endpoint
662    #[serde(skip_serializing_if = "Option::is_none")]
663    pub private_endpoint: Option<String>,
664
665    /// Memory limit in GB
666    #[serde(skip_serializing_if = "Option::is_none")]
667    pub memory_limit_in_gb: Option<f64>,
668
669    /// Dataset size in GB
670    #[serde(skip_serializing_if = "Option::is_none")]
671    pub dataset_size_in_gb: Option<f64>,
672
673    /// Memory used in MB
674    #[serde(skip_serializing_if = "Option::is_none")]
675    pub memory_used_in_mb: Option<f64>,
676
677    /// Read operations per second
678    #[serde(skip_serializing_if = "Option::is_none")]
679    pub read_operations_per_second: Option<i32>,
680
681    /// Write operations per second
682    #[serde(skip_serializing_if = "Option::is_none")]
683    pub write_operations_per_second: Option<i32>,
684
685    /// Data persistence setting for this region
686    #[serde(skip_serializing_if = "Option::is_none")]
687    pub data_persistence: Option<String>,
688
689    /// Alert configurations for this region
690    #[serde(skip_serializing_if = "Option::is_none")]
691    pub alerts: Option<Vec<DatabaseAlertSpec>>,
692
693    /// Security configuration for this region
694    #[serde(skip_serializing_if = "Option::is_none")]
695    pub security: Option<Security>,
696
697    /// Backup configuration for this region
698    #[serde(skip_serializing_if = "Option::is_none")]
699    pub backup: Option<Backup>,
700
701    /// Query performance factor
702    #[serde(skip_serializing_if = "Option::is_none")]
703    pub query_performance_factor: Option<String>,
704}
705
706/// Database backup request message
707///
708/// # Example
709///
710/// ```
711/// use redis_cloud::flexible::databases::DatabaseBackupRequest;
712///
713/// let request = DatabaseBackupRequest::builder()
714///     .region_name("us-east-1")
715///     .build();
716/// ```
717#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
718#[serde(rename_all = "camelCase")]
719pub struct DatabaseBackupRequest {
720    /// Subscription ID being updated. Server-populated from the path.
721    #[serde(skip_serializing_if = "Option::is_none")]
722    #[builder(default, setter(strip_option))]
723    pub subscription_id: Option<i32>,
724
725    /// Database ID being updated. Server-populated from the path.
726    #[serde(skip_serializing_if = "Option::is_none")]
727    #[builder(default, setter(strip_option))]
728    pub database_id: Option<i32>,
729
730    /// Required for Active-Active databases. Name of the cloud provider region to back up. When backing up an Active-Active database, you must back up each region separately.
731    #[serde(skip_serializing_if = "Option::is_none")]
732    #[builder(default, setter(strip_option, into))]
733    pub region_name: Option<String>,
734
735    /// Optional. Manually backs up data to this location, instead of the set 'remoteBackup' location.
736    #[serde(skip_serializing_if = "Option::is_none")]
737    #[builder(default, setter(strip_option, into))]
738    pub adhoc_backup_path: Option<String>,
739
740    /// Read-only on the response; populated by the server with the
741    /// operation type (e.g. `"BACKUP_DATABASE"`).
742    #[serde(skip_serializing_if = "Option::is_none")]
743    #[builder(default, setter(strip_option, into))]
744    pub command_type: Option<String>,
745}
746
747/// Database
748///
749/// Represents a Redis Cloud database with all known API fields as first-class struct members.
750/// The `extra` field is reserved only for truly unknown/future fields that may be added to the API.
751#[derive(Debug, Clone, Serialize, Deserialize)]
752#[serde(rename_all = "camelCase")]
753pub struct Database {
754    /// Database ID - always present in API responses
755    pub database_id: i32,
756
757    /// Database name
758    #[serde(skip_serializing_if = "Option::is_none")]
759    pub name: Option<String>,
760
761    /// Database status (e.g., "active", "pending", "error", "draft")
762    #[serde(skip_serializing_if = "Option::is_none")]
763    pub status: Option<String>,
764
765    /// Cloud provider (e.g., "AWS", "GCP", "Azure")
766    #[serde(skip_serializing_if = "Option::is_none")]
767    pub provider: Option<String>,
768
769    /// Cloud region (e.g., "us-east-1", "europe-west1")
770    #[serde(skip_serializing_if = "Option::is_none")]
771    pub region: Option<String>,
772
773    /// Redis version (e.g., "7.2", "7.0")
774    #[serde(skip_serializing_if = "Option::is_none")]
775    pub redis_version: Option<String>,
776
777    /// Redis Serialization Protocol version
778    #[serde(skip_serializing_if = "Option::is_none")]
779    pub resp_version: Option<String>,
780
781    /// Total memory limit in GB (including replication and overhead)
782    #[serde(skip_serializing_if = "Option::is_none")]
783    pub memory_limit_in_gb: Option<f64>,
784
785    /// Dataset size in GB (actual data size, excluding replication)
786    #[serde(skip_serializing_if = "Option::is_none")]
787    pub dataset_size_in_gb: Option<f64>,
788
789    /// Memory used in MB
790    #[serde(skip_serializing_if = "Option::is_none")]
791    pub memory_used_in_mb: Option<f64>,
792
793    /// Private endpoint for database connections
794    #[serde(skip_serializing_if = "Option::is_none")]
795    pub private_endpoint: Option<String>,
796
797    /// Public endpoint for database connections (if enabled)
798    #[serde(skip_serializing_if = "Option::is_none")]
799    pub public_endpoint: Option<String>,
800
801    /// TCP port on which the database is available
802    #[serde(skip_serializing_if = "Option::is_none")]
803    pub port: Option<i32>,
804
805    /// Data eviction policy (e.g., "volatile-lru", "allkeys-lru", "noeviction")
806    #[serde(skip_serializing_if = "Option::is_none")]
807    pub data_eviction_policy: Option<String>,
808
809    /// Data persistence setting (e.g., "aof-every-1-sec", "snapshot-every-1-hour", "none")
810    #[serde(skip_serializing_if = "Option::is_none")]
811    pub data_persistence: Option<String>,
812
813    /// Whether replication is enabled
814    #[serde(skip_serializing_if = "Option::is_none")]
815    pub replication: Option<bool>,
816
817    /// Protocol used (e.g., "redis", "memcached")
818    #[serde(skip_serializing_if = "Option::is_none")]
819    pub protocol: Option<String>,
820
821    /// Support for OSS Cluster API
822    #[serde(skip_serializing_if = "Option::is_none")]
823    pub support_oss_cluster_api: Option<bool>,
824
825    /// Use external endpoint for OSS Cluster API
826    #[serde(skip_serializing_if = "Option::is_none")]
827    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
828
829    /// Whether TLS is enabled for connections
830    #[serde(skip_serializing_if = "Option::is_none")]
831    pub enable_tls: Option<bool>,
832
833    /// Throughput measurement configuration
834    #[serde(skip_serializing_if = "Option::is_none")]
835    pub throughput_measurement: Option<DatabaseThroughputSpec>,
836
837    /// Local throughput measurement for Active-Active databases
838    #[serde(skip_serializing_if = "Option::is_none")]
839    pub local_throughput_measurement: Option<Vec<LocalThroughput>>,
840
841    /// Average item size in bytes (for Auto Tiering)
842    #[serde(skip_serializing_if = "Option::is_none")]
843    pub average_item_size_in_bytes: Option<i64>,
844
845    /// Path to periodic backup storage location
846    #[serde(skip_serializing_if = "Option::is_none")]
847    pub periodic_backup_path: Option<String>,
848
849    /// Remote backup configuration
850    #[serde(skip_serializing_if = "Option::is_none")]
851    pub remote_backup: Option<DatabaseBackupConfig>,
852
853    /// List of source IP addresses or subnet masks allowed to connect
854    #[serde(skip_serializing_if = "Option::is_none")]
855    pub source_ip: Option<Vec<String>>,
856
857    /// Client TLS/SSL certificate (deprecated, use `client_tls_certificates`)
858    #[serde(skip_serializing_if = "Option::is_none")]
859    pub client_ssl_certificate: Option<String>,
860
861    /// List of client TLS/SSL certificates for mTLS authentication
862    #[serde(skip_serializing_if = "Option::is_none")]
863    pub client_tls_certificates: Option<Vec<DatabaseCertificateSpec>>,
864
865    /// Database password (masked in responses for security)
866    #[serde(skip_serializing_if = "Option::is_none")]
867    pub password: Option<String>,
868
869    /// Memcached SASL username
870    #[serde(skip_serializing_if = "Option::is_none")]
871    pub sasl_username: Option<String>,
872
873    /// Memcached SASL password (masked in responses)
874    #[serde(skip_serializing_if = "Option::is_none")]
875    pub sasl_password: Option<String>,
876
877    /// Database alert configurations
878    #[serde(skip_serializing_if = "Option::is_none")]
879    pub alerts: Option<Vec<DatabaseAlertSpec>>,
880
881    /// Redis modules/capabilities enabled on this database
882    #[serde(skip_serializing_if = "Option::is_none")]
883    pub modules: Option<Vec<DatabaseModuleSpec>>,
884
885    /// Database hashing policy for clustering
886    #[serde(skip_serializing_if = "Option::is_none")]
887    pub sharding_type: Option<String>,
888
889    /// Query performance factor (for search and query databases)
890    #[serde(skip_serializing_if = "Option::is_none")]
891    pub query_performance_factor: Option<String>,
892
893    /// List of databases this database is a replica of
894    #[serde(skip_serializing_if = "Option::is_none")]
895    pub replica_of: Option<Vec<String>>,
896
897    /// Replica configuration
898    #[serde(skip_serializing_if = "Option::is_none")]
899    pub replica: Option<ReplicaOfSpec>,
900
901    /// Whether default Redis user is enabled
902    #[serde(skip_serializing_if = "Option::is_none")]
903    pub enable_default_user: Option<bool>,
904
905    /// Whether this is an Active-Active (CRDB) database
906    #[serde(skip_serializing_if = "Option::is_none")]
907    pub active_active_redis: Option<bool>,
908
909    /// Memory storage type: "ram" or "ram-and-flash" (Auto Tiering)
910    #[serde(skip_serializing_if = "Option::is_none")]
911    pub memory_storage: Option<String>,
912
913    /// Redis version compliance status
914    #[serde(skip_serializing_if = "Option::is_none")]
915    pub redis_version_compliance: Option<String>,
916
917    /// Whether automatic minor version upgrades are enabled
918    #[serde(skip_serializing_if = "Option::is_none")]
919    pub auto_minor_version_upgrade: Option<bool>,
920
921    /// Number of shards in the database cluster
922    #[serde(skip_serializing_if = "Option::is_none")]
923    pub number_of_shards: Option<i32>,
924
925    /// Regex rules for custom hashing policy
926    #[serde(skip_serializing_if = "Option::is_none")]
927    pub regex_rules: Option<Vec<RegexRule>>,
928
929    /// Whether SSL client authentication is enabled
930    #[serde(skip_serializing_if = "Option::is_none")]
931    pub ssl_client_authentication: Option<bool>,
932
933    /// Whether TLS client authentication is enabled
934    #[serde(skip_serializing_if = "Option::is_none")]
935    pub tls_client_authentication: Option<bool>,
936
937    /// Timestamp when the database was activated.
938    ///
939    /// Wire field is `activatedOn` per the OpenAPI spec. The previous Rust
940    /// name (`activated`) serialized as `activated`, so real responses
941    /// silently deserialized to `None`.
942    #[serde(skip_serializing_if = "Option::is_none")]
943    pub activated_on: Option<String>,
944
945    /// Timestamp of last modification
946    #[serde(skip_serializing_if = "Option::is_none")]
947    pub last_modified: Option<String>,
948
949    /// HATEOAS links for API navigation
950    #[serde(skip_serializing_if = "Option::is_none")]
951    pub links: Option<Vec<Link>>,
952}
953
954/// Optional. Changes Redis database alert details.
955#[derive(Debug, Clone, Serialize, Deserialize)]
956pub struct DatabaseAlertSpec {
957    /// Alert type. Available options depend on Plan type. See [Configure alerts](https://redis.io/docs/latest/operate/rc/databases/monitor-performance/#configure-metric-alerts) for more information.
958    pub name: String,
959
960    /// Value over which an alert will be sent. Default values and range depend on the alert type. See [Configure alerts](https://redis.io/docs/latest/operate/rc/databases/monitor-performance/#configure-metric-alerts) for more information.
961    pub value: i32,
962}
963
964/// Request structure for creating a new Pro database
965///
966/// Contains all configuration options for creating a database in a Pro subscription,
967/// including memory settings, replication, persistence, modules, and networking.
968///
969/// # Example
970///
971/// ```
972/// use redis_cloud::flexible::databases::DatabaseCreateRequest;
973///
974/// let request = DatabaseCreateRequest::builder()
975///     .name("my-database")
976///     .memory_limit_in_gb(1.0)
977///     .replication(true)
978///     .build();
979/// ```
980#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
981#[serde(rename_all = "camelCase")]
982pub struct DatabaseCreateRequest {
983    /// Subscription ID being updated. Server-populated from the path.
984    #[serde(skip_serializing_if = "Option::is_none")]
985    #[builder(default, setter(strip_option))]
986    pub subscription_id: Option<i32>,
987
988    /// 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'
989    #[serde(skip_serializing_if = "Option::is_none")]
990    #[builder(default, setter(strip_option))]
991    pub dry_run: Option<bool>,
992
993    /// 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.
994    #[builder(setter(into))]
995    pub name: String,
996
997    /// Optional. Database protocol. Only set to 'memcached' if you have a legacy application. Default: 'redis'
998    #[serde(skip_serializing_if = "Option::is_none")]
999    #[builder(default, setter(strip_option, into))]
1000    pub protocol: Option<String>,
1001
1002    /// Optional. TCP port on which the database is available (10000-19999). Generated automatically if not set.
1003    #[serde(skip_serializing_if = "Option::is_none")]
1004    #[builder(default, setter(strip_option))]
1005    pub port: Option<i32>,
1006
1007    /// Optional. Total memory in GB, including replication and other overhead. You cannot set both datasetSizeInGb and totalMemoryInGb.
1008    #[serde(skip_serializing_if = "Option::is_none")]
1009    #[builder(default, setter(strip_option))]
1010    pub memory_limit_in_gb: Option<f64>,
1011
1012    /// 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.
1013    #[serde(skip_serializing_if = "Option::is_none")]
1014    #[builder(default, setter(strip_option))]
1015    pub dataset_size_in_gb: Option<f64>,
1016
1017    /// 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')
1018    #[serde(skip_serializing_if = "Option::is_none")]
1019    #[builder(default, setter(strip_option, into))]
1020    pub redis_version: Option<String>,
1021
1022    /// Optional. Redis Serialization Protocol version. Must be compatible with Redis version.
1023    #[serde(skip_serializing_if = "Option::is_none")]
1024    #[builder(default, setter(strip_option, into))]
1025    pub resp_version: Option<String>,
1026
1027    /// Optional. Support [OSS Cluster API](https://redis.io/docs/latest/operate/rc/databases/configuration/clustering/#oss-cluster-api). Default: 'false'
1028    #[serde(skip_serializing_if = "Option::is_none")]
1029    #[builder(default, setter(strip_option))]
1030    pub support_oss_cluster_api: Option<bool>,
1031
1032    /// Optional. If set to 'true', the database will use the external endpoint for OSS Cluster API. This setting blocks the database's private endpoint. Can only be set if 'supportOSSClusterAPI' is 'true'. Default: 'false'
1033    #[serde(skip_serializing_if = "Option::is_none")]
1034    #[builder(default, setter(strip_option))]
1035    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
1036
1037    /// Optional. Type and rate of data persistence in persistent storage. Default: 'none'
1038    #[serde(skip_serializing_if = "Option::is_none")]
1039    #[builder(default, setter(strip_option, into))]
1040    pub data_persistence: Option<String>,
1041
1042    /// Optional. Data eviction policy. Default: 'volatile-lru'
1043    #[serde(skip_serializing_if = "Option::is_none")]
1044    #[builder(default, setter(strip_option, into))]
1045    pub data_eviction_policy: Option<String>,
1046
1047    /// Optional. Sets database replication. Default: 'true'
1048    #[serde(skip_serializing_if = "Option::is_none")]
1049    #[builder(default, setter(strip_option))]
1050    pub replication: Option<bool>,
1051
1052    /// Optional. This database will be a replica of the specified Redis databases provided as one or more URI(s). Example: 'redis://user:password@host:port'. If the URI provided is a Redis Cloud database, only host and port should be provided. Example: ['<redis://endpoint1:6379>', '<redis://endpoint2:6380>'].
1053    #[serde(skip_serializing_if = "Option::is_none")]
1054    #[builder(default, setter(strip_option))]
1055    pub replica_of: Option<Vec<String>>,
1056
1057    /// Optional. Replica-of (Active-Passive) configuration. See [`ReplicaOfSpec`].
1058    #[serde(skip_serializing_if = "Option::is_none")]
1059    #[builder(default, setter(strip_option))]
1060    pub replica: Option<ReplicaOfSpec>,
1061
1062    /// Optional. Throughput measurement spec. See [`DatabaseThroughputSpec`].
1063    #[serde(skip_serializing_if = "Option::is_none")]
1064    #[builder(default, setter(strip_option))]
1065    pub throughput_measurement: Option<DatabaseThroughputSpec>,
1066
1067    /// Optional. Expected throughput per region for an Active-Active database. Default: 1000 read and write ops/sec for each region
1068    #[serde(skip_serializing_if = "Option::is_none")]
1069    #[builder(default, setter(strip_option))]
1070    pub local_throughput_measurement: Option<Vec<LocalThroughput>>,
1071
1072    /// 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
1073    #[serde(skip_serializing_if = "Option::is_none")]
1074    #[builder(default, setter(strip_option))]
1075    pub average_item_size_in_bytes: Option<i64>,
1076
1077    /// Optional. The path to a backup storage location. If specified, the database will back up every 24 hours to this location, and you can manually back up the database to this location at any time.
1078    #[serde(skip_serializing_if = "Option::is_none")]
1079    #[builder(default, setter(strip_option, into))]
1080    pub periodic_backup_path: Option<String>,
1081
1082    /// Optional. Remote backup configuration. See [`DatabaseBackupConfig`].
1083    #[serde(skip_serializing_if = "Option::is_none")]
1084    #[builder(default, setter(strip_option))]
1085    pub remote_backup: Option<DatabaseBackupConfig>,
1086
1087    /// Optional. List of source IP addresses or subnet masks to allow. If specified, Redis clients will be able to connect to this database only from within the specified source IP addresses ranges. Example: '['192.168.10.0/32', '192.168.12.0/24']'
1088    #[serde(skip_serializing_if = "Option::is_none")]
1089    #[builder(default, setter(strip_option))]
1090    pub source_ip: Option<Vec<String>>,
1091
1092    /// Optional. A public key client TLS/SSL certificate with new line characters replaced with '\n'. If specified, mTLS authentication will be required to authenticate user connections. Default: 'null'
1093    #[serde(skip_serializing_if = "Option::is_none")]
1094    #[builder(default, setter(strip_option, into))]
1095    pub client_ssl_certificate: Option<String>,
1096
1097    /// Optional. A list of client TLS/SSL certificates. If specified, mTLS authentication will be required to authenticate user connections.
1098    #[serde(skip_serializing_if = "Option::is_none")]
1099    #[builder(default, setter(strip_option))]
1100    pub client_tls_certificates: Option<Vec<DatabaseCertificateSpec>>,
1101
1102    /// Optional. When 'true', requires TLS authentication for all connections - mTLS with valid clientTlsCertificates, regular TLS when clientTlsCertificates is not provided. Default: 'false'
1103    #[serde(skip_serializing_if = "Option::is_none")]
1104    #[builder(default, setter(strip_option))]
1105    pub enable_tls: Option<bool>,
1106
1107    /// Optional. Password to access the database. If not set, a random 32-character alphanumeric password will be automatically generated. Can only be set if 'protocol' is 'redis'.
1108    #[serde(skip_serializing_if = "Option::is_none")]
1109    #[builder(default, setter(strip_option, into))]
1110    pub password: Option<String>,
1111
1112    /// Optional. Memcached (SASL) Username to access the database. If not set, the username will be set to a 'mc-' prefix followed by a random 5 character long alphanumeric. Can only be set if 'protocol' is 'memcached'.
1113    #[serde(skip_serializing_if = "Option::is_none")]
1114    #[builder(default, setter(strip_option, into))]
1115    pub sasl_username: Option<String>,
1116
1117    /// Optional. Memcached (SASL) Password to access the database. If not set, a random 32 character long alphanumeric password will be automatically generated. Can only be set if 'protocol' is 'memcached'.
1118    #[serde(skip_serializing_if = "Option::is_none")]
1119    #[builder(default, setter(strip_option, into))]
1120    pub sasl_password: Option<String>,
1121
1122    /// Optional. Redis database alert details.
1123    #[serde(skip_serializing_if = "Option::is_none")]
1124    #[builder(default, setter(strip_option))]
1125    pub alerts: Option<Vec<DatabaseAlertSpec>>,
1126
1127    /// 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.
1128    #[serde(skip_serializing_if = "Option::is_none")]
1129    #[builder(default, setter(strip_option))]
1130    pub modules: Option<Vec<DatabaseModuleSpec>>,
1131
1132    /// Optional. Database [Hashing policy](https://redis.io/docs/latest/operate/rc/databases/configuration/clustering/#manage-the-hashing-policy).
1133    #[serde(skip_serializing_if = "Option::is_none")]
1134    #[builder(default, setter(strip_option, into))]
1135    pub sharding_type: Option<String>,
1136
1137    /// Read-only on the response; populated by the server with the
1138    /// operation type (e.g. `"CREATE_DATABASE"`).
1139    #[serde(skip_serializing_if = "Option::is_none")]
1140    #[builder(default, setter(strip_option, into))]
1141    pub command_type: Option<String>,
1142
1143    /// 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.
1144    #[serde(skip_serializing_if = "Option::is_none")]
1145    #[builder(default, setter(strip_option, into))]
1146    pub query_performance_factor: Option<String>,
1147}
1148
1149/// Database import request
1150///
1151/// # Example
1152///
1153/// ```
1154/// use redis_cloud::flexible::databases::DatabaseImportRequest;
1155///
1156/// let request = DatabaseImportRequest::builder()
1157///     .source_type("aws-s3")
1158///     .import_from_uri(vec!["s3://bucket/backup.rdb".to_string()])
1159///     .build();
1160/// ```
1161#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
1162#[serde(rename_all = "camelCase")]
1163pub struct DatabaseImportRequest {
1164    /// Subscription ID being updated. Server-populated from the path.
1165    #[serde(skip_serializing_if = "Option::is_none")]
1166    #[builder(default, setter(strip_option))]
1167    pub subscription_id: Option<i32>,
1168
1169    /// Database ID being updated. Server-populated from the path.
1170    #[serde(skip_serializing_if = "Option::is_none")]
1171    #[builder(default, setter(strip_option))]
1172    pub database_id: Option<i32>,
1173
1174    /// Type of storage from which to import the database RDB file or Redis data.
1175    #[builder(setter(into))]
1176    pub source_type: String,
1177
1178    /// One or more paths to source data files or Redis databases, as appropriate to specified source type.
1179    pub import_from_uri: Vec<String>,
1180
1181    /// Read-only on the response; populated by the server with the
1182    /// operation type (e.g. `"IMPORT_DATABASE"`).
1183    #[serde(skip_serializing_if = "Option::is_none")]
1184    #[builder(default, setter(strip_option, into))]
1185    pub command_type: Option<String>,
1186}
1187
1188/// Upgrades the specified Pro database to a later Redis version.
1189#[derive(Debug, Clone, Serialize, Deserialize)]
1190#[serde(rename_all = "camelCase")]
1191pub struct DatabaseUpgradeRedisVersionRequest {
1192    /// Database ID being updated. Server-populated from the path.
1193    #[serde(skip_serializing_if = "Option::is_none")]
1194    pub database_id: Option<i32>,
1195
1196    /// Subscription ID being updated. Server-populated from the path.
1197    #[serde(skip_serializing_if = "Option::is_none")]
1198    pub subscription_id: Option<i32>,
1199
1200    /// The target Redis version the database will be upgraded to. Use GET /subscriptions/redis-versions to get a list of available Redis versions.
1201    pub target_redis_version: String,
1202
1203    /// Read-only on the response; populated by the server with the
1204    /// operation type (e.g. `"UPGRADE_DATABASE_REDIS_VERSION"`).
1205    #[serde(skip_serializing_if = "Option::is_none")]
1206    pub command_type: Option<String>,
1207}
1208
1209/// `DatabaseSlowLogEntries`
1210#[derive(Debug, Clone, Serialize, Deserialize)]
1211pub struct DatabaseSlowLogEntries {
1212    /// Slowlog entries returned for the database.
1213    #[serde(skip_serializing_if = "Option::is_none")]
1214    pub entries: Option<Vec<DatabaseSlowLogEntry>>,
1215
1216    /// HATEOAS links
1217    #[serde(skip_serializing_if = "Option::is_none")]
1218    pub links: Option<Vec<Link>>,
1219}
1220
1221/// Optional. A list of regions and local settings to update.
1222#[derive(Debug, Clone, Serialize, Deserialize)]
1223#[serde(rename_all = "camelCase")]
1224pub struct LocalRegionProperties {
1225    /// Required. Name of the region to update.
1226    #[serde(skip_serializing_if = "Option::is_none")]
1227    pub region: Option<String>,
1228
1229    /// Optional. Remote backup configuration for this region. See [`DatabaseBackupConfig`].
1230    #[serde(skip_serializing_if = "Option::is_none")]
1231    pub remote_backup: Option<DatabaseBackupConfig>,
1232
1233    /// Optional. Local throughput settings for this region. See [`LocalThroughput`].
1234    #[serde(skip_serializing_if = "Option::is_none")]
1235    pub local_throughput_measurement: Option<LocalThroughput>,
1236
1237    /// Optional. Type and rate of data persistence for this region. If set, 'globalDataPersistence' will not apply to this region.
1238    #[serde(skip_serializing_if = "Option::is_none")]
1239    pub data_persistence: Option<String>,
1240
1241    /// Optional. Changes the password used to access the database in this region. If set, 'globalPassword' will not apply to this region.
1242    #[serde(skip_serializing_if = "Option::is_none")]
1243    pub password: Option<String>,
1244
1245    /// Optional. List of source IP addresses or subnet masks to allow in this region. If set, Redis clients will be able to connect to the database in this region only from within the specified source IP addresses ranges, and 'globalSourceIp' will not apply to this region. Example: ['192.168.10.0/32', '192.168.12.0/24']
1246    #[serde(skip_serializing_if = "Option::is_none")]
1247    pub source_ip: Option<Vec<String>>,
1248
1249    /// Optional. Redis database alert settings for this region. If set, 'glboalAlerts' will not apply to this region.
1250    #[serde(skip_serializing_if = "Option::is_none")]
1251    pub alerts: Option<Vec<DatabaseAlertSpec>>,
1252
1253    /// Optional. Redis Serialization Protocol version for this region. Must be compatible with Redis version.
1254    #[serde(skip_serializing_if = "Option::is_none")]
1255    pub resp_version: Option<String>,
1256}
1257
1258/// Database update request
1259///
1260/// # Example
1261///
1262/// ```
1263/// use redis_cloud::flexible::databases::DatabaseUpdateRequest;
1264///
1265/// let request = DatabaseUpdateRequest::builder()
1266///     .name("updated-name")
1267///     .memory_limit_in_gb(2.0)
1268///     .build();
1269/// ```
1270#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
1271#[serde(rename_all = "camelCase")]
1272pub struct DatabaseUpdateRequest {
1273    /// Subscription ID being updated. Server-populated from the path.
1274    #[serde(skip_serializing_if = "Option::is_none")]
1275    #[builder(default, setter(strip_option))]
1276    pub subscription_id: Option<i32>,
1277
1278    /// Database ID being updated. Server-populated from the path.
1279    #[serde(skip_serializing_if = "Option::is_none")]
1280    #[builder(default, setter(strip_option))]
1281    pub database_id: Option<i32>,
1282
1283    /// Optional. When 'false': Creates a deployment plan and deploys it, updating any resources required by the plan. When 'true': creates a read-only deployment plan and does not update any resources. Default: 'false'
1284    #[serde(skip_serializing_if = "Option::is_none")]
1285    #[builder(default, setter(strip_option))]
1286    pub dry_run: Option<bool>,
1287
1288    /// Optional. Updated database name.
1289    #[serde(skip_serializing_if = "Option::is_none")]
1290    #[builder(default, setter(strip_option, into))]
1291    pub name: Option<String>,
1292
1293    /// Optional. Total memory in GB, including replication and other overhead. You cannot set both datasetSizeInGb and totalMemoryInGb.
1294    #[serde(skip_serializing_if = "Option::is_none")]
1295    #[builder(default, setter(strip_option))]
1296    pub memory_limit_in_gb: Option<f64>,
1297
1298    /// 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.
1299    #[serde(skip_serializing_if = "Option::is_none")]
1300    #[builder(default, setter(strip_option))]
1301    pub dataset_size_in_gb: Option<f64>,
1302
1303    /// Optional. Redis Serialization Protocol version. Must be compatible with Redis version.
1304    #[serde(skip_serializing_if = "Option::is_none")]
1305    #[builder(default, setter(strip_option, into))]
1306    pub resp_version: Option<String>,
1307
1308    /// Optional. Throughput measurement spec. See [`DatabaseThroughputSpec`].
1309    #[serde(skip_serializing_if = "Option::is_none")]
1310    #[builder(default, setter(strip_option))]
1311    pub throughput_measurement: Option<DatabaseThroughputSpec>,
1312
1313    /// Optional. Type and rate of data persistence in persistent storage.
1314    #[serde(skip_serializing_if = "Option::is_none")]
1315    #[builder(default, setter(strip_option, into))]
1316    pub data_persistence: Option<String>,
1317
1318    /// Optional. Data eviction policy.
1319    #[serde(skip_serializing_if = "Option::is_none")]
1320    #[builder(default, setter(strip_option, into))]
1321    pub data_eviction_policy: Option<String>,
1322
1323    /// Optional. Turns database replication on or off.
1324    #[serde(skip_serializing_if = "Option::is_none")]
1325    #[builder(default, setter(strip_option))]
1326    pub replication: Option<bool>,
1327
1328    /// Optional. Hashing policy Regex rules. Used only if 'shardingType' is 'custom-regex-rules'.
1329    #[serde(skip_serializing_if = "Option::is_none")]
1330    #[builder(default, setter(strip_option))]
1331    pub regex_rules: Option<Vec<String>>,
1332
1333    /// Optional. This database will be a replica of the specified Redis databases provided as one or more URI(s). Example: 'redis://user:password@host:port'. If the URI provided is a Redis Cloud database, only host and port should be provided. Example: ['<redis://endpoint1:6379>', '<redis://endpoint2:6380>'].
1334    #[serde(skip_serializing_if = "Option::is_none")]
1335    #[builder(default, setter(strip_option))]
1336    pub replica_of: Option<Vec<String>>,
1337
1338    /// Optional. Replica-of (Active-Passive) configuration. See [`ReplicaOfSpec`].
1339    #[serde(skip_serializing_if = "Option::is_none")]
1340    #[builder(default, setter(strip_option))]
1341    pub replica: Option<ReplicaOfSpec>,
1342
1343    /// Optional. Support Redis [OSS Cluster API](https://redis.io/docs/latest/operate/rc/databases/configuration/clustering/#oss-cluster-api).
1344    #[serde(skip_serializing_if = "Option::is_none")]
1345    #[builder(default, setter(strip_option))]
1346    pub support_oss_cluster_api: Option<bool>,
1347
1348    /// Optional. If set to 'true', the database will use the external endpoint for OSS Cluster API. This setting blocks the database's private endpoint. Can only be set if 'supportOSSClusterAPI' is 'true'.
1349    #[serde(skip_serializing_if = "Option::is_none")]
1350    #[builder(default, setter(strip_option))]
1351    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
1352
1353    /// Optional. Changes the password used to access the database with the 'default' user. Can only be set if 'protocol' is 'redis'.
1354    #[serde(skip_serializing_if = "Option::is_none")]
1355    #[builder(default, setter(strip_option, into))]
1356    pub password: Option<String>,
1357
1358    /// Optional. Changes the Memcached (SASL) username to access the database. Can only be set if 'protocol' is 'memcached'.
1359    #[serde(skip_serializing_if = "Option::is_none")]
1360    #[builder(default, setter(strip_option, into))]
1361    pub sasl_username: Option<String>,
1362
1363    /// Optional. Changes the Memcached (SASL) password to access the database. Can only be set if 'protocol' is 'memcached'.
1364    #[serde(skip_serializing_if = "Option::is_none")]
1365    #[builder(default, setter(strip_option, into))]
1366    pub sasl_password: Option<String>,
1367
1368    /// Optional. List of source IP addresses or subnet masks to allow. If specified, Redis clients will be able to connect to this database only from within the specified source IP addresses ranges. Example: '['192.168.10.0/32', '192.168.12.0/24']'
1369    #[serde(skip_serializing_if = "Option::is_none")]
1370    #[builder(default, setter(strip_option))]
1371    pub source_ip: Option<Vec<String>>,
1372
1373    /// Optional. A public key client TLS/SSL certificate with new line characters replaced with '\n'. If specified, mTLS authentication will be required to authenticate user connections if it is not already required. If set to an empty string, TLS client certificates will be removed and mTLS will not be required. TLS connection may still apply, depending on the value of 'enableTls'.
1374    #[serde(skip_serializing_if = "Option::is_none")]
1375    #[builder(default, setter(strip_option, into))]
1376    pub client_ssl_certificate: Option<String>,
1377
1378    /// Optional. A list of client TLS/SSL certificates. If specified, mTLS authentication will be required to authenticate user connections. If set to an empty list, TLS client certificates will be removed and mTLS will not be required. TLS connection may still apply, depending on the value of 'enableTls'.
1379    #[serde(skip_serializing_if = "Option::is_none")]
1380    #[builder(default, setter(strip_option))]
1381    pub client_tls_certificates: Option<Vec<DatabaseCertificateSpec>>,
1382
1383    /// Optional. When 'true', requires TLS authentication for all connections - mTLS with valid clientTlsCertificates, regular TLS when clientTlsCertificates is not provided. If enableTls is set to 'false' while mTLS is required, it will remove the mTLS requirement and erase previously provided clientTlsCertificates.
1384    #[serde(skip_serializing_if = "Option::is_none")]
1385    #[builder(default, setter(strip_option))]
1386    pub enable_tls: Option<bool>,
1387
1388    /// Optional. When 'true', allows connecting to the database with the 'default' user. When 'false', only defined access control users can connect to the database. Can only be set if 'protocol' is 'redis'.
1389    #[serde(skip_serializing_if = "Option::is_none")]
1390    #[builder(default, setter(strip_option))]
1391    pub enable_default_user: Option<bool>,
1392
1393    /// Optional. Changes the backup location path. If specified, the database will back up every 24 hours to this location, and you can manually back up the database to this location at any time. If set to an empty string, the backup path will be removed.
1394    #[serde(skip_serializing_if = "Option::is_none")]
1395    #[builder(default, setter(strip_option, into))]
1396    pub periodic_backup_path: Option<String>,
1397
1398    /// Optional. Remote backup configuration. See [`DatabaseBackupConfig`].
1399    #[serde(skip_serializing_if = "Option::is_none")]
1400    #[builder(default, setter(strip_option))]
1401    pub remote_backup: Option<DatabaseBackupConfig>,
1402
1403    /// Optional. Changes Redis database alert details.
1404    #[serde(skip_serializing_if = "Option::is_none")]
1405    #[builder(default, setter(strip_option))]
1406    pub alerts: Option<Vec<DatabaseAlertSpec>>,
1407
1408    /// Read-only on the response; populated by the server with the
1409    /// operation type (e.g. `"UPDATE_DATABASE"`).
1410    #[serde(skip_serializing_if = "Option::is_none")]
1411    #[builder(default, setter(strip_option, into))]
1412    pub command_type: Option<String>,
1413
1414    /// Optional. Changes the query performance factor. 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.
1415    #[serde(skip_serializing_if = "Option::is_none")]
1416    #[builder(default, setter(strip_option, into))]
1417    pub query_performance_factor: Option<String>,
1418}
1419
1420// ============================================================================
1421// Handler
1422// ============================================================================
1423
1424/// Handler for Pro database operations
1425///
1426/// Manages database lifecycle, configuration, backup/restore, import/export,
1427/// and monitoring for Redis Cloud Pro subscriptions.
1428pub struct DatabaseHandler {
1429    client: CloudClient,
1430}
1431
1432impl DatabaseHandler {
1433    /// Create a new handler
1434    #[must_use]
1435    pub fn new(client: CloudClient) -> Self {
1436        Self { client }
1437    }
1438
1439    /// Get all databases in a Pro subscription
1440    ///
1441    /// Gets a list of all databases in the specified Pro subscription.
1442    ///
1443    /// GET /subscriptions/{subscriptionId}/databases
1444    ///
1445    /// # Arguments
1446    ///
1447    /// * `subscription_id` - The subscription ID
1448    /// * `offset` - Optional offset for pagination
1449    /// * `limit` - Optional limit for pagination
1450    ///
1451    /// # Example
1452    ///
1453    /// ```no_run
1454    /// use redis_cloud::CloudClient;
1455    ///
1456    /// # async fn example() -> redis_cloud::Result<()> {
1457    /// let client = CloudClient::builder()
1458    ///     .api_key("your-api-key")
1459    ///     .api_secret("your-api-secret")
1460    ///     .build()?;
1461    ///
1462    /// // Get all databases in subscription 123
1463    /// let databases = client.databases().get_subscription_databases(123, None, None).await?;
1464    ///
1465    /// // With pagination
1466    /// let page = client.databases().get_subscription_databases(123, Some(0), Some(10)).await?;
1467    /// # Ok(())
1468    /// # }
1469    /// ```
1470    pub async fn get_subscription_databases(
1471        &self,
1472        subscription_id: i32,
1473        offset: Option<i32>,
1474        limit: Option<i32>,
1475    ) -> Result<AccountSubscriptionDatabases> {
1476        let mut query = Vec::new();
1477        if let Some(v) = offset {
1478            query.push(format!("offset={v}"));
1479        }
1480        if let Some(v) = limit {
1481            query.push(format!("limit={v}"));
1482        }
1483        let query_string = if query.is_empty() {
1484            String::new()
1485        } else {
1486            format!("?{}", query.join("&"))
1487        };
1488        self.client
1489            .get(&format!(
1490                "/subscriptions/{subscription_id}/databases{query_string}"
1491            ))
1492            .await
1493    }
1494
1495    /// Create Pro database in existing subscription
1496    /// Creates a new database in an existing Pro subscription.
1497    ///
1498    /// POST /subscriptions/{subscriptionId}/databases
1499    pub async fn create_database(
1500        &self,
1501        subscription_id: i32,
1502        request: &DatabaseCreateRequest,
1503    ) -> Result<TaskStateUpdate> {
1504        self.client
1505            .post(
1506                &format!("/subscriptions/{subscription_id}/databases"),
1507                request,
1508            )
1509            .await
1510    }
1511
1512    /// Delete Pro database
1513    /// Deletes a database from a Pro subscription.
1514    ///
1515    /// DELETE /subscriptions/{subscriptionId}/databases/{databaseId}
1516    pub async fn delete_database_by_id(
1517        &self,
1518        subscription_id: i32,
1519        database_id: i32,
1520    ) -> Result<TaskStateUpdate> {
1521        let response = self
1522            .client
1523            .delete_raw(&format!(
1524                "/subscriptions/{subscription_id}/databases/{database_id}"
1525            ))
1526            .await?;
1527        serde_json::from_value(response).map_err(Into::into)
1528    }
1529
1530    /// Get a single Pro database
1531    ///
1532    /// Gets details and settings of a single database in a Pro subscription.
1533    ///
1534    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}
1535    ///
1536    /// # Example
1537    ///
1538    /// ```no_run
1539    /// use redis_cloud::CloudClient;
1540    ///
1541    /// # async fn example() -> redis_cloud::Result<()> {
1542    /// let client = CloudClient::builder()
1543    ///     .api_key("your-api-key")
1544    ///     .api_secret("your-api-secret")
1545    ///     .build()?;
1546    ///
1547    /// let database = client.databases().get_subscription_database_by_id(123, 456).await?;
1548    ///
1549    /// println!("Database: {} (status: {:?})",
1550    ///     database.name.unwrap_or_default(),
1551    ///     database.status);
1552    /// # Ok(())
1553    /// # }
1554    /// ```
1555    pub async fn get_subscription_database_by_id(
1556        &self,
1557        subscription_id: i32,
1558        database_id: i32,
1559    ) -> Result<Database> {
1560        self.client
1561            .get(&format!(
1562                "/subscriptions/{subscription_id}/databases/{database_id}"
1563            ))
1564            .await
1565    }
1566
1567    /// Update Pro database
1568    /// Updates an existing Pro database.
1569    ///
1570    /// PUT /subscriptions/{subscriptionId}/databases/{databaseId}
1571    pub async fn update_database(
1572        &self,
1573        subscription_id: i32,
1574        database_id: i32,
1575        request: &DatabaseUpdateRequest,
1576    ) -> Result<TaskStateUpdate> {
1577        self.client
1578            .put(
1579                &format!("/subscriptions/{subscription_id}/databases/{database_id}"),
1580                request,
1581            )
1582            .await
1583    }
1584
1585    /// Get Pro database backup status
1586    /// Gets information on the latest backup attempt for this Pro database.
1587    ///
1588    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/backup
1589    pub async fn get_database_backup_status(
1590        &self,
1591        subscription_id: i32,
1592        database_id: i32,
1593        region_name: Option<String>,
1594    ) -> Result<TaskStateUpdate> {
1595        let mut query = Vec::new();
1596        if let Some(v) = region_name {
1597            query.push(format!("regionName={v}"));
1598        }
1599        let query_string = if query.is_empty() {
1600            String::new()
1601        } else {
1602            format!("?{}", query.join("&"))
1603        };
1604        self.client
1605            .get(&format!(
1606                "/subscriptions/{subscription_id}/databases/{database_id}/backup{query_string}"
1607            ))
1608            .await
1609    }
1610
1611    /// Back up Pro database
1612    /// Manually back up the specified Pro database to a backup path. By default, backups will be stored in the 'remoteBackup' location for this database.
1613    ///
1614    /// POST /subscriptions/{subscriptionId}/databases/{databaseId}/backup
1615    pub async fn backup_database(
1616        &self,
1617        subscription_id: i32,
1618        database_id: i32,
1619        request: &DatabaseBackupRequest,
1620    ) -> Result<TaskStateUpdate> {
1621        self.client
1622            .post(
1623                &format!("/subscriptions/{subscription_id}/databases/{database_id}/backup"),
1624                request,
1625            )
1626            .await
1627    }
1628
1629    /// Get Pro database TLS certificate
1630    /// Gets the X.509 PEM (base64) encoded server certificate for TLS connection to the database. Requires 'enableTLS' to be 'true' for the database.
1631    ///
1632    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/certificate
1633    pub async fn get_subscription_database_certificate(
1634        &self,
1635        subscription_id: i32,
1636        database_id: i32,
1637    ) -> Result<DatabaseCertificate> {
1638        self.client
1639            .get(&format!(
1640                "/subscriptions/{subscription_id}/databases/{database_id}/certificate"
1641            ))
1642            .await
1643    }
1644
1645    /// Flush Pro database
1646    /// Deletes all data from the specified Pro database.
1647    ///
1648    /// PUT /subscriptions/{subscriptionId}/databases/{databaseId}/flush
1649    pub async fn flush_crdb(
1650        &self,
1651        subscription_id: i32,
1652        database_id: i32,
1653        request: &CrdbFlushRequest,
1654    ) -> Result<TaskStateUpdate> {
1655        self.client
1656            .put(
1657                &format!("/subscriptions/{subscription_id}/databases/{database_id}/flush"),
1658                request,
1659            )
1660            .await
1661    }
1662
1663    /// Get Pro database import status
1664    /// Gets information on the latest import attempt for this Pro database.
1665    ///
1666    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/import
1667    pub async fn get_database_import_status(
1668        &self,
1669        subscription_id: i32,
1670        database_id: i32,
1671    ) -> Result<TaskStateUpdate> {
1672        self.client
1673            .get(&format!(
1674                "/subscriptions/{subscription_id}/databases/{database_id}/import"
1675            ))
1676            .await
1677    }
1678
1679    /// Import data to a Pro database
1680    /// Imports data from an RDB file or from a different Redis database into this Pro database. WARNING: Importing data into a database removes all existing data from the database.
1681    ///
1682    /// POST /subscriptions/{subscriptionId}/databases/{databaseId}/import
1683    pub async fn import_database(
1684        &self,
1685        subscription_id: i32,
1686        database_id: i32,
1687        request: &DatabaseImportRequest,
1688    ) -> Result<TaskStateUpdate> {
1689        self.client
1690            .post(
1691                &format!("/subscriptions/{subscription_id}/databases/{database_id}/import"),
1692                request,
1693            )
1694            .await
1695    }
1696
1697    /// Update Active-Active database
1698    /// (Active-Active databases only) Updates database properties for an Active-Active database.
1699    ///
1700    /// PUT /subscriptions/{subscriptionId}/databases/{databaseId}/regions
1701    pub async fn update_crdb_local_properties(
1702        &self,
1703        subscription_id: i32,
1704        database_id: i32,
1705        request: &CrdbUpdatePropertiesRequest,
1706    ) -> Result<TaskStateUpdate> {
1707        self.client
1708            .put(
1709                &format!("/subscriptions/{subscription_id}/databases/{database_id}/regions"),
1710                request,
1711            )
1712            .await
1713    }
1714
1715    /// Get database slowlog
1716    /// Gets the slowlog for a specific database.
1717    ///
1718    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/slow-log
1719    pub async fn get_slow_log(
1720        &self,
1721        subscription_id: i32,
1722        database_id: i32,
1723        region_name: Option<String>,
1724    ) -> Result<DatabaseSlowLogEntries> {
1725        let mut query = Vec::new();
1726        if let Some(v) = region_name {
1727            query.push(format!("regionName={v}"));
1728        }
1729        let query_string = if query.is_empty() {
1730            String::new()
1731        } else {
1732            format!("?{}", query.join("&"))
1733        };
1734        self.client
1735            .get(&format!(
1736                "/subscriptions/{subscription_id}/databases/{database_id}/slow-log{query_string}"
1737            ))
1738            .await
1739    }
1740
1741    /// Get database tags
1742    /// Gets a list of all database tags.
1743    ///
1744    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/tags
1745    pub async fn get_tags(&self, subscription_id: i32, database_id: i32) -> Result<CloudTags> {
1746        self.client
1747            .get(&format!(
1748                "/subscriptions/{subscription_id}/databases/{database_id}/tags"
1749            ))
1750            .await
1751    }
1752
1753    /// Add a database tag
1754    /// Adds a single database tag to a database.
1755    ///
1756    /// POST /subscriptions/{subscriptionId}/databases/{databaseId}/tags
1757    pub async fn create_tag(
1758        &self,
1759        subscription_id: i32,
1760        database_id: i32,
1761        request: &DatabaseTagCreateRequest,
1762    ) -> Result<CloudTag> {
1763        self.client
1764            .post(
1765                &format!("/subscriptions/{subscription_id}/databases/{database_id}/tags"),
1766                request,
1767            )
1768            .await
1769    }
1770
1771    /// Overwrite database tags
1772    /// Overwrites all tags on the database.
1773    ///
1774    /// PUT /subscriptions/{subscriptionId}/databases/{databaseId}/tags
1775    pub async fn update_tags(
1776        &self,
1777        subscription_id: i32,
1778        database_id: i32,
1779        request: &DatabaseTagsUpdateRequest,
1780    ) -> Result<CloudTags> {
1781        self.client
1782            .put(
1783                &format!("/subscriptions/{subscription_id}/databases/{database_id}/tags"),
1784                request,
1785            )
1786            .await
1787    }
1788
1789    /// Delete database tag
1790    /// Removes the specified tag from the database.
1791    ///
1792    /// DELETE /subscriptions/{subscriptionId}/databases/{databaseId}/tags/{tagKey}
1793    pub async fn delete_tag(
1794        &self,
1795        subscription_id: i32,
1796        database_id: i32,
1797        tag_key: String,
1798    ) -> Result<HashMap<String, Value>> {
1799        let response = self
1800            .client
1801            .delete_raw(&format!(
1802                "/subscriptions/{subscription_id}/databases/{database_id}/tags/{tag_key}"
1803            ))
1804            .await?;
1805        serde_json::from_value(response).map_err(Into::into)
1806    }
1807
1808    /// Update database tag value
1809    /// Updates the value of the specified database tag.
1810    ///
1811    /// PUT /subscriptions/{subscriptionId}/databases/{databaseId}/tags/{tagKey}
1812    pub async fn update_tag(
1813        &self,
1814        subscription_id: i32,
1815        database_id: i32,
1816        tag_key: String,
1817        request: &DatabaseTagUpdateRequest,
1818    ) -> Result<CloudTag> {
1819        self.client
1820            .put(
1821                &format!("/subscriptions/{subscription_id}/databases/{database_id}/tags/{tag_key}"),
1822                request,
1823            )
1824            .await
1825    }
1826
1827    /// Get Pro database version upgrade status
1828    /// Gets information on the latest upgrade attempt for this Pro database.
1829    ///
1830    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/upgrade
1831    pub async fn get_database_redis_version_upgrade_status(
1832        &self,
1833        subscription_id: i32,
1834        database_id: i32,
1835    ) -> Result<BdbVersionUpgradeStatus> {
1836        self.client
1837            .get(&format!(
1838                "/subscriptions/{subscription_id}/databases/{database_id}/upgrade"
1839            ))
1840            .await
1841    }
1842
1843    /// Upgrade Pro database version
1844    ///
1845    /// POST /subscriptions/{subscriptionId}/databases/{databaseId}/upgrade
1846    pub async fn upgrade_database_redis_version(
1847        &self,
1848        subscription_id: i32,
1849        database_id: i32,
1850        request: &DatabaseUpgradeRedisVersionRequest,
1851    ) -> Result<TaskStateUpdate> {
1852        self.client
1853            .post(
1854                &format!("/subscriptions/{subscription_id}/databases/{database_id}/upgrade"),
1855                request,
1856            )
1857            .await
1858    }
1859
1860    /// Get available target Redis versions for upgrade
1861    /// Gets a list of Redis versions that the database can be upgraded to.
1862    ///
1863    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/available-target-versions
1864    pub async fn get_available_target_versions(
1865        &self,
1866        subscription_id: i32,
1867        database_id: i32,
1868    ) -> Result<Value> {
1869        self.client
1870            .get_raw(&format!(
1871                "/subscriptions/{subscription_id}/databases/{database_id}/available-target-versions"
1872            ))
1873            .await
1874    }
1875
1876    /// Flush Pro database (standard, non-Active-Active)
1877    /// Deletes all data from the specified Pro database.
1878    ///
1879    /// PUT /subscriptions/{subscriptionId}/databases/{databaseId}/flush
1880    pub async fn flush_database(
1881        &self,
1882        subscription_id: i32,
1883        database_id: i32,
1884    ) -> Result<TaskStateUpdate> {
1885        // Empty body for standard flush
1886        self.client
1887            .put_raw(
1888                &format!("/subscriptions/{subscription_id}/databases/{database_id}/flush"),
1889                serde_json::json!({}),
1890            )
1891            .await
1892            .and_then(|v| serde_json::from_value(v).map_err(Into::into))
1893    }
1894
1895    // ========================================================================
1896    // Pagination Helpers
1897    // ========================================================================
1898
1899    /// Stream all databases in a Pro subscription
1900    ///
1901    /// Returns an async stream that automatically handles pagination, yielding
1902    /// individual [`Database`] items. This is useful when you need to process
1903    /// a large number of databases without loading them all into memory at once.
1904    ///
1905    /// # Arguments
1906    ///
1907    /// * `subscription_id` - The subscription ID
1908    ///
1909    /// # Example
1910    ///
1911    /// ```no_run
1912    /// use redis_cloud::CloudClient;
1913    /// use futures::StreamExt;
1914    /// use std::pin::pin;
1915    ///
1916    /// # async fn example() -> redis_cloud::Result<()> {
1917    /// let client = CloudClient::builder()
1918    ///     .api_key("your-api-key")
1919    ///     .api_secret("your-api-secret")
1920    ///     .build()?;
1921    ///
1922    /// let handler = client.databases();
1923    /// let mut stream = pin!(handler.stream_databases(123));
1924    /// while let Some(result) = stream.next().await {
1925    ///     let database = result?;
1926    ///     println!("Database: {} (ID: {})", database.name.unwrap_or_default(), database.database_id);
1927    /// }
1928    /// # Ok(())
1929    /// # }
1930    /// ```
1931    pub fn stream_databases(
1932        &self,
1933        subscription_id: i32,
1934    ) -> impl Stream<Item = Result<Database>> + '_ {
1935        self.stream_databases_with_page_size(subscription_id, 100)
1936    }
1937
1938    /// Stream all databases with custom page size
1939    ///
1940    /// Like [`stream_databases`](Self::stream_databases), but allows specifying
1941    /// the page size for API requests.
1942    ///
1943    /// # Arguments
1944    ///
1945    /// * `subscription_id` - The subscription ID
1946    /// * `page_size` - Number of databases to fetch per API request
1947    pub fn stream_databases_with_page_size(
1948        &self,
1949        subscription_id: i32,
1950        page_size: i32,
1951    ) -> impl Stream<Item = Result<Database>> + '_ {
1952        try_stream! {
1953            let mut offset = 0;
1954
1955            loop {
1956                let response = self
1957                    .get_subscription_databases(subscription_id, Some(offset), Some(page_size))
1958                    .await?;
1959
1960                // Extract databases from the response
1961                let databases = Self::extract_databases_from_response(&response);
1962
1963                if databases.is_empty() {
1964                    break;
1965                }
1966
1967                let count = databases.len();
1968                for db in databases {
1969                    yield db;
1970                }
1971
1972                // If we got fewer than page_size, we've reached the end
1973                #[allow(clippy::cast_sign_loss)]
1974                if count < page_size as usize {
1975                    break;
1976                }
1977
1978                offset += page_size;
1979            }
1980        }
1981    }
1982
1983    /// Get all databases in a subscription (collected)
1984    ///
1985    /// Fetches all databases by automatically handling pagination and returns
1986    /// them as a single vector. Use [`stream_databases`](Self::stream_databases)
1987    /// if you prefer to process databases one at a time.
1988    ///
1989    /// # Arguments
1990    ///
1991    /// * `subscription_id` - The subscription ID
1992    ///
1993    /// # Example
1994    ///
1995    /// ```no_run
1996    /// use redis_cloud::CloudClient;
1997    ///
1998    /// # async fn example() -> redis_cloud::Result<()> {
1999    /// let client = CloudClient::builder()
2000    ///     .api_key("your-api-key")
2001    ///     .api_secret("your-api-secret")
2002    ///     .build()?;
2003    ///
2004    /// let all_databases = client.databases().get_all_databases(123).await?;
2005    /// println!("Total databases: {}", all_databases.len());
2006    /// # Ok(())
2007    /// # }
2008    /// ```
2009    pub async fn get_all_databases(&self, subscription_id: i32) -> Result<Vec<Database>> {
2010        let mut databases = Vec::new();
2011        let mut offset = 0;
2012        let page_size = 100;
2013
2014        loop {
2015            let response = self
2016                .get_subscription_databases(subscription_id, Some(offset), Some(page_size))
2017                .await?;
2018
2019            let page = Self::extract_databases_from_response(&response);
2020            let count = page.len();
2021            databases.extend(page);
2022
2023            #[allow(clippy::cast_sign_loss)]
2024            if count < page_size as usize {
2025                break;
2026            }
2027            offset += page_size;
2028        }
2029
2030        Ok(databases)
2031    }
2032
2033    /// Extract databases from an `AccountSubscriptionDatabases` response
2034    fn extract_databases_from_response(response: &AccountSubscriptionDatabases) -> Vec<Database> {
2035        response
2036            .subscription
2037            .first()
2038            .map(|sub| sub.databases.clone())
2039            .unwrap_or_default()
2040    }
2041
2042    // ========================================================================
2043    // Simplified API Methods
2044    // ========================================================================
2045
2046    /// List databases in a subscription (simplified)
2047    ///
2048    /// Returns a flat list of databases, unwrapping the nested API response.
2049    /// This is a convenience method that wraps [`get_subscription_databases`](Self::get_subscription_databases).
2050    ///
2051    /// # Arguments
2052    ///
2053    /// * `subscription_id` - The subscription ID
2054    ///
2055    /// # Example
2056    ///
2057    /// ```no_run
2058    /// use redis_cloud::CloudClient;
2059    ///
2060    /// # async fn example() -> redis_cloud::Result<()> {
2061    /// let client = CloudClient::builder()
2062    ///     .api_key("your-api-key")
2063    ///     .api_secret("your-api-secret")
2064    ///     .build()?;
2065    ///
2066    /// let databases = client.databases().list(123).await?;
2067    /// for db in databases {
2068    ///     println!("Database: {} (ID: {})", db.name.unwrap_or_default(), db.database_id);
2069    /// }
2070    /// # Ok(())
2071    /// # }
2072    /// ```
2073    pub async fn list(&self, subscription_id: i32) -> Result<Vec<Database>> {
2074        let response = self
2075            .get_subscription_databases(subscription_id, None, None)
2076            .await?;
2077        Ok(Self::extract_databases_from_response(&response))
2078    }
2079
2080    /// Get a database by ID (simplified)
2081    ///
2082    /// Alias for [`get_subscription_database_by_id`](Self::get_subscription_database_by_id).
2083    ///
2084    /// # Arguments
2085    ///
2086    /// * `subscription_id` - The subscription ID
2087    /// * `database_id` - The database ID
2088    ///
2089    /// # Example
2090    ///
2091    /// ```no_run
2092    /// use redis_cloud::CloudClient;
2093    ///
2094    /// # async fn example() -> redis_cloud::Result<()> {
2095    /// let client = CloudClient::builder()
2096    ///     .api_key("your-api-key")
2097    ///     .api_secret("your-api-secret")
2098    ///     .build()?;
2099    ///
2100    /// let database = client.databases().get(123, 456).await?;
2101    /// println!("Database: {} (status: {:?})", database.name.unwrap_or_default(), database.status);
2102    /// # Ok(())
2103    /// # }
2104    /// ```
2105    pub async fn get(&self, subscription_id: i32, database_id: i32) -> Result<Database> {
2106        self.get_subscription_database_by_id(subscription_id, database_id)
2107            .await
2108    }
2109
2110    /// Create a database (simplified)
2111    ///
2112    /// Alias for [`create_database`](Self::create_database).
2113    ///
2114    /// # Arguments
2115    ///
2116    /// * `subscription_id` - The subscription ID
2117    /// * `request` - The database creation request
2118    ///
2119    /// # Example
2120    ///
2121    /// ```no_run
2122    /// use redis_cloud::CloudClient;
2123    /// use redis_cloud::flexible::databases::DatabaseCreateRequest;
2124    ///
2125    /// # async fn example() -> redis_cloud::Result<()> {
2126    /// let client = CloudClient::builder()
2127    ///     .api_key("your-api-key")
2128    ///     .api_secret("your-api-secret")
2129    ///     .build()?;
2130    ///
2131    /// let request = DatabaseCreateRequest::builder()
2132    ///     .name("my-database")
2133    ///     .memory_limit_in_gb(1.0)
2134    ///     .build();
2135    ///
2136    /// let task = client.databases().create(123, &request).await?;
2137    /// println!("Task ID: {:?}", task.task_id);
2138    /// # Ok(())
2139    /// # }
2140    /// ```
2141    pub async fn create(
2142        &self,
2143        subscription_id: i32,
2144        request: &DatabaseCreateRequest,
2145    ) -> Result<TaskStateUpdate> {
2146        self.create_database(subscription_id, request).await
2147    }
2148
2149    /// Update a database (simplified)
2150    ///
2151    /// Alias for [`update_database`](Self::update_database).
2152    ///
2153    /// # Arguments
2154    ///
2155    /// * `subscription_id` - The subscription ID
2156    /// * `database_id` - The database ID
2157    /// * `request` - The database update request
2158    ///
2159    /// # Example
2160    ///
2161    /// ```no_run
2162    /// use redis_cloud::CloudClient;
2163    /// use redis_cloud::flexible::databases::DatabaseUpdateRequest;
2164    ///
2165    /// # async fn example() -> redis_cloud::Result<()> {
2166    /// let client = CloudClient::builder()
2167    ///     .api_key("your-api-key")
2168    ///     .api_secret("your-api-secret")
2169    ///     .build()?;
2170    ///
2171    /// let request = DatabaseUpdateRequest::builder()
2172    ///     .name("updated-name")
2173    ///     .build();
2174    ///
2175    /// let task = client.databases().update(123, 456, &request).await?;
2176    /// println!("Task ID: {:?}", task.task_id);
2177    /// # Ok(())
2178    /// # }
2179    /// ```
2180    pub async fn update(
2181        &self,
2182        subscription_id: i32,
2183        database_id: i32,
2184        request: &DatabaseUpdateRequest,
2185    ) -> Result<TaskStateUpdate> {
2186        self.update_database(subscription_id, database_id, request)
2187            .await
2188    }
2189
2190    /// Delete a database (simplified)
2191    ///
2192    /// Alias for [`delete_database_by_id`](Self::delete_database_by_id).
2193    ///
2194    /// # Arguments
2195    ///
2196    /// * `subscription_id` - The subscription ID
2197    /// * `database_id` - The database ID
2198    ///
2199    /// # Example
2200    ///
2201    /// ```no_run
2202    /// use redis_cloud::CloudClient;
2203    ///
2204    /// # async fn example() -> redis_cloud::Result<()> {
2205    /// let client = CloudClient::builder()
2206    ///     .api_key("your-api-key")
2207    ///     .api_secret("your-api-secret")
2208    ///     .build()?;
2209    ///
2210    /// let task = client.databases().delete(123, 456).await?;
2211    /// println!("Task ID: {:?}", task.task_id);
2212    /// # Ok(())
2213    /// # }
2214    /// ```
2215    pub async fn delete(&self, subscription_id: i32, database_id: i32) -> Result<TaskStateUpdate> {
2216        self.delete_database_by_id(subscription_id, database_id)
2217            .await
2218    }
2219
2220    /// Get Pro database traffic state
2221    /// Gets the current traffic state for this Pro database, including whether
2222    /// traffic is stopped and whether it can be resumed.
2223    ///
2224    /// GET /subscriptions/{subscriptionId}/databases/{databaseId}/traffic
2225    pub async fn get_traffic(
2226        &self,
2227        subscription_id: i32,
2228        database_id: i32,
2229    ) -> Result<DatabaseTrafficStateResponse> {
2230        self.client
2231            .get(&format!(
2232                "/subscriptions/{subscription_id}/databases/{database_id}/traffic"
2233            ))
2234            .await
2235    }
2236
2237    /// Resume Pro database traffic
2238    /// Resumes traffic to this Pro database after it has been stopped.
2239    ///
2240    /// POST /subscriptions/{subscriptionId}/databases/{databaseId}/traffic/resume
2241    pub async fn resume_traffic(&self, subscription_id: i32, database_id: i32) -> Result<()> {
2242        self.client
2243            .post(
2244                &format!("/subscriptions/{subscription_id}/databases/{database_id}/traffic/resume"),
2245                &serde_json::json!({}),
2246            )
2247            .await
2248    }
2249}