Skip to main content

redis_cloud/fixed/
databases.rs

1//! Database management operations for Essentials (Fixed) subscriptions
2//!
3//! This module provides database management functionality for Redis Cloud Essentials
4//! (formerly Fixed) subscriptions, which offer a simplified, cost-effective option
5//! for smaller workloads with predictable capacity requirements.
6//!
7//! # Overview
8//!
9//! Essentials databases are pre-configured Redis instances with fixed memory allocations
10//! and simplified pricing. They're ideal for development, testing, and production
11//! workloads that don't require auto-scaling or advanced clustering features.
12//!
13//! # Key Features
14//!
15//! - **Fixed Capacity**: Pre-defined memory sizes from 250MB to 12GB
16//! - **Simple Pricing**: Predictable monthly costs
17//! - **Essential Features**: Replication, persistence, and backup support
18//! - **Module Support**: Limited module availability based on plan
19//! - **Quick Setup**: Simplified configuration for faster deployment
20//!
21//! # Differences from Pro Databases
22//!
23//! - Fixed memory allocations (no auto-scaling)
24//! - Limited to single-region deployments
25//! - Simplified module selection
26//! - No clustering support
27//! - Predictable pricing model
28//!
29//! # Example Usage
30//!
31//! ```no_run
32//! use redis_cloud::{CloudClient, FixedDatabaseHandler};
33//!
34//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
35//! let client = CloudClient::builder()
36//!     .api_key("your-api-key")
37//!     .api_secret("your-api-secret")
38//!     .build()?;
39//!
40//! let handler = FixedDatabaseHandler::new(client);
41//!
42//! // Example: List databases in a fixed subscription (ID 123)
43//! let databases = handler.list(123, None, None).await?;
44//! # Ok(())
45//! # }
46//! ```
47
48use crate::types::Link;
49pub use crate::types::{CloudTag, CloudTags, DatabaseTrafficStateResponse, Tag, TaskStateUpdate};
50use crate::{CloudClient, Result};
51use serde::{Deserialize, Serialize};
52use serde_json::Value;
53use std::collections::HashMap;
54use typed_builder::TypedBuilder;
55
56// ============================================================================
57// Models
58// ============================================================================
59
60/// `RedisLabs` Account Subscription Databases information
61///
62/// Response from `GET /fixed/subscriptions/{subscriptionId}/databases`.
63///
64/// Note: the OpenAPI schema lists only `accountId` and `links`, but real responses
65/// (and the spec's own example) include a `subscription` object containing the
66/// databases. See [`FixedSubscriptionDatabasesInfo`] for the inner shape.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct AccountFixedSubscriptionDatabases {
70    /// Account ID
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub account_id: Option<i32>,
73
74    /// Subscription information with the nested databases array.
75    ///
76    /// The Essentials response wraps databases under a single `subscription` object
77    /// (unlike the Pro response, which uses an array — see
78    /// [`crate::flexible::databases::AccountSubscriptionDatabases`]).
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub subscription: Option<FixedSubscriptionDatabasesInfo>,
81
82    /// HATEOAS links
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub links: Option<Vec<Link>>,
85}
86
87/// Subscription databases info returned within [`AccountFixedSubscriptionDatabases`].
88#[derive(Debug, Clone, Serialize, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct FixedSubscriptionDatabasesInfo {
91    /// Subscription ID
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub subscription_id: Option<i32>,
94
95    /// Number of databases reported by the API for this subscription
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<FixedDatabase>,
102
103    /// HATEOAS links
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub links: Option<Vec<Link>>,
106}
107
108/// Database import request
109#[derive(Debug, Clone, Serialize, Deserialize)]
110#[serde(rename_all = "camelCase")]
111pub struct FixedDatabaseImportRequest {
112    /// Subscription ID being updated. Server-populated from the path.
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub subscription_id: Option<i32>,
115
116    /// Database ID being updated. Server-populated from the path.
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub database_id: Option<i32>,
119
120    /// Type of storage from which to import the database RDB file or Redis data.
121    pub source_type: String,
122
123    /// One or more paths to source data files or Redis databases, as appropriate to specified source type.
124    pub import_from_uri: Vec<String>,
125
126    /// Read-only on the response; populated by the server with the
127    /// operation type (e.g. `"IMPORT_DATABASE"`).
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub command_type: Option<String>,
130}
131
132/// Database tag update request message
133#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(rename_all = "camelCase")]
135pub struct DatabaseTagUpdateRequest {
136    /// Subscription ID being updated. Server-populated from the path.
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub subscription_id: Option<i32>,
139
140    /// Database ID being updated. Server-populated from the path.
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub database_id: Option<i32>,
143
144    /// Tag key being updated. Server-populated from the path.
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub key: Option<String>,
147
148    /// Database tag value
149    pub value: String,
150
151    /// Read-only on the response; populated by the server with the
152    /// operation type (e.g. `"UPDATE_DATABASE_TAG"`).
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub command_type: Option<String>,
155}
156
157/// `DynamicEndpoints`
158#[derive(Debug, Clone, Serialize, Deserialize)]
159pub struct DynamicEndpoints {
160    /// Public endpoint for the database (if available).
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub public: Option<String>,
163
164    /// Private endpoint for the database (if available).
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub private: Option<String>,
167}
168
169/// Database tags update request message
170#[derive(Debug, Clone, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct DatabaseTagsUpdateRequest {
173    /// Subscription ID being updated. Server-populated from the path.
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub subscription_id: Option<i32>,
176
177    /// Database ID being updated. Server-populated from the path.
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub database_id: Option<i32>,
180
181    /// List of database tags.
182    pub tags: Vec<Tag>,
183
184    /// Read-only on the response; populated by the server with the
185    /// operation type (e.g. `"UPDATE_DATABASE_TAGS"`).
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub command_type: Option<String>,
188}
189
190/// Optional. This database will be a replica of the specified Redis databases, provided as a list of objects with endpoint and certificate details.
191#[derive(Debug, Clone, Serialize, Deserialize)]
192#[serde(rename_all = "camelCase")]
193pub struct DatabaseSyncSourceSpec {
194    /// 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>'.
195    pub endpoint: String,
196
197    /// 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.
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub encryption: Option<bool>,
200
201    /// 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.
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub server_cert: Option<String>,
204}
205
206/// 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'.
207#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(rename_all = "camelCase")]
209pub struct DatabaseCertificateSpec {
210    /// Client certificate public key in PEM format, with new line characters replaced with '\n'.
211    pub public_certificate_pem_string: String,
212}
213
214/// Database slowlog entry
215#[derive(Debug, Clone, Serialize, Deserialize)]
216#[serde(rename_all = "camelCase")]
217pub struct DatabaseSlowLogEntry {
218    /// Slowlog entry identifier.
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub id: Option<i32>,
221
222    /// Timestamp when the command began executing.
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub start_time: Option<String>,
225
226    /// Execution duration in microseconds.
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub duration: Option<i32>,
229
230    /// Command arguments captured for this slowlog entry.
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub arguments: Option<String>,
233}
234
235/// Database tag
236#[derive(Debug, Clone, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct DatabaseTagCreateRequest {
239    /// Database tag key.
240    pub key: String,
241
242    /// Database tag value.
243    pub value: String,
244
245    /// Subscription ID being updated. Server-populated from the path.
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub subscription_id: Option<i32>,
248
249    /// Database ID being updated. Server-populated from the path.
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub database_id: Option<i32>,
252
253    /// Read-only on the response; populated by the server with the
254    /// operation type (e.g. `"CREATE_DATABASE_TAG"`).
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub command_type: Option<String>,
257}
258
259/// Essentials database backup request message
260#[derive(Debug, Clone, Serialize, Deserialize)]
261#[serde(rename_all = "camelCase")]
262pub struct FixedDatabaseBackupRequest {
263    /// Subscription ID being updated. Server-populated from the path.
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub subscription_id: Option<i32>,
266
267    /// Database ID being updated. Server-populated from the path.
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub database_id: Option<i32>,
270
271    /// Optional. Manually backs up data to this location, instead of the set 'periodicBackupPath' location.
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub adhoc_backup_path: Option<String>,
274
275    /// Read-only on the response; populated by the server with the
276    /// operation type (e.g. `"BACKUP_DATABASE"`).
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub command_type: Option<String>,
279}
280
281/// 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.
282#[derive(Debug, Clone, Serialize, Deserialize)]
283#[serde(rename_all = "camelCase")]
284pub struct DatabaseModuleSpec {
285    /// Redis advanced capability name. Use GET /database-modules for a list of available capabilities.
286    pub name: String,
287
288    /// Optional. Redis advanced capability parameters. Use GET /database-modules to get the available capabilities and their parameters.
289    ///
290    /// Kept as a [`Value`] because the wire shape is asymmetric: create
291    /// requests send an object (capability name → parameter map), while
292    /// database reads return an array. A typed map only matched the request
293    /// side and failed to deserialize real responses.
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub parameters: Option<Value>,
296
297    /// Module id (response only).
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub id: Option<i32>,
300
301    /// Human-readable capability name, e.g. `"Search and query"` (response only).
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub capability_name: Option<String>,
304
305    /// Module version (response only).
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub version: Option<String>,
308
309    /// Module description (response only).
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub description: Option<String>,
312}
313
314/// Optional. Changes Replica Of (also known as Active-Passive) configuration details.
315#[derive(Debug, Clone, Serialize, Deserialize)]
316#[serde(rename_all = "camelCase")]
317pub struct ReplicaOfSpec {
318    /// Description of the replica configuration
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub description: Option<String>,
321
322    /// Optional. This database will be a replica of the specified Redis databases, provided as a list of objects with endpoint and certificate details.
323    #[serde(default)]
324    pub sync_sources: Vec<DatabaseSyncSourceSpec>,
325}
326
327/// Database backup status and configuration
328#[derive(Debug, Clone, Serialize, Deserialize)]
329#[serde(rename_all = "camelCase")]
330pub struct DatabaseBackupStatus {
331    /// Whether backup is enabled
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub enabled: Option<bool>,
334
335    /// Current backup status
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub status: Option<String>,
338
339    /// Backup interval (e.g., "every-24-hours")
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub interval: Option<String>,
342
343    /// Backup destination path
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub destination: Option<String>,
346}
347
348/// Optional. Changes Redis database alert details.
349#[derive(Debug, Clone, Serialize, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct DatabaseAlertSpec {
352    /// 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.
353    pub name: String,
354
355    /// 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.
356    pub value: i32,
357
358    /// Alert id (response only). A composite string such as `"<dbId>-<n>"`.
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub id: Option<String>,
361
362    /// Default threshold value for this alert type (response only).
363    #[serde(skip_serializing_if = "Option::is_none")]
364    pub default_value: Option<i32>,
365}
366
367/// Security configuration returned within a [`FixedDatabase`] read response
368/// (nested `security` object).
369#[derive(Debug, Clone, Serialize, Deserialize)]
370#[serde(rename_all = "camelCase")]
371pub struct FixedDatabaseSecurity {
372    /// Whether the default Redis user is enabled.
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub default_user_enabled: Option<bool>,
375
376    /// Whether SSL client authentication is enabled.
377    #[serde(skip_serializing_if = "Option::is_none")]
378    pub ssl_client_authentication: Option<bool>,
379
380    /// Whether TLS client authentication is enabled.
381    #[serde(skip_serializing_if = "Option::is_none")]
382    pub tls_client_authentication: Option<bool>,
383
384    /// Whether TLS is enabled for connections.
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub enable_tls: Option<bool>,
387
388    /// Source IP addresses / CIDR blocks allowed to connect.
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub source_ips: Option<Vec<String>>,
391
392    /// Database password (masked in responses).
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub password: Option<String>,
395}
396
397/// A single regex rule within a fixed database's clustering policy.
398#[derive(Debug, Clone, Serialize, Deserialize)]
399#[serde(rename_all = "camelCase")]
400pub struct FixedDatabaseRegexRule {
401    /// Order of this rule.
402    pub ordinal: i32,
403
404    /// Regex pattern.
405    pub pattern: String,
406}
407
408/// Clustering configuration returned within a [`FixedDatabase`] read response
409/// (nested `clustering` object).
410#[derive(Debug, Clone, Serialize, Deserialize)]
411#[serde(rename_all = "camelCase")]
412pub struct FixedDatabaseClustering {
413    /// Whether clustering is enabled.
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub enabled: Option<bool>,
416
417    /// Regex rules for the custom hashing policy.
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub regex_rules: Option<Vec<FixedDatabaseRegexRule>>,
420
421    /// Hashing policy (e.g. `"standard"`).
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub hashing_policy: Option<String>,
424}
425
426/// Backup configuration returned within a [`FixedDatabase`] read response
427/// (nested `backup` object).
428#[derive(Debug, Clone, Serialize, Deserialize)]
429#[serde(rename_all = "camelCase")]
430pub struct FixedDatabaseBackup {
431    /// Whether remote backup is enabled.
432    #[serde(skip_serializing_if = "Option::is_none")]
433    pub remote_backup_enabled: Option<bool>,
434}
435
436/// `FixedDatabase`
437#[derive(Debug, Clone, Serialize, Deserialize)]
438#[serde(rename_all = "camelCase")]
439pub struct FixedDatabase {
440    /// Database identifier.
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub database_id: Option<i32>,
443
444    /// Database name.
445    #[serde(skip_serializing_if = "Option::is_none")]
446    pub name: Option<String>,
447
448    /// Database protocol (e.g. `"redis"`, `"stack"`, `"memcached"`).
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub protocol: Option<String>,
451
452    /// Cloud provider (e.g. `"AWS"`, `"GCP"`, `"Azure"`).
453    #[serde(skip_serializing_if = "Option::is_none")]
454    pub provider: Option<String>,
455
456    /// Cloud region where the database is hosted.
457    #[serde(skip_serializing_if = "Option::is_none")]
458    pub region: Option<String>,
459
460    /// Redis version currently running on the database.
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub redis_version: Option<String>,
463
464    /// Redis version compliance level (e.g. `"latest"`).
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub redis_version_compliance: Option<String>,
467
468    /// Redis Serialization Protocol (RESP) version in use.
469    #[serde(skip_serializing_if = "Option::is_none")]
470    pub resp_version: Option<String>,
471
472    /// Current database status (e.g. `"active"`, `"pending"`).
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub status: Option<String>,
475
476    /// Memory limit from the plan, in the plan's measurement unit.
477    #[serde(skip_serializing_if = "Option::is_none")]
478    pub plan_memory_limit: Option<f64>,
479
480    /// Dataset size from the plan, in the plan's measurement unit.
481    #[serde(skip_serializing_if = "Option::is_none")]
482    pub plan_dataset_size: Option<f64>,
483
484    /// Measurement unit for memory limits (e.g. `"GB"`, `"MB"`).
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub memory_limit_measurement_unit: Option<String>,
487
488    /// Total memory limit in GB, including replication and other overhead.
489    #[serde(skip_serializing_if = "Option::is_none")]
490    pub memory_limit_in_gb: Option<f64>,
491
492    /// Maximum dataset size for this database in GB.
493    #[serde(skip_serializing_if = "Option::is_none")]
494    pub dataset_size_in_gb: Option<f64>,
495
496    /// Currently used memory, in MB.
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub memory_used_in_mb: Option<f64>,
499
500    /// Network usage so far this month, in bytes.
501    #[serde(skip_serializing_if = "Option::is_none")]
502    pub network_monthly_usage_in_byte: Option<f64>,
503
504    /// Memory storage type (e.g. `"ram"`, `"ram-and-flash"`).
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub memory_storage: Option<String>,
507
508    /// Whether Redis Flex (auto-tiering) is enabled.
509    #[serde(skip_serializing_if = "Option::is_none")]
510    pub redis_flex: Option<bool>,
511
512    /// Whether Redis OSS Cluster API support is enabled.
513    #[serde(
514        rename = "supportOSSClusterApi",
515        skip_serializing_if = "Option::is_none"
516    )]
517    pub support_oss_cluster_api: Option<bool>,
518
519    /// Whether the external endpoint is used for OSS Cluster API.
520    #[serde(
521        rename = "useExternalEndpointForOSSClusterApi",
522        skip_serializing_if = "Option::is_none"
523    )]
524    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
525
526    /// Data persistence type (e.g. `"none"`, `"aof-every-1-second"`, `"snapshot-every-1-hour"`).
527    #[serde(skip_serializing_if = "Option::is_none")]
528    pub data_persistence: Option<String>,
529
530    /// Whether replication is enabled.
531    #[serde(skip_serializing_if = "Option::is_none")]
532    pub replication: Option<bool>,
533
534    /// Data eviction policy (e.g. `"allkeys-lru"`, `"noeviction"`).
535    #[serde(skip_serializing_if = "Option::is_none")]
536    pub data_eviction_policy: Option<String>,
537
538    /// Timestamp when the database was activated.
539    #[serde(skip_serializing_if = "Option::is_none")]
540    pub activated_on: Option<String>,
541
542    /// Timestamp when the database was last modified.
543    #[serde(skip_serializing_if = "Option::is_none")]
544    pub last_modified: Option<String>,
545
546    /// Public endpoint hostname/port for connecting from the internet.
547    #[serde(skip_serializing_if = "Option::is_none")]
548    pub public_endpoint: Option<String>,
549
550    /// Private endpoint hostname/port for connecting from inside the VPC.
551    #[serde(skip_serializing_if = "Option::is_none")]
552    pub private_endpoint: Option<String>,
553
554    /// Additional dynamic endpoints. See [`DynamicEndpoints`].
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub dynamic_endpoints: Option<DynamicEndpoints>,
557
558    /// Replica-as-source endpoints (`public`/`private`), returned on reads.
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub replica_as_source_endpoints: Option<DynamicEndpoints>,
561
562    /// Security configuration (TLS, source IPs, authentication).
563    ///
564    /// Returned as a nested `security` object on reads. See
565    /// [`FixedDatabaseSecurity`].
566    #[serde(skip_serializing_if = "Option::is_none")]
567    pub security: Option<FixedDatabaseSecurity>,
568
569    /// Replica of configuration
570    #[serde(skip_serializing_if = "Option::is_none")]
571    pub replica: Option<ReplicaOfSpec>,
572
573    /// Clustering configuration (shard hashing).
574    ///
575    /// Returned as a nested `clustering` object on reads. See
576    /// [`FixedDatabaseClustering`].
577    #[serde(skip_serializing_if = "Option::is_none")]
578    pub clustering: Option<FixedDatabaseClustering>,
579
580    /// Redis modules/capabilities enabled on this database
581    #[serde(skip_serializing_if = "Option::is_none")]
582    pub modules: Option<Vec<DatabaseModuleSpec>>,
583
584    /// Database alert configurations
585    #[serde(skip_serializing_if = "Option::is_none")]
586    pub alerts: Option<Vec<DatabaseAlertSpec>>,
587
588    /// Backup configuration as returned on reads (nested `backup` object).
589    /// See [`FixedDatabaseBackup`].
590    #[serde(skip_serializing_if = "Option::is_none")]
591    pub backup: Option<FixedDatabaseBackup>,
592
593    /// HATEOAS links
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub links: Option<Vec<Link>>,
596}
597
598/// `DatabaseSlowLogEntries`
599#[derive(Debug, Clone, Serialize, Deserialize)]
600pub struct DatabaseSlowLogEntries {
601    /// Slowlog entries returned for the database.
602    #[serde(skip_serializing_if = "Option::is_none")]
603    pub entries: Option<Vec<DatabaseSlowLogEntry>>,
604
605    /// HATEOAS links
606    #[serde(skip_serializing_if = "Option::is_none")]
607    pub links: Option<Vec<Link>>,
608}
609
610/// Essentials database definition
611///
612/// # Example
613///
614/// ```
615/// use redis_cloud::fixed::databases::FixedDatabaseCreateRequest;
616///
617/// let request = FixedDatabaseCreateRequest::builder()
618///     .name("my-database")
619///     .replication(true)
620///     .build();
621/// ```
622#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
623#[serde(rename_all = "camelCase")]
624pub struct FixedDatabaseCreateRequest {
625    /// Subscription ID being updated. Server-populated from the path.
626    #[serde(skip_serializing_if = "Option::is_none")]
627    #[builder(default, setter(strip_option))]
628    pub subscription_id: Option<i32>,
629
630    /// 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.
631    #[builder(setter(into))]
632    pub name: String,
633
634    /// Optional. Database protocol. Use 'stack' to get all of Redis' advanced capabilities. Only use 'redis' for Pay-as-you-go or Redis Flex subscriptions. Default: 'stack' for most subscriptions, 'redis' for Redis Flex subscriptions.
635    #[serde(skip_serializing_if = "Option::is_none")]
636    #[builder(default, setter(strip_option, into))]
637    pub protocol: Option<String>,
638
639    /// (Pay-as-you-go subscriptions only) Optional. Total memory in GB, including replication and other overhead. You cannot set both datasetSizeInGb and totalMemoryInGb.
640    #[serde(skip_serializing_if = "Option::is_none")]
641    #[builder(default, setter(strip_option))]
642    pub memory_limit_in_gb: Option<f64>,
643
644    /// (Pay-as-you-go subscriptions only) 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.
645    #[serde(skip_serializing_if = "Option::is_none")]
646    #[builder(default, setter(strip_option))]
647    pub dataset_size_in_gb: Option<f64>,
648
649    /// (Pay-as-you-go subscriptions only) Optional. Support Redis [OSS Cluster API](https://redis.io/docs/latest/operate/rc/databases/configuration/clustering/#oss-cluster-api). Default: 'false'
650    #[serde(skip_serializing_if = "Option::is_none")]
651    #[builder(default, setter(strip_option))]
652    pub support_oss_cluster_api: Option<bool>,
653
654    /// Optional. If specified, redisVersion defines the Redis database version. If omitted, the Redis version will be set to the default version.  (available in 'GET /fixed/redis-versions')
655    #[serde(skip_serializing_if = "Option::is_none")]
656    #[builder(default, setter(strip_option, into))]
657    pub redis_version: Option<String>,
658
659    /// Optional. Redis Serialization Protocol version. Must be compatible with Redis version.
660    #[serde(skip_serializing_if = "Option::is_none")]
661    #[builder(default, setter(strip_option, into))]
662    pub resp_version: Option<String>,
663
664    /// (Pay-as-you-go subscriptions only) 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'
665    #[serde(skip_serializing_if = "Option::is_none")]
666    #[builder(default, setter(strip_option))]
667    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
668
669    /// (Pay-as-you-go subscriptions only) Optional. Distributes database data to different cloud instances. Default: 'false'
670    #[serde(skip_serializing_if = "Option::is_none")]
671    #[builder(default, setter(strip_option))]
672    pub enable_database_clustering: Option<bool>,
673
674    /// (Pay-as-you-go subscriptions only) Optional. Specifies the number of master shards.
675    #[serde(skip_serializing_if = "Option::is_none")]
676    #[builder(default, setter(strip_option))]
677    pub number_of_shards: Option<i32>,
678
679    /// Optional. Type and rate of data persistence in persistent storage. Use GET /fixed/plans/{planId} to see if your plan supports data persistence.
680    #[serde(skip_serializing_if = "Option::is_none")]
681    #[builder(default, setter(strip_option, into))]
682    pub data_persistence: Option<String>,
683
684    /// Optional. Data eviction policy.
685    #[serde(skip_serializing_if = "Option::is_none")]
686    #[builder(default, setter(strip_option, into))]
687    pub data_eviction_policy: Option<String>,
688
689    /// Optional. Sets database replication. Use GET /fixed/plans/{planId} to see if your plan supports database replication.
690    #[serde(skip_serializing_if = "Option::is_none")]
691    #[builder(default, setter(strip_option))]
692    pub replication: Option<bool>,
693
694    /// 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. Use GET /fixed/plans/{planId} to see if your plan supports database backups.
695    #[serde(skip_serializing_if = "Option::is_none")]
696    #[builder(default, setter(strip_option, into))]
697    pub periodic_backup_path: Option<String>,
698
699    /// 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. Use GET /fixed/plans/{planId} to see how many CIDR allow rules your plan supports. Example: '['192.168.10.0/32', '192.168.12.0/24']'
700    #[serde(skip_serializing_if = "Option::is_none")]
701    #[builder(default, setter(strip_option))]
702    pub source_ips: Option<Vec<String>>,
703
704    /// (Pay-as-you-go subscriptions only) Optional. Hashing policy Regex rules. Used only if 'enableDatabaseClustering' is set to 'true' and .
705    #[serde(skip_serializing_if = "Option::is_none")]
706    #[builder(default, setter(strip_option))]
707    pub regex_rules: Option<Vec<String>>,
708
709    /// 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>'].
710    #[serde(skip_serializing_if = "Option::is_none")]
711    #[builder(default, setter(strip_option))]
712    pub replica_of: Option<Vec<String>>,
713
714    /// Optional. Replica-of (Active-Passive) configuration. See [`ReplicaOfSpec`].
715    #[serde(skip_serializing_if = "Option::is_none")]
716    #[builder(default, setter(strip_option))]
717    pub replica: Option<ReplicaOfSpec>,
718
719    /// 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'
720    #[serde(skip_serializing_if = "Option::is_none")]
721    #[builder(default, setter(strip_option, into))]
722    pub client_ssl_certificate: Option<String>,
723
724    /// Optional. A list of client TLS/SSL certificates. If specified, mTLS authentication will be required to authenticate user connections.
725    #[serde(skip_serializing_if = "Option::is_none")]
726    #[builder(default, setter(strip_option))]
727    pub client_tls_certificates: Option<Vec<DatabaseCertificateSpec>>,
728
729    /// Optional. When 'true', requires TLS authentication for all connections - mTLS with valid clientTlsCertificates, regular TLS when clientTlsCertificates is not provided. Default: 'false'
730    #[serde(skip_serializing_if = "Option::is_none")]
731    #[builder(default, setter(strip_option))]
732    pub enable_tls: Option<bool>,
733
734    /// Optional. Password to access the database. If not set, a random 32-character alphanumeric password will be automatically generated.
735    #[serde(skip_serializing_if = "Option::is_none")]
736    #[builder(default, setter(strip_option, into))]
737    pub password: Option<String>,
738
739    /// Optional. Redis database alert details.
740    #[serde(skip_serializing_if = "Option::is_none")]
741    #[builder(default, setter(strip_option))]
742    pub alerts: Option<Vec<DatabaseAlertSpec>>,
743
744    /// 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. Can only be set if 'protocol' is 'redis'.
745    #[serde(skip_serializing_if = "Option::is_none")]
746    #[builder(default, setter(strip_option))]
747    pub modules: Option<Vec<DatabaseModuleSpec>>,
748
749    /// Read-only on the response; populated by the server with the
750    /// operation type (e.g. `"CREATE_FIXED_DATABASE"`).
751    #[serde(skip_serializing_if = "Option::is_none")]
752    #[builder(default, setter(strip_option, into))]
753    pub command_type: Option<String>,
754}
755
756/// Essentials database update request
757///
758/// # Example
759///
760/// ```
761/// use redis_cloud::fixed::databases::FixedDatabaseUpdateRequest;
762///
763/// let request = FixedDatabaseUpdateRequest::builder()
764///     .name("updated-name")
765///     .build();
766/// ```
767#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
768#[serde(rename_all = "camelCase")]
769pub struct FixedDatabaseUpdateRequest {
770    /// Subscription ID being updated. Server-populated from the path.
771    #[serde(skip_serializing_if = "Option::is_none")]
772    #[builder(default, setter(strip_option))]
773    pub subscription_id: Option<i32>,
774
775    /// Database ID being updated. Server-populated from the path.
776    #[serde(skip_serializing_if = "Option::is_none")]
777    #[builder(default, setter(strip_option))]
778    pub database_id: Option<i32>,
779
780    /// Optional. Updated database name.
781    #[serde(skip_serializing_if = "Option::is_none")]
782    #[builder(default, setter(strip_option, into))]
783    pub name: Option<String>,
784
785    /// (Pay-as-you-go subscriptions only) Optional. Total memory in GB, including replication and other overhead. You cannot set both datasetSizeInGb and totalMemoryInGb.
786    #[serde(skip_serializing_if = "Option::is_none")]
787    #[builder(default, setter(strip_option))]
788    pub memory_limit_in_gb: Option<f64>,
789
790    /// (Pay-as-you-go subscriptions only) 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.
791    #[serde(skip_serializing_if = "Option::is_none")]
792    #[builder(default, setter(strip_option))]
793    pub dataset_size_in_gb: Option<f64>,
794
795    /// (Pay-as-you-go subscriptions only) Optional. Support Redis [OSS Cluster API](https://redis.io/docs/latest/operate/rc/databases/configuration/clustering/#oss-cluster-api).
796    #[serde(skip_serializing_if = "Option::is_none")]
797    #[builder(default, setter(strip_option))]
798    pub support_oss_cluster_api: Option<bool>,
799
800    /// Optional. Redis Serialization Protocol version. Must be compatible with Redis version.
801    #[serde(skip_serializing_if = "Option::is_none")]
802    #[builder(default, setter(strip_option, into))]
803    pub resp_version: Option<String>,
804
805    /// (Pay-as-you-go subscriptions only) 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'
806    #[serde(skip_serializing_if = "Option::is_none")]
807    #[builder(default, setter(strip_option))]
808    pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
809
810    /// (Pay-as-you-go subscriptions only) Optional. Distributes database data to different cloud instances.
811    #[serde(skip_serializing_if = "Option::is_none")]
812    #[builder(default, setter(strip_option))]
813    pub enable_database_clustering: Option<bool>,
814
815    /// (Pay-as-you-go subscriptions only) Optional. Changes the number of master shards.
816    #[serde(skip_serializing_if = "Option::is_none")]
817    #[builder(default, setter(strip_option))]
818    pub number_of_shards: Option<i32>,
819
820    /// Optional. Type and rate of data persistence in persistent storage. Use GET /fixed/plans/{planId} to see if your plan supports data persistence.
821    #[serde(skip_serializing_if = "Option::is_none")]
822    #[builder(default, setter(strip_option, into))]
823    pub data_persistence: Option<String>,
824
825    /// Optional. Turns database replication on or off.
826    #[serde(skip_serializing_if = "Option::is_none")]
827    #[builder(default, setter(strip_option, into))]
828    pub data_eviction_policy: Option<String>,
829
830    /// Optional. Sets database replication. Use GET /fixed/plans/{planId} to see if your plan supports database replication.
831    #[serde(skip_serializing_if = "Option::is_none")]
832    #[builder(default, setter(strip_option))]
833    pub replication: Option<bool>,
834
835    /// 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. Use GET /fixed/plans/{planId} to see if your plan supports database backups. If set to an empty string, the backup path will be removed.
836    #[serde(skip_serializing_if = "Option::is_none")]
837    #[builder(default, setter(strip_option, into))]
838    pub periodic_backup_path: Option<String>,
839
840    /// 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']'
841    #[serde(skip_serializing_if = "Option::is_none")]
842    #[builder(default, setter(strip_option))]
843    pub source_ips: Option<Vec<String>>,
844
845    /// Optional. This database will be a replica of the specified Redis databases provided as one or more URI (sample format: 'redis://user:password@host:port)'. If the URI provided is Redis Cloud instance, only host and port should be provided (using the format: ['<redis://endpoint1:6379>', '<redis://endpoint2:6380>'] ).
846    #[serde(skip_serializing_if = "Option::is_none")]
847    #[builder(default, setter(strip_option))]
848    pub replica_of: Option<Vec<String>>,
849
850    /// Optional. Replica-of (Active-Passive) configuration. See [`ReplicaOfSpec`].
851    #[serde(skip_serializing_if = "Option::is_none")]
852    #[builder(default, setter(strip_option))]
853    pub replica: Option<ReplicaOfSpec>,
854
855    /// (Pay-as-you-go subscriptions only) Optional. Hashing policy Regex rules. Used only if 'shardingType' is 'custom-regex-rules'.
856    #[serde(skip_serializing_if = "Option::is_none")]
857    #[builder(default, setter(strip_option))]
858    pub regex_rules: Option<Vec<String>>,
859
860    /// 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'.
861    #[serde(skip_serializing_if = "Option::is_none")]
862    #[builder(default, setter(strip_option, into))]
863    pub client_ssl_certificate: Option<String>,
864
865    /// 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'.
866    #[serde(skip_serializing_if = "Option::is_none")]
867    #[builder(default, setter(strip_option))]
868    pub client_tls_certificates: Option<Vec<DatabaseCertificateSpec>>,
869
870    /// 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.
871    #[serde(skip_serializing_if = "Option::is_none")]
872    #[builder(default, setter(strip_option))]
873    pub enable_tls: Option<bool>,
874
875    /// Optional. Changes the password used to access the database with the 'default' user.
876    #[serde(skip_serializing_if = "Option::is_none")]
877    #[builder(default, setter(strip_option, into))]
878    pub password: Option<String>,
879
880    /// Optional. When 'true', allows connecting to the database with the 'default' user. When 'false', only defined access control users can connect to the database.
881    #[serde(skip_serializing_if = "Option::is_none")]
882    #[builder(default, setter(strip_option))]
883    pub enable_default_user: Option<bool>,
884
885    /// Optional. Changes Redis database alert details.
886    #[serde(skip_serializing_if = "Option::is_none")]
887    #[builder(default, setter(strip_option))]
888    pub alerts: Option<Vec<DatabaseAlertSpec>>,
889
890    /// Read-only on the response; populated by the server with the
891    /// operation type (e.g. `"UPDATE_FIXED_DATABASE"`).
892    #[serde(skip_serializing_if = "Option::is_none")]
893    #[builder(default, setter(strip_option, into))]
894    pub command_type: Option<String>,
895}
896
897// ============================================================================
898// Handler
899// ============================================================================
900
901/// Handler for Essentials database operations
902///
903/// Manages fixed-capacity databases with simplified configuration
904/// and predictable pricing for Redis Cloud Essentials subscriptions.
905pub struct FixedDatabaseHandler {
906    client: CloudClient,
907}
908
909impl FixedDatabaseHandler {
910    /// Create a new handler
911    #[must_use]
912    pub fn new(client: CloudClient) -> Self {
913        Self { client }
914    }
915
916    /// Get all databases in an Essentials subscription
917    /// Gets a list of all databases in the specified Essentials subscription.
918    ///
919    /// GET /fixed/subscriptions/{subscriptionId}/databases
920    pub async fn list(
921        &self,
922        subscription_id: i32,
923        offset: Option<i32>,
924        limit: Option<i32>,
925    ) -> Result<AccountFixedSubscriptionDatabases> {
926        let mut query = Vec::new();
927        if let Some(v) = offset {
928            query.push(format!("offset={v}"));
929        }
930        if let Some(v) = limit {
931            query.push(format!("limit={v}"));
932        }
933        let query_string = if query.is_empty() {
934            String::new()
935        } else {
936            format!("?{}", query.join("&"))
937        };
938        self.client
939            .get(&format!(
940                "/fixed/subscriptions/{subscription_id}/databases{query_string}"
941            ))
942            .await
943    }
944
945    /// Create Essentials database
946    /// Creates a new database in the specified Essentials subscription.
947    ///
948    /// POST /fixed/subscriptions/{subscriptionId}/databases
949    pub async fn create(
950        &self,
951        subscription_id: i32,
952        request: &FixedDatabaseCreateRequest,
953    ) -> Result<TaskStateUpdate> {
954        self.client
955            .post(
956                &format!("/fixed/subscriptions/{subscription_id}/databases"),
957                request,
958            )
959            .await
960    }
961
962    /// Delete Essentials database
963    /// Deletes a database from an Essentials subscription.
964    ///
965    /// DELETE /fixed/subscriptions/{subscriptionId}/databases/{databaseId}
966    pub async fn delete_by_id(
967        &self,
968        subscription_id: i32,
969        database_id: i32,
970    ) -> Result<TaskStateUpdate> {
971        let response = self
972            .client
973            .delete_raw(&format!(
974                "/fixed/subscriptions/{subscription_id}/databases/{database_id}"
975            ))
976            .await?;
977        serde_json::from_value(response).map_err(Into::into)
978    }
979
980    /// Get a single Essentials database
981    /// Gets details and settings of a single database in an Essentials subscription.
982    ///
983    /// GET /fixed/subscriptions/{subscriptionId}/databases/{databaseId}
984    pub async fn get_by_id(&self, subscription_id: i32, database_id: i32) -> Result<FixedDatabase> {
985        self.client
986            .get(&format!(
987                "/fixed/subscriptions/{subscription_id}/databases/{database_id}"
988            ))
989            .await
990    }
991
992    /// Update Essentials database
993    /// Updates the specified Essentials database.
994    ///
995    /// PUT /fixed/subscriptions/{subscriptionId}/databases/{databaseId}
996    pub async fn update(
997        &self,
998        subscription_id: i32,
999        database_id: i32,
1000        request: &FixedDatabaseUpdateRequest,
1001    ) -> Result<TaskStateUpdate> {
1002        self.client
1003            .put(
1004                &format!("/fixed/subscriptions/{subscription_id}/databases/{database_id}"),
1005                request,
1006            )
1007            .await
1008    }
1009
1010    /// Backup Essentials database status
1011    /// Information on the latest database backup status identified by Essentials subscription Id and Essentials database Id
1012    ///
1013    /// GET /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/backup
1014    pub async fn get_backup_status(
1015        &self,
1016        subscription_id: i32,
1017        database_id: i32,
1018    ) -> Result<TaskStateUpdate> {
1019        self.client
1020            .get(&format!(
1021                "/fixed/subscriptions/{subscription_id}/databases/{database_id}/backup"
1022            ))
1023            .await
1024    }
1025
1026    /// Back up Essentials database
1027    /// Manually back up the specified Essentials database to a backup path. By default, backups will be stored in the 'periodicBackupPath' location for this database.
1028    ///
1029    /// POST /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/backup
1030    pub async fn backup(
1031        &self,
1032        subscription_id: i32,
1033        database_id: i32,
1034        request: &FixedDatabaseBackupRequest,
1035    ) -> Result<TaskStateUpdate> {
1036        self.client
1037            .post(
1038                &format!("/fixed/subscriptions/{subscription_id}/databases/{database_id}/backup"),
1039                request,
1040            )
1041            .await
1042    }
1043
1044    /// Get Essentials database import status
1045    /// Gets information on the latest import attempt for this Essentials database.
1046    ///
1047    /// GET /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/import
1048    pub async fn get_import_status(
1049        &self,
1050        subscription_id: i32,
1051        database_id: i32,
1052    ) -> Result<TaskStateUpdate> {
1053        self.client
1054            .get(&format!(
1055                "/fixed/subscriptions/{subscription_id}/databases/{database_id}/import"
1056            ))
1057            .await
1058    }
1059
1060    /// Import data to an Essentials database
1061    /// Imports data from an RDB file or from a different Redis database into this Essentials database. WARNING: Importing data into a database removes all existing data from the database.
1062    ///
1063    /// POST /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/import
1064    pub async fn import(
1065        &self,
1066        subscription_id: i32,
1067        database_id: i32,
1068        request: &FixedDatabaseImportRequest,
1069    ) -> Result<TaskStateUpdate> {
1070        self.client
1071            .post(
1072                &format!("/fixed/subscriptions/{subscription_id}/databases/{database_id}/import"),
1073                request,
1074            )
1075            .await
1076    }
1077
1078    /// Get Essentials database slow-log by database id
1079    /// Get slow-log for a specific database identified by Essentials subscription Id and database Id
1080    ///
1081    /// GET /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/slow-log
1082    pub async fn get_slow_log(
1083        &self,
1084        subscription_id: i32,
1085        database_id: i32,
1086    ) -> Result<DatabaseSlowLogEntries> {
1087        self.client
1088            .get(&format!(
1089                "/fixed/subscriptions/{subscription_id}/databases/{database_id}/slow-log"
1090            ))
1091            .await
1092    }
1093
1094    /// Get database tags
1095    /// Gets a list of all database tags.
1096    ///
1097    /// GET /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/tags
1098    pub async fn get_tags(&self, subscription_id: i32, database_id: i32) -> Result<CloudTags> {
1099        self.client
1100            .get(&format!(
1101                "/fixed/subscriptions/{subscription_id}/databases/{database_id}/tags"
1102            ))
1103            .await
1104    }
1105
1106    /// Add a database tag
1107    /// Adds a single database tag to a database.
1108    ///
1109    /// POST /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/tags
1110    pub async fn create_tag(
1111        &self,
1112        subscription_id: i32,
1113        database_id: i32,
1114        request: &DatabaseTagCreateRequest,
1115    ) -> Result<CloudTag> {
1116        self.client
1117            .post(
1118                &format!("/fixed/subscriptions/{subscription_id}/databases/{database_id}/tags"),
1119                request,
1120            )
1121            .await
1122    }
1123
1124    /// Overwrite database tags
1125    /// Overwrites all tags on the database.
1126    ///
1127    /// PUT /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/tags
1128    pub async fn update_tags(
1129        &self,
1130        subscription_id: i32,
1131        database_id: i32,
1132        request: &DatabaseTagsUpdateRequest,
1133    ) -> Result<CloudTags> {
1134        self.client
1135            .put(
1136                &format!("/fixed/subscriptions/{subscription_id}/databases/{database_id}/tags"),
1137                request,
1138            )
1139            .await
1140    }
1141
1142    /// Delete database tag
1143    /// Removes the specified tag from the database.
1144    ///
1145    /// DELETE /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/tags/{tagKey}
1146    pub async fn delete_tag(
1147        &self,
1148        subscription_id: i32,
1149        database_id: i32,
1150        tag_key: String,
1151    ) -> Result<HashMap<String, Value>> {
1152        let response = self
1153            .client
1154            .delete_raw(&format!(
1155                "/fixed/subscriptions/{subscription_id}/databases/{database_id}/tags/{tag_key}"
1156            ))
1157            .await?;
1158        serde_json::from_value(response).map_err(Into::into)
1159    }
1160
1161    /// Update database tag value
1162    /// Updates the value of the specified database tag.
1163    ///
1164    /// PUT /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/tags/{tagKey}
1165    pub async fn update_tag(
1166        &self,
1167        subscription_id: i32,
1168        database_id: i32,
1169        tag_key: String,
1170        request: &DatabaseTagUpdateRequest,
1171    ) -> Result<CloudTag> {
1172        self.client
1173            .put(
1174                &format!(
1175                    "/fixed/subscriptions/{subscription_id}/databases/{database_id}/tags/{tag_key}"
1176                ),
1177                request,
1178            )
1179            .await
1180    }
1181
1182    // ========================================================================
1183    // Additional endpoints
1184    // ========================================================================
1185
1186    /// Get available target Redis versions for upgrade
1187    /// Gets a list of Redis versions that the Essentials database can be upgraded to.
1188    ///
1189    /// GET /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/available-target-versions
1190    pub async fn get_available_target_versions(
1191        &self,
1192        subscription_id: i32,
1193        database_id: i32,
1194    ) -> Result<Value> {
1195        self.client
1196            .get_raw(&format!(
1197                "/fixed/subscriptions/{subscription_id}/databases/{database_id}/available-target-versions"
1198            ))
1199            .await
1200    }
1201
1202    /// Get Essentials database version upgrade status
1203    /// Gets information on the latest upgrade attempt for this Essentials database.
1204    ///
1205    /// GET /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/upgrade
1206    pub async fn get_upgrade_status(
1207        &self,
1208        subscription_id: i32,
1209        database_id: i32,
1210    ) -> Result<Value> {
1211        self.client
1212            .get_raw(&format!(
1213                "/fixed/subscriptions/{subscription_id}/databases/{database_id}/upgrade"
1214            ))
1215            .await
1216    }
1217
1218    /// Upgrade Essentials database Redis version
1219    /// Upgrades the specified Essentials database to a later Redis version.
1220    ///
1221    /// POST /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/upgrade
1222    pub async fn upgrade_redis_version(
1223        &self,
1224        subscription_id: i32,
1225        database_id: i32,
1226        target_version: &str,
1227    ) -> Result<Value> {
1228        let request = serde_json::json!({
1229            "targetVersion": target_version
1230        });
1231        self.client
1232            .post_raw(
1233                &format!("/fixed/subscriptions/{subscription_id}/databases/{database_id}/upgrade"),
1234                request,
1235            )
1236            .await
1237    }
1238
1239    /// Get Essentials database traffic state
1240    /// Gets the current traffic state for this Essentials database, including
1241    /// whether traffic is stopped and whether it can be resumed.
1242    ///
1243    /// GET /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/traffic
1244    pub async fn get_traffic(
1245        &self,
1246        subscription_id: i32,
1247        database_id: i32,
1248    ) -> Result<DatabaseTrafficStateResponse> {
1249        self.client
1250            .get(&format!(
1251                "/fixed/subscriptions/{subscription_id}/databases/{database_id}/traffic"
1252            ))
1253            .await
1254    }
1255
1256    /// Resume Essentials database traffic
1257    /// Resumes traffic to this Essentials database after it has been stopped.
1258    ///
1259    /// POST /fixed/subscriptions/{subscriptionId}/databases/{databaseId}/traffic/resume
1260    pub async fn resume_traffic(&self, subscription_id: i32, database_id: i32) -> Result<()> {
1261        self.client
1262            .post(
1263                &format!(
1264                    "/fixed/subscriptions/{subscription_id}/databases/{database_id}/traffic/resume"
1265                ),
1266                &serde_json::json!({}),
1267            )
1268            .await
1269    }
1270}