Skip to content

Commit ae0af3e

Browse files
committed
feat(redis-cloud): expose all known API fields as first-class struct members
Addresses ergonomics issues raised in #373 by promoting documented API fields from the generic 'extra' field to typed, first-class struct members. Changes: - Database: Expanded from 3 fields to ~40 first-class fields including all configuration options (TLS, replication, persistence, modules, throughput, backups, ACLs, etc.) - Subscription: Expanded from 11 to 17 fields including deployment_type, cloud_details, pricing, created_timestamp - AccountSubscriptionDatabases: Added subscription field for easier access to nested database arrays - TaskStateUpdate: Added progress field and improved documentation - ProcessorResponse: Enhanced documentation for all fields Benefits: - Eliminates verbose boilerplate for extracting common fields from 'extra' - Provides compile-time type safety and IDE autocomplete for all known fields - Maintains backward compatibility - 'extra' still available for unknown fields - Reduces friction when consuming the library in production applications The 'extra' field now serves its intended purpose: capturing truly unknown or future API fields, not hiding documented ones. Fixes #373
1 parent d00107e commit ae0af3e

File tree

4 files changed

+254
-17
lines changed

4 files changed

+254
-17
lines changed

crates/redis-cloud/src/flexible/databases.rs

Lines changed: 172 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,25 @@ use std::collections::HashMap;
6161
// ============================================================================
6262

6363
/// RedisLabs Account Subscription Databases information
64+
///
65+
/// Response from GET /subscriptions/{subscriptionId}/databases
6466
#[derive(Debug, Clone, Serialize, Deserialize)]
6567
#[serde(rename_all = "camelCase")]
6668
pub struct AccountSubscriptionDatabases {
69+
/// Account ID
6770
#[serde(skip_serializing_if = "Option::is_none")]
6871
pub account_id: Option<i32>,
6972

70-
/// HATEOAS links
73+
/// Subscription information with nested databases array
74+
/// Contains subscriptionId, numberOfDatabases, and databases array
75+
#[serde(skip_serializing_if = "Option::is_none")]
76+
pub subscription: Option<Value>,
77+
78+
/// HATEOAS links for API navigation
7179
#[serde(skip_serializing_if = "Option::is_none")]
7280
pub links: Option<Vec<HashMap<String, Value>>>,
7381

74-
/// Additional fields from the API
82+
/// Only for truly unknown/future API fields
7583
#[serde(flatten)]
7684
pub extra: Value,
7785
}
@@ -522,17 +530,176 @@ pub struct DatabaseBackupRequest {
522530
}
523531

524532
/// Database
533+
///
534+
/// Represents a Redis Cloud database with all known API fields as first-class struct members.
535+
/// The `extra` field is reserved only for truly unknown/future fields that may be added to the API.
525536
#[derive(Debug, Clone, Serialize, Deserialize)]
526537
#[serde(rename_all = "camelCase")]
527538
pub struct Database {
539+
/// Database ID - always present in API responses
540+
pub database_id: i32,
541+
542+
/// Database name
528543
#[serde(skip_serializing_if = "Option::is_none")]
529-
pub database_id: Option<i32>,
544+
pub name: Option<String>,
530545

531-
/// HATEOAS links
546+
/// Database status (e.g., "active", "pending", "error", "draft")
547+
#[serde(skip_serializing_if = "Option::is_none")]
548+
pub status: Option<String>,
549+
550+
/// Cloud provider (e.g., "AWS", "GCP", "Azure")
551+
#[serde(skip_serializing_if = "Option::is_none")]
552+
pub provider: Option<String>,
553+
554+
/// Cloud region (e.g., "us-east-1", "europe-west1")
555+
#[serde(skip_serializing_if = "Option::is_none")]
556+
pub region: Option<String>,
557+
558+
/// Redis version (e.g., "7.2", "7.0")
559+
#[serde(skip_serializing_if = "Option::is_none")]
560+
pub redis_version: Option<String>,
561+
562+
/// Redis Serialization Protocol version
563+
#[serde(skip_serializing_if = "Option::is_none")]
564+
pub resp_version: Option<String>,
565+
566+
/// Total memory limit in GB (including replication and overhead)
567+
#[serde(skip_serializing_if = "Option::is_none")]
568+
pub memory_limit_in_gb: Option<f64>,
569+
570+
/// Dataset size in GB (actual data size, excluding replication)
571+
#[serde(skip_serializing_if = "Option::is_none")]
572+
pub dataset_size_in_gb: Option<f64>,
573+
574+
/// Memory used in MB
575+
#[serde(skip_serializing_if = "Option::is_none")]
576+
pub memory_used_in_mb: Option<f64>,
577+
578+
/// Private endpoint for database connections
579+
#[serde(skip_serializing_if = "Option::is_none")]
580+
pub private_endpoint: Option<String>,
581+
582+
/// Public endpoint for database connections (if enabled)
583+
#[serde(skip_serializing_if = "Option::is_none")]
584+
pub public_endpoint: Option<String>,
585+
586+
/// TCP port on which the database is available
587+
#[serde(skip_serializing_if = "Option::is_none")]
588+
pub port: Option<i32>,
589+
590+
/// Data eviction policy (e.g., "volatile-lru", "allkeys-lru", "noeviction")
591+
#[serde(skip_serializing_if = "Option::is_none")]
592+
pub data_eviction_policy: Option<String>,
593+
594+
/// Data persistence setting (e.g., "aof-every-1-sec", "snapshot-every-1-hour", "none")
595+
#[serde(skip_serializing_if = "Option::is_none")]
596+
pub data_persistence: Option<String>,
597+
598+
/// Whether replication is enabled
599+
#[serde(skip_serializing_if = "Option::is_none")]
600+
pub replication: Option<bool>,
601+
602+
/// Protocol used (e.g., "redis", "memcached")
603+
#[serde(skip_serializing_if = "Option::is_none")]
604+
pub protocol: Option<String>,
605+
606+
/// Support for OSS Cluster API
607+
#[serde(skip_serializing_if = "Option::is_none")]
608+
pub support_oss_cluster_api: Option<bool>,
609+
610+
/// Use external endpoint for OSS Cluster API
611+
#[serde(skip_serializing_if = "Option::is_none")]
612+
pub use_external_endpoint_for_oss_cluster_api: Option<bool>,
613+
614+
/// Whether TLS is enabled for connections
615+
#[serde(skip_serializing_if = "Option::is_none")]
616+
pub enable_tls: Option<bool>,
617+
618+
/// Throughput measurement configuration
619+
#[serde(skip_serializing_if = "Option::is_none")]
620+
pub throughput_measurement: Option<DatabaseThroughputSpec>,
621+
622+
/// Local throughput measurement for Active-Active databases
623+
#[serde(skip_serializing_if = "Option::is_none")]
624+
pub local_throughput_measurement: Option<Vec<LocalThroughput>>,
625+
626+
/// Average item size in bytes (for Auto Tiering)
627+
#[serde(skip_serializing_if = "Option::is_none")]
628+
pub average_item_size_in_bytes: Option<i64>,
629+
630+
/// Path to periodic backup storage location
631+
#[serde(skip_serializing_if = "Option::is_none")]
632+
pub periodic_backup_path: Option<String>,
633+
634+
/// Remote backup configuration
635+
#[serde(skip_serializing_if = "Option::is_none")]
636+
pub remote_backup: Option<Value>,
637+
638+
/// List of source IP addresses or subnet masks allowed to connect
639+
#[serde(skip_serializing_if = "Option::is_none")]
640+
pub source_ip: Option<Vec<String>>,
641+
642+
/// Client TLS/SSL certificate (deprecated, use client_tls_certificates)
643+
#[serde(skip_serializing_if = "Option::is_none")]
644+
pub client_ssl_certificate: Option<String>,
645+
646+
/// List of client TLS/SSL certificates for mTLS authentication
647+
#[serde(skip_serializing_if = "Option::is_none")]
648+
pub client_tls_certificates: Option<Vec<Value>>,
649+
650+
/// Database password (masked in responses for security)
651+
#[serde(skip_serializing_if = "Option::is_none")]
652+
pub password: Option<String>,
653+
654+
/// Memcached SASL username
655+
#[serde(skip_serializing_if = "Option::is_none")]
656+
pub sasl_username: Option<String>,
657+
658+
/// Memcached SASL password (masked in responses)
659+
#[serde(skip_serializing_if = "Option::is_none")]
660+
pub sasl_password: Option<String>,
661+
662+
/// Database alert configurations
663+
#[serde(skip_serializing_if = "Option::is_none")]
664+
pub alerts: Option<Vec<Value>>,
665+
666+
/// Redis modules/capabilities enabled on this database
667+
#[serde(skip_serializing_if = "Option::is_none")]
668+
pub modules: Option<Vec<Value>>,
669+
670+
/// Database hashing policy for clustering
671+
#[serde(skip_serializing_if = "Option::is_none")]
672+
pub sharding_type: Option<String>,
673+
674+
/// Query performance factor (for search and query databases)
675+
#[serde(skip_serializing_if = "Option::is_none")]
676+
pub query_performance_factor: Option<String>,
677+
678+
/// List of databases this database is a replica of
679+
#[serde(skip_serializing_if = "Option::is_none")]
680+
pub replica_of: Option<Vec<String>>,
681+
682+
/// Replica configuration
683+
#[serde(skip_serializing_if = "Option::is_none")]
684+
pub replica: Option<Value>,
685+
686+
/// Whether default Redis user is enabled
687+
#[serde(skip_serializing_if = "Option::is_none")]
688+
pub enable_default_user: Option<bool>,
689+
690+
/// Timestamp when database was activated
691+
#[serde(skip_serializing_if = "Option::is_none")]
692+
pub activated: Option<String>,
693+
694+
/// Timestamp of last modification
695+
#[serde(skip_serializing_if = "Option::is_none")]
696+
pub last_modified: Option<String>,
697+
698+
/// HATEOAS links for API navigation
532699
#[serde(skip_serializing_if = "Option::is_none")]
533700
pub links: Option<Vec<HashMap<String, Value>>>,
534701

535-
/// Additional fields from the API
702+
/// Only for truly unknown/future API fields. All documented fields should be first-class members above.
536703
#[serde(flatten)]
537704
pub extra: Value,
538705
}

crates/redis-cloud/src/flexible/subscriptions.rs

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -580,42 +580,80 @@ pub struct MaintenanceWindow {
580580
/// RedisLabs Subscription information
581581
#[derive(Debug, Clone, Serialize, Deserialize)]
582582
#[serde(rename_all = "camelCase")]
583+
/// Subscription
584+
///
585+
/// Represents a Redis Cloud subscription with all known API fields as first-class struct members.
586+
/// The `extra` field is reserved only for truly unknown/future fields that may be added to the API.
583587
pub struct Subscription {
588+
/// Subscription ID
584589
#[serde(skip_serializing_if = "Option::is_none")]
585590
pub id: Option<i32>,
586591

592+
/// Subscription name
587593
#[serde(skip_serializing_if = "Option::is_none")]
588594
pub name: Option<String>,
589595

596+
/// Subscription status (e.g., "active", "pending", "error")
597+
#[serde(skip_serializing_if = "Option::is_none")]
598+
pub status: Option<String>,
599+
600+
/// Payment method ID
590601
#[serde(skip_serializing_if = "Option::is_none")]
591602
pub payment_method_id: Option<i32>,
592603

604+
/// Payment method type (e.g., "credit-card", "marketplace")
593605
#[serde(skip_serializing_if = "Option::is_none")]
594-
pub status: Option<String>,
606+
pub payment_method_type: Option<String>,
595607

608+
/// Payment method (e.g., "credit-card", "marketplace")
609+
#[serde(skip_serializing_if = "Option::is_none")]
610+
pub payment_method: Option<String>,
611+
612+
/// Memory storage type: "ram" or "ram-and-flash" (Auto Tiering)
596613
#[serde(skip_serializing_if = "Option::is_none")]
597614
pub memory_storage: Option<String>,
598615

616+
/// Persistent storage encryption type
617+
#[serde(skip_serializing_if = "Option::is_none")]
618+
pub persistent_storage_encryption_type: Option<String>,
619+
620+
/// Deployment type: "single-region" or "active-active"
621+
#[serde(skip_serializing_if = "Option::is_none")]
622+
pub deployment_type: Option<String>,
623+
624+
/// Number of databases in this subscription
599625
#[serde(skip_serializing_if = "Option::is_none")]
600626
pub number_of_databases: Option<i32>,
601627

628+
/// Cloud provider details (AWS, GCP, Azure configurations)
602629
#[serde(skip_serializing_if = "Option::is_none")]
603-
pub payment_method_type: Option<String>,
630+
pub cloud_details: Option<Vec<Value>>,
604631

632+
/// Pricing details for the subscription
605633
#[serde(skip_serializing_if = "Option::is_none")]
606-
pub persistent_storage_encryption_type: Option<String>,
634+
pub pricing: Option<Vec<Value>>,
635+
636+
/// Redis version for databases created in this subscription (deprecated)
637+
#[serde(skip_serializing_if = "Option::is_none")]
638+
pub redis_version: Option<String>,
607639

640+
/// Deletion grace period for customer-managed keys
608641
#[serde(skip_serializing_if = "Option::is_none")]
609642
pub deletion_grace_period: Option<String>,
610643

644+
/// Customer-managed key access details for encryption
611645
#[serde(skip_serializing_if = "Option::is_none")]
612646
pub customer_managed_key_access_details: Option<CustomerManagedKeyAccessDetails>,
613647

614-
/// HATEOAS links
648+
/// Timestamp when subscription was created
649+
#[serde(skip_serializing_if = "Option::is_none")]
650+
pub created_timestamp: Option<String>,
651+
652+
/// HATEOAS links for API navigation
615653
#[serde(skip_serializing_if = "Option::is_none")]
616654
pub links: Option<Vec<HashMap<String, Value>>>,
617655

618-
/// Additional fields from the API
656+
/// Only for truly unknown/future API fields. All documented fields should be first-class members above.
619657
#[serde(flatten)]
620658
pub extra: Value,
621659
}

crates/redis-cloud/src/tasks.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,56 +65,75 @@ use std::collections::HashMap;
6565
// ============================================================================
6666

6767
/// ProcessorResponse
68+
///
69+
/// Contains the result of an asynchronous operation
6870
#[derive(Debug, Clone, Serialize, Deserialize)]
6971
#[serde(rename_all = "camelCase")]
7072
pub struct ProcessorResponse {
73+
/// ID of the created/modified resource
7174
#[serde(skip_serializing_if = "Option::is_none")]
7275
pub resource_id: Option<i32>,
7376

77+
/// Additional resource ID (for operations creating multiple resources)
7478
#[serde(skip_serializing_if = "Option::is_none")]
7579
pub additional_resource_id: Option<i32>,
7680

81+
/// Full resource object for the created/modified resource
7782
#[serde(skip_serializing_if = "Option::is_none")]
7883
pub resource: Option<HashMap<String, Value>>,
7984

85+
/// Error message if the operation failed
8086
#[serde(skip_serializing_if = "Option::is_none")]
8187
pub error: Option<String>,
8288

89+
/// Additional information about the operation
8390
#[serde(skip_serializing_if = "Option::is_none")]
8491
pub additional_info: Option<String>,
8592

86-
/// Additional fields from the API
93+
/// Only for truly unknown/future API fields
8794
#[serde(flatten)]
8895
pub extra: Value,
8996
}
9097

9198
/// TaskStateUpdate
99+
///
100+
/// Represents the state and result of an asynchronous task
92101
#[derive(Debug, Clone, Serialize, Deserialize)]
93102
#[serde(rename_all = "camelCase")]
94103
pub struct TaskStateUpdate {
104+
/// Unique task identifier
95105
#[serde(skip_serializing_if = "Option::is_none")]
96106
pub task_id: Option<String>,
97107

108+
/// Type of command being executed (e.g., "CREATE_DATABASE", "DELETE_SUBSCRIPTION")
98109
#[serde(skip_serializing_if = "Option::is_none")]
99110
pub command_type: Option<String>,
100111

112+
/// Current task status (e.g., "processing", "completed", "failed")
101113
#[serde(skip_serializing_if = "Option::is_none")]
102114
pub status: Option<String>,
103115

116+
/// Human-readable description of the task
104117
#[serde(skip_serializing_if = "Option::is_none")]
105118
pub description: Option<String>,
106119

120+
/// Timestamp of last task update
107121
#[serde(skip_serializing_if = "Option::is_none")]
108122
pub timestamp: Option<String>,
109123

124+
/// Task completion percentage (0-100)
125+
#[serde(skip_serializing_if = "Option::is_none")]
126+
pub progress: Option<f64>,
127+
128+
/// Result data once task is completed
110129
#[serde(skip_serializing_if = "Option::is_none")]
111130
pub response: Option<ProcessorResponse>,
112131

113-
/// HATEOAS links
132+
/// HATEOAS links for API navigation
114133
#[serde(skip_serializing_if = "Option::is_none")]
115134
pub links: Option<Vec<HashMap<String, Value>>>,
116135

117-
/// Additional fields from the API
136+
/// Only for truly unknown/future API fields
118137
#[serde(flatten)]
119138
pub extra: Value,
120139
}

0 commit comments

Comments
 (0)