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