Skip to main content

redisctl_core/cloud/
params.rs

1//! Convenience parameter structs for Cloud database operations
2//!
3//! These structs provide a simpler interface for common operations,
4//! while still allowing fallback to Layer 1 builders for edge cases.
5
6use redis_cloud::databases::{DatabaseCreateRequest, DatabaseImportRequest, DatabaseUpdateRequest};
7
8/// Parameters for creating a database
9///
10/// This is a convenience wrapper around `DatabaseCreateRequest` that
11/// provides a simpler API for common use cases. For advanced options,
12/// use `DatabaseCreateRequest::builder()` directly.
13///
14/// # Example
15///
16/// ```rust,ignore
17/// use redisctl_core::cloud::CreateDatabaseParams;
18///
19/// let params = CreateDatabaseParams::new("my-database", 1.0)
20///     .with_replication(true)
21///     .with_protocol("stack")
22///     .with_data_persistence("aof-every-1-second");
23///
24/// let request = params.into_request();
25/// ```
26#[derive(Debug, Clone)]
27pub struct CreateDatabaseParams {
28    /// Database name (required)
29    pub name: String,
30    /// Memory limit in GB (required)
31    pub memory_limit_in_gb: f64,
32    /// Enable replication (default: true)
33    pub replication: Option<bool>,
34    /// Protocol: "redis", "stack", or "memcached" (default: "redis")
35    pub protocol: Option<String>,
36    /// Data persistence: "none", "aof-every-1-second", "aof-every-write",
37    /// "snapshot-every-1-hour", "snapshot-every-6-hours", "snapshot-every-12-hours"
38    pub data_persistence: Option<String>,
39    /// Data eviction policy (default: "volatile-lru")
40    pub data_eviction_policy: Option<String>,
41    /// Redis version
42    pub redis_version: Option<String>,
43    /// Support OSS Cluster API
44    pub support_oss_cluster_api: Option<bool>,
45    /// TCP port (10000-19999)
46    pub port: Option<i32>,
47}
48
49impl CreateDatabaseParams {
50    /// Create new params with required fields
51    #[must_use]
52    pub fn new(name: impl Into<String>, memory_limit_in_gb: f64) -> Self {
53        Self {
54            name: name.into(),
55            memory_limit_in_gb,
56            replication: None,
57            protocol: None,
58            data_persistence: None,
59            data_eviction_policy: None,
60            redis_version: None,
61            support_oss_cluster_api: None,
62            port: None,
63        }
64    }
65
66    /// Set replication
67    #[must_use]
68    pub fn with_replication(mut self, replication: bool) -> Self {
69        self.replication = Some(replication);
70        self
71    }
72
73    /// Set protocol
74    #[must_use]
75    pub fn with_protocol(mut self, protocol: impl Into<String>) -> Self {
76        self.protocol = Some(protocol.into());
77        self
78    }
79
80    /// Set data persistence
81    #[must_use]
82    pub fn with_data_persistence(mut self, persistence: impl Into<String>) -> Self {
83        self.data_persistence = Some(persistence.into());
84        self
85    }
86
87    /// Set eviction policy
88    #[must_use]
89    pub fn with_eviction_policy(mut self, policy: impl Into<String>) -> Self {
90        self.data_eviction_policy = Some(policy.into());
91        self
92    }
93
94    /// Set Redis version
95    #[must_use]
96    pub fn with_redis_version(mut self, version: impl Into<String>) -> Self {
97        self.redis_version = Some(version.into());
98        self
99    }
100
101    /// Enable OSS Cluster API support
102    #[must_use]
103    pub fn with_oss_cluster_api(mut self, enabled: bool) -> Self {
104        self.support_oss_cluster_api = Some(enabled);
105        self
106    }
107
108    /// Set TCP port
109    #[must_use]
110    pub fn with_port(mut self, port: i32) -> Self {
111        self.port = Some(port);
112        self
113    }
114
115    /// Convert to Layer 1 `DatabaseCreateRequest`
116    ///
117    /// Uses the TypedBuilder pattern from redis-cloud, setting only
118    /// the fields that have values.
119    #[must_use]
120    pub fn into_request(self) -> DatabaseCreateRequest {
121        // Build with just the required name field, then set optionals
122        // We need to use the full builder chain due to TypedBuilder's type system
123        DatabaseCreateRequest::builder()
124            .name(&self.name)
125            .memory_limit_in_gb(self.memory_limit_in_gb)
126            .replication(self.replication.unwrap_or(true))
127            .protocol(self.protocol.unwrap_or_else(|| "redis".to_string()))
128            .data_persistence(self.data_persistence.unwrap_or_else(|| "none".to_string()))
129            .data_eviction_policy(
130                self.data_eviction_policy
131                    .unwrap_or_else(|| "volatile-lru".to_string()),
132            )
133            .build()
134    }
135}
136
137/// Parameters for updating a database
138///
139/// All fields are optional - only set fields you want to change.
140#[derive(Debug, Clone, Default)]
141pub struct UpdateDatabaseParams {
142    /// New database name
143    pub name: Option<String>,
144    /// New memory limit in GB
145    pub memory_limit_in_gb: Option<f64>,
146    /// Change replication setting
147    pub replication: Option<bool>,
148    /// Change data persistence
149    pub data_persistence: Option<String>,
150    /// Change eviction policy
151    pub data_eviction_policy: Option<String>,
152    /// Change OSS Cluster API support
153    pub support_oss_cluster_api: Option<bool>,
154}
155
156impl UpdateDatabaseParams {
157    /// Create empty update params
158    #[must_use]
159    pub fn new() -> Self {
160        Self::default()
161    }
162
163    /// Set new name
164    #[must_use]
165    pub fn with_name(mut self, name: impl Into<String>) -> Self {
166        self.name = Some(name.into());
167        self
168    }
169
170    /// Set new memory limit
171    #[must_use]
172    pub fn with_memory_limit(mut self, memory_gb: f64) -> Self {
173        self.memory_limit_in_gb = Some(memory_gb);
174        self
175    }
176
177    /// Set replication
178    #[must_use]
179    pub fn with_replication(mut self, replication: bool) -> Self {
180        self.replication = Some(replication);
181        self
182    }
183
184    /// Set data persistence
185    #[must_use]
186    pub fn with_data_persistence(mut self, persistence: impl Into<String>) -> Self {
187        self.data_persistence = Some(persistence.into());
188        self
189    }
190
191    /// Set eviction policy
192    #[must_use]
193    pub fn with_eviction_policy(mut self, policy: impl Into<String>) -> Self {
194        self.data_eviction_policy = Some(policy.into());
195        self
196    }
197
198    /// Enable/disable OSS Cluster API
199    #[must_use]
200    pub fn with_oss_cluster_api(mut self, enabled: bool) -> Self {
201        self.support_oss_cluster_api = Some(enabled);
202        self
203    }
204
205    /// Check if any fields are set
206    #[must_use]
207    pub fn is_empty(&self) -> bool {
208        self.name.is_none()
209            && self.memory_limit_in_gb.is_none()
210            && self.replication.is_none()
211            && self.data_persistence.is_none()
212            && self.data_eviction_policy.is_none()
213            && self.support_oss_cluster_api.is_none()
214    }
215
216    /// Convert to Layer 1 `DatabaseUpdateRequest`
217    #[must_use]
218    pub fn into_request(self) -> DatabaseUpdateRequest {
219        // DatabaseUpdateRequest has all optional fields, so we can build with defaults
220        // and only set what we have
221        let mut req = DatabaseUpdateRequest::builder().build();
222
223        req.name = self.name;
224        req.memory_limit_in_gb = self.memory_limit_in_gb;
225        req.replication = self.replication;
226        req.data_persistence = self.data_persistence;
227        req.data_eviction_policy = self.data_eviction_policy;
228        req.support_oss_cluster_api = self.support_oss_cluster_api;
229
230        req
231    }
232}
233
234/// Parameters for importing data into a database
235#[derive(Debug, Clone)]
236pub struct ImportDatabaseParams {
237    /// Source type: "http", "redis", "ftp", "aws-s3", "azure-blob-storage", "google-blob-storage"
238    pub source_type: String,
239    /// URIs to import from
240    pub import_from_uri: Vec<String>,
241}
242
243impl ImportDatabaseParams {
244    /// Create new import params
245    #[must_use]
246    pub fn new(source_type: impl Into<String>, uri: impl Into<String>) -> Self {
247        Self {
248            source_type: source_type.into(),
249            import_from_uri: vec![uri.into()],
250        }
251    }
252
253    /// Add additional URI to import from
254    #[must_use]
255    pub fn with_additional_uri(mut self, uri: impl Into<String>) -> Self {
256        self.import_from_uri.push(uri.into());
257        self
258    }
259
260    /// Convert to Layer 1 `DatabaseImportRequest`
261    #[must_use]
262    pub fn into_request(self) -> DatabaseImportRequest {
263        DatabaseImportRequest::builder()
264            .source_type(&self.source_type)
265            .import_from_uri(self.import_from_uri)
266            .build()
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    #[test]
275    fn test_create_database_params_basic() {
276        let params = CreateDatabaseParams::new("test-db", 1.0);
277        let request = params.into_request();
278        assert_eq!(request.name, "test-db");
279        assert_eq!(request.memory_limit_in_gb, Some(1.0));
280    }
281
282    #[test]
283    fn test_create_database_params_with_options() {
284        let params = CreateDatabaseParams::new("test-db", 2.0)
285            .with_replication(true)
286            .with_protocol("stack")
287            .with_data_persistence("aof-every-1-second");
288
289        let request = params.into_request();
290        assert_eq!(request.name, "test-db");
291        assert_eq!(request.memory_limit_in_gb, Some(2.0));
292        assert_eq!(request.replication, Some(true));
293        assert_eq!(request.protocol, Some("stack".to_string()));
294        assert_eq!(
295            request.data_persistence,
296            Some("aof-every-1-second".to_string())
297        );
298    }
299
300    #[test]
301    fn test_update_database_params_empty() {
302        let params = UpdateDatabaseParams::new();
303        assert!(params.is_empty());
304    }
305
306    #[test]
307    fn test_update_database_params_with_changes() {
308        let params = UpdateDatabaseParams::new()
309            .with_name("new-name")
310            .with_memory_limit(4.0);
311
312        assert!(!params.is_empty());
313        let request = params.into_request();
314        assert_eq!(request.name, Some("new-name".to_string()));
315        assert_eq!(request.memory_limit_in_gb, Some(4.0));
316    }
317
318    #[test]
319    fn test_import_database_params() {
320        let params = ImportDatabaseParams::new("aws-s3", "s3://bucket/file.rdb")
321            .with_additional_uri("s3://bucket/file2.rdb");
322
323        let request = params.into_request();
324        // source_type and import_from_uri are NOT Option-wrapped in DatabaseImportRequest
325        assert_eq!(request.source_type, "aws-s3");
326        assert_eq!(
327            request.import_from_uri,
328            vec![
329                "s3://bucket/file.rdb".to_string(),
330                "s3://bucket/file2.rdb".to_string()
331            ]
332        );
333    }
334}