Skip to main content

redis_cloud/fixed/
subscriptions.rs

1//! Subscription management for Essentials (Fixed) plans
2//!
3//! This module manages Redis Cloud Essentials subscriptions, which provide
4//! simplified, fixed-capacity Redis deployments with predictable pricing.
5//! Essentials subscriptions are ideal for smaller, stable workloads.
6//!
7//! # Overview
8//!
9//! Essentials subscriptions offer a streamlined experience with pre-defined
10//! plans that include specific memory allocations, regions, and feature sets.
11//! Unlike Pro subscriptions, they don't support auto-scaling or multi-region
12//! deployments.
13//!
14//! # Key Features
15//!
16//! - **Fixed Plans**: Pre-defined subscription plans with set resources
17//! - **Simple Management**: Create, update, and delete subscriptions
18//! - **Plan Discovery**: Browse available plans by region and size
19//! - **Redis Versions**: Access supported Redis versions for the subscription
20//! - **Cost Predictability**: Fixed monthly pricing based on plan selection
21//!
22//! # Plan Structure
23//!
24//! Essentials plans are defined by:
25//! - Memory size (250MB to 12GB)
26//! - Cloud provider and region
27//! - Included features and modules
28//! - Fixed monthly price
29//!
30//! # Example Usage
31//!
32//! ```no_run
33//! use redis_cloud::{CloudClient, FixedSubscriptionHandler};
34//!
35//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
36//! let client = CloudClient::builder()
37//!     .api_key("your-api-key")
38//!     .api_secret("your-api-secret")
39//!     .build()?;
40//!
41//! let handler = FixedSubscriptionHandler::new(client);
42//!
43//! // List available plans
44//! let plans = handler.list_plans(None, None).await?;
45//!
46//! // Get all fixed subscriptions
47//! let subscriptions = handler.list().await?;
48//! # Ok(())
49//! # }
50//! ```
51
52use crate::types::{Link, ProcessorResponse};
53use crate::{CloudClient, Result};
54use serde::{Deserialize, Serialize};
55use typed_builder::TypedBuilder;
56
57// ============================================================================
58// Models
59// ============================================================================
60
61/// `RedisVersions`
62#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct RedisVersions {
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub redis_versions: Option<Vec<RedisVersion>>,
67}
68
69/// Redis list of Essentials subscriptions plans
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct FixedSubscriptionsPlans {
72    /// HATEOAS links
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub links: Option<Vec<Link>>,
75}
76
77/// Essentials subscription update request
78///
79/// # Example
80///
81/// ```
82/// use redis_cloud::fixed::subscriptions::FixedSubscriptionUpdateRequest;
83///
84/// let request = FixedSubscriptionUpdateRequest::builder()
85///     .name("updated-subscription")
86///     .build();
87/// ```
88#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
89#[serde(rename_all = "camelCase")]
90pub struct FixedSubscriptionUpdateRequest {
91    #[serde(skip_serializing_if = "Option::is_none")]
92    #[builder(default, setter(strip_option))]
93    pub subscription_id: Option<i32>,
94
95    /// Optional. Updated subscription name.
96    #[serde(skip_serializing_if = "Option::is_none")]
97    #[builder(default, setter(strip_option, into))]
98    pub name: Option<String>,
99
100    /// Optional. An Essentials plan ID. The plan describes the dataset size, cloud provider and region, and available database configuration options. Use GET /fixed/plans/subscriptions/{subscriptionId} to get a list of compatible options for the specified subscription.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    #[builder(default, setter(strip_option))]
103    pub plan_id: Option<i32>,
104
105    /// Optional. The payment method for the subscription. If set to 'credit-card' , 'paymentMethodId' must be defined.
106    #[serde(skip_serializing_if = "Option::is_none")]
107    #[builder(default, setter(strip_option, into))]
108    pub payment_method: Option<String>,
109
110    /// Optional. The payment method ID you'd like to use for this subscription. Must be a valid payment method ID for this account. Use GET /payment-methods to get a list of payment methods for your account. This value is optional if 'paymentMethod' is 'marketplace', but required if 'paymentMethod' is 'credit-card'.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    #[builder(default, setter(strip_option))]
113    pub payment_method_id: Option<i32>,
114
115    #[serde(skip_serializing_if = "Option::is_none")]
116    #[builder(default, setter(strip_option, into))]
117    pub command_type: Option<String>,
118}
119
120/// Redis Essentials subscription plan information
121#[derive(Debug, Clone, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct FixedSubscriptionsPlan {
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub id: Option<i32>,
126
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub name: Option<String>,
129
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub size: Option<f64>,
132
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub dataset_size: Option<f64>,
135
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub size_measurement_unit: Option<String>,
138
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub provider: Option<String>,
141
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub region: Option<String>,
144
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub region_id: Option<i32>,
147
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub price: Option<i32>,
150
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub price_currency: Option<String>,
153
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub price_period: Option<String>,
156
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub maximum_databases: Option<i32>,
159
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub maximum_throughput: Option<i32>,
162
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub maximum_bandwidth_gb: Option<i32>,
165
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub availability: Option<String>,
168
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub connections: Option<String>,
171
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub cidr_allow_rules: Option<i32>,
174
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub support_data_persistence: Option<bool>,
177
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub redis_flex: Option<bool>,
180
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub support_instant_and_daily_backups: Option<bool>,
183
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub support_replication: Option<bool>,
186
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub support_clustering: Option<bool>,
189
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub support_ssl: Option<bool>,
192
193    /// List of supported alert types for this plan
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub supported_alerts: Option<Vec<String>>,
196
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub customer_support: Option<String>,
199
200    /// HATEOAS links
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub links: Option<Vec<Link>>,
203}
204
205/// Essentials subscription create request
206///
207/// # Example
208///
209/// ```
210/// use redis_cloud::fixed::subscriptions::FixedSubscriptionCreateRequest;
211///
212/// let request = FixedSubscriptionCreateRequest::builder()
213///     .name("my-subscription")
214///     .plan_id(123)
215///     .build();
216/// ```
217#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
218#[serde(rename_all = "camelCase")]
219pub struct FixedSubscriptionCreateRequest {
220    /// New Essentials subscription name.
221    #[builder(setter(into))]
222    pub name: String,
223
224    /// An Essentials plan ID. The plan describes the dataset size, cloud provider and region, and available database configuration options. Use GET /fixed/plans to get a list of available options.
225    pub plan_id: i32,
226
227    /// Optional. The payment method for the subscription. If set to 'credit-card', 'paymentMethodId' must be defined. Default: 'credit-card'
228    #[serde(skip_serializing_if = "Option::is_none")]
229    #[builder(default, setter(strip_option, into))]
230    pub payment_method: Option<String>,
231
232    /// Optional. A valid payment method ID for this account. Use GET /payment-methods to get a list of all payment methods for your account. This value is optional if 'paymentMethod' is 'marketplace', but required for all other account types.
233    #[serde(skip_serializing_if = "Option::is_none")]
234    #[builder(default, setter(strip_option))]
235    pub payment_method_id: Option<i32>,
236
237    #[serde(skip_serializing_if = "Option::is_none")]
238    #[builder(default, setter(strip_option, into))]
239    pub command_type: Option<String>,
240}
241
242/// Redis list of Essentials subscriptions in current account
243#[derive(Debug, Clone, Serialize, Deserialize)]
244#[serde(rename_all = "camelCase")]
245pub struct FixedSubscriptions {
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub account_id: Option<i32>,
248
249    /// List of Essentials subscriptions
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub subscriptions: Option<Vec<FixedSubscription>>,
252
253    /// HATEOAS links
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub links: Option<Vec<Link>>,
256}
257
258/// `RedisVersion`
259#[derive(Debug, Clone, Serialize, Deserialize)]
260#[serde(rename_all = "camelCase")]
261pub struct RedisVersion {
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub version: Option<String>,
264
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub eol_date: Option<String>,
267
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub is_preview: Option<bool>,
270
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub is_default: Option<bool>,
273}
274
275/// Redis Essentials Subscription information
276#[derive(Debug, Clone, Serialize, Deserialize)]
277#[serde(rename_all = "camelCase")]
278pub struct FixedSubscription {
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub id: Option<i32>,
281
282    #[serde(skip_serializing_if = "Option::is_none")]
283    pub name: Option<String>,
284
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub status: Option<String>,
287
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub payment_method_id: Option<i32>,
290
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub payment_method_type: Option<String>,
293
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub plan_id: Option<i32>,
296
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub plan_name: Option<String>,
299
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub plan_type: Option<String>,
302
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub size: Option<f64>,
305
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub size_measurement_unit: Option<String>,
308
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub provider: Option<String>,
311
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub region: Option<String>,
314
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub price: Option<i32>,
317
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub price_period: Option<String>,
320
321    #[serde(skip_serializing_if = "Option::is_none")]
322    pub price_currency: Option<String>,
323
324    #[serde(skip_serializing_if = "Option::is_none")]
325    pub maximum_databases: Option<i32>,
326
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub availability: Option<String>,
329
330    #[serde(skip_serializing_if = "Option::is_none")]
331    pub connections: Option<String>,
332
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub cidr_allow_rules: Option<i32>,
335
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub support_data_persistence: Option<bool>,
338
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub support_instant_and_daily_backups: Option<bool>,
341
342    #[serde(skip_serializing_if = "Option::is_none")]
343    pub support_replication: Option<bool>,
344
345    #[serde(skip_serializing_if = "Option::is_none")]
346    pub support_clustering: Option<bool>,
347
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub customer_support: Option<String>,
350
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub creation_date: Option<String>,
353
354    #[serde(skip_serializing_if = "Option::is_none")]
355    pub database_status: Option<String>,
356
357    /// HATEOAS links
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub links: Option<Vec<Link>>,
360}
361
362/// `TaskStateUpdate`
363#[derive(Debug, Clone, Serialize, Deserialize)]
364#[serde(rename_all = "camelCase")]
365pub struct TaskStateUpdate {
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub task_id: Option<String>,
368
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub command_type: Option<String>,
371
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub status: Option<String>,
374
375    #[serde(skip_serializing_if = "Option::is_none")]
376    pub description: Option<String>,
377
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub timestamp: Option<String>,
380
381    #[serde(skip_serializing_if = "Option::is_none")]
382    pub response: Option<ProcessorResponse>,
383
384    /// HATEOAS links
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub links: Option<Vec<Link>>,
387}
388
389// ============================================================================
390// Handler
391// ============================================================================
392
393/// Handler for Essentials subscription operations
394///
395/// Manages fixed-capacity subscriptions with pre-defined plans,
396/// simplified pricing, and streamlined configuration options.
397pub struct FixedSubscriptionHandler {
398    client: CloudClient,
399}
400
401impl FixedSubscriptionHandler {
402    /// Create a new handler
403    #[must_use]
404    pub fn new(client: CloudClient) -> Self {
405        Self { client }
406    }
407
408    /// Get Essentials plans
409    /// Gets a list of Essentials plans. The plan describes the dataset size, cloud provider and region, and available database configuration options for an Essentials database.
410    ///
411    /// GET /fixed/plans
412    pub async fn list_plans(
413        &self,
414        provider: Option<String>,
415        redis_flex: Option<bool>,
416    ) -> Result<FixedSubscriptionsPlans> {
417        let mut query = Vec::new();
418        if let Some(v) = provider {
419            query.push(format!("provider={v}"));
420        }
421        if let Some(v) = redis_flex {
422            query.push(format!("redisFlex={v}"));
423        }
424        let query_string = if query.is_empty() {
425            String::new()
426        } else {
427            format!("?{}", query.join("&"))
428        };
429        self.client
430            .get(&format!("/fixed/plans{query_string}"))
431            .await
432    }
433
434    /// Get Essentials plans for a subscription
435    /// Gets a list of compatible Essentials plans for the specified Essentials subscription.
436    ///
437    /// GET /fixed/plans/subscriptions/{subscriptionId}
438    pub async fn get_plans_by_subscription_id(
439        &self,
440        subscription_id: i32,
441    ) -> Result<FixedSubscriptionsPlans> {
442        self.client
443            .get(&format!("/fixed/plans/subscriptions/{subscription_id}"))
444            .await
445    }
446
447    /// Get a single Essentials plan
448    /// Gets information on the specified Essentials plan.
449    ///
450    /// GET /fixed/plans/{planId}
451    pub async fn get_plan_by_id(&self, plan_id: i32) -> Result<FixedSubscriptionsPlan> {
452        self.client.get(&format!("/fixed/plans/{plan_id}")).await
453    }
454
455    /// Get available Redis database versions for specific Essentials subscription
456    /// Gets a list of all available Redis database versions for a specific Essentials subscription.
457    ///
458    /// GET /fixed/redis-versions
459    pub async fn get_redis_versions(&self, subscription_id: i32) -> Result<RedisVersions> {
460        let mut query = Vec::new();
461        query.push(format!("subscriptionId={subscription_id}"));
462        let query_string = if query.is_empty() {
463            String::new()
464        } else {
465            format!("?{}", query.join("&"))
466        };
467        self.client
468            .get(&format!("/fixed/redis-versions{query_string}"))
469            .await
470    }
471
472    /// Get Essentials subscriptions
473    /// Gets a list of all Essentials subscriptions in the current account.
474    ///
475    /// GET /fixed/subscriptions
476    pub async fn list(&self) -> Result<FixedSubscriptions> {
477        self.client.get("/fixed/subscriptions").await
478    }
479
480    /// Create Essentials subscription
481    /// Creates a new Essentials subscription.
482    ///
483    /// POST /fixed/subscriptions
484    pub async fn create(
485        &self,
486        request: &FixedSubscriptionCreateRequest,
487    ) -> Result<TaskStateUpdate> {
488        self.client.post("/fixed/subscriptions", request).await
489    }
490
491    /// Delete Essentials subscription
492    /// Deletes the specified Essentials subscription. All databases in the subscription must be deleted before deleting it.
493    ///
494    /// DELETE /fixed/subscriptions/{subscriptionId}
495    pub async fn delete_by_id(&self, subscription_id: i32) -> Result<TaskStateUpdate> {
496        let response = self
497            .client
498            .delete_raw(&format!("/fixed/subscriptions/{subscription_id}"))
499            .await?;
500        serde_json::from_value(response).map_err(Into::into)
501    }
502
503    /// Get a single Essentials subscription
504    /// Gets information on the specified Essentials subscription.
505    ///
506    /// GET /fixed/subscriptions/{subscriptionId}
507    pub async fn get_by_id(&self, subscription_id: i32) -> Result<FixedSubscription> {
508        self.client
509            .get(&format!("/fixed/subscriptions/{subscription_id}"))
510            .await
511    }
512
513    /// Update Essentials subscription
514    /// Updates the specified Essentials subscription.
515    ///
516    /// PUT /fixed/subscriptions/{subscriptionId}
517    pub async fn update(
518        &self,
519        subscription_id: i32,
520        request: &FixedSubscriptionUpdateRequest,
521    ) -> Result<TaskStateUpdate> {
522        self.client
523            .put(&format!("/fixed/subscriptions/{subscription_id}"), request)
524            .await
525    }
526}