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;
53pub use crate::types::TaskStateUpdate;
54use crate::{CloudClient, Result};
55use serde::{Deserialize, Serialize};
56use typed_builder::TypedBuilder;
57
58// ============================================================================
59// Models
60// ============================================================================
61
62/// `RedisVersions`
63#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(rename_all = "camelCase")]
65pub struct RedisVersions {
66    /// List of Redis versions available for the account.
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub redis_versions: Option<Vec<RedisVersion>>,
69}
70
71/// Redis list of Essentials subscriptions plans
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct FixedSubscriptionsPlans {
74    /// The available Essentials plans. The OpenAPI schema omits this array, but
75    /// the live `GET /fixed/plans` responses include it (see #140).
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub plans: Option<Vec<FixedSubscriptionsPlan>>,
78
79    /// HATEOAS links
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub links: Option<Vec<Link>>,
82}
83
84/// Essentials subscription update request
85///
86/// # Example
87///
88/// ```
89/// use redis_cloud::fixed::subscriptions::FixedSubscriptionUpdateRequest;
90///
91/// let request = FixedSubscriptionUpdateRequest::builder()
92///     .name("updated-subscription")
93///     .build();
94/// ```
95#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
96#[serde(rename_all = "camelCase")]
97pub struct FixedSubscriptionUpdateRequest {
98    /// Subscription ID being updated. Server-populated from the path.
99    #[serde(skip_serializing_if = "Option::is_none")]
100    #[builder(default, setter(strip_option))]
101    pub subscription_id: Option<i32>,
102
103    /// Optional. Updated subscription name.
104    #[serde(skip_serializing_if = "Option::is_none")]
105    #[builder(default, setter(strip_option, into))]
106    pub name: Option<String>,
107
108    /// 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.
109    #[serde(skip_serializing_if = "Option::is_none")]
110    #[builder(default, setter(strip_option))]
111    pub plan_id: Option<i32>,
112
113    /// Optional. The payment method for the subscription. If set to 'credit-card' , 'paymentMethodId' must be defined.
114    #[serde(skip_serializing_if = "Option::is_none")]
115    #[builder(default, setter(strip_option, into))]
116    pub payment_method: Option<String>,
117
118    /// 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'.
119    #[serde(skip_serializing_if = "Option::is_none")]
120    #[builder(default, setter(strip_option))]
121    pub payment_method_id: Option<i32>,
122
123    /// Read-only on the response; populated by the server with the
124    /// operation type (e.g. `"UPDATE_FIXED_SUBSCRIPTION"`).
125    #[serde(skip_serializing_if = "Option::is_none")]
126    #[builder(default, setter(strip_option, into))]
127    pub command_type: Option<String>,
128}
129
130/// Redis Essentials subscription plan information
131#[derive(Debug, Clone, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct FixedSubscriptionsPlan {
134    /// Plan identifier.
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub id: Option<i32>,
137
138    /// Plan name.
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub name: Option<String>,
141
142    /// Total memory size of the plan in the plan's measurement unit.
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub size: Option<f64>,
145
146    /// Dataset size of the plan in the plan's measurement unit.
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub dataset_size: Option<f64>,
149
150    /// Measurement unit for `size`/`dataset_size` (e.g. `"GB"`, `"MB"`).
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub size_measurement_unit: Option<String>,
153
154    /// Cloud provider (e.g. `"AWS"`, `"GCP"`, `"Azure"`).
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub provider: Option<String>,
157
158    /// Cloud region for the plan.
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub region: Option<String>,
161
162    /// Region identifier.
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub region_id: Option<i32>,
165
166    /// Plan price in the plan's currency.
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub price: Option<i32>,
169
170    /// ISO currency code for the plan price (e.g. `"USD"`).
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub price_currency: Option<String>,
173
174    /// Billing period for the plan price (e.g. `"Month"`, `"Hour"`).
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub price_period: Option<String>,
177
178    /// Maximum number of databases allowed under this plan.
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub maximum_databases: Option<i32>,
181
182    /// Maximum throughput (ops/sec) allowed under this plan.
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub maximum_throughput: Option<i32>,
185
186    /// Maximum monthly bandwidth, in GB.
187    ///
188    /// Wire field is `maximumBandwidthGB` (capital GB); `rename_all = "camelCase"`
189    /// would produce `maximumBandwidthGb` and drop the value (#108 casing pitfall).
190    #[serde(rename = "maximumBandwidthGB", skip_serializing_if = "Option::is_none")]
191    pub maximum_bandwidth_gb: Option<i32>,
192
193    /// Availability tier (e.g. `"Single-zone"`, `"Multi-zone"`).
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub availability: Option<String>,
196
197    /// Connection limit description for this plan.
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub connections: Option<String>,
200
201    /// Number of CIDR allow rules supported by this plan.
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub cidr_allow_rules: Option<i32>,
204
205    /// Whether the plan supports data persistence.
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub support_data_persistence: Option<bool>,
208
209    /// Whether the plan supports Redis Flex (auto-tiering).
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub redis_flex: Option<bool>,
212
213    /// Whether the plan supports instant and daily backups.
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub support_instant_and_daily_backups: Option<bool>,
216
217    /// Whether the plan supports replication.
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub support_replication: Option<bool>,
220
221    /// Whether the plan supports clustering.
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub support_clustering: Option<bool>,
224
225    /// Whether the plan supports SSL/TLS connections.
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub support_ssl: Option<bool>,
228
229    /// List of supported alert types for this plan
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub supported_alerts: Option<Vec<String>>,
232
233    /// Customer support tier included with this plan.
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub customer_support: Option<String>,
236
237    /// HATEOAS links
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub links: Option<Vec<Link>>,
240}
241
242/// Essentials subscription create request
243///
244/// # Example
245///
246/// ```
247/// use redis_cloud::fixed::subscriptions::FixedSubscriptionCreateRequest;
248///
249/// let request = FixedSubscriptionCreateRequest::builder()
250///     .name("my-subscription")
251///     .plan_id(123)
252///     .build();
253/// ```
254#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)]
255#[serde(rename_all = "camelCase")]
256pub struct FixedSubscriptionCreateRequest {
257    /// New Essentials subscription name.
258    #[builder(setter(into))]
259    pub name: String,
260
261    /// 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.
262    pub plan_id: i32,
263
264    /// Optional. The payment method for the subscription. If set to 'credit-card', 'paymentMethodId' must be defined. Default: 'credit-card'
265    #[serde(skip_serializing_if = "Option::is_none")]
266    #[builder(default, setter(strip_option, into))]
267    pub payment_method: Option<String>,
268
269    /// 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.
270    #[serde(skip_serializing_if = "Option::is_none")]
271    #[builder(default, setter(strip_option))]
272    pub payment_method_id: Option<i32>,
273
274    /// Read-only on the response; populated by the server with the
275    /// operation type (e.g. `"CREATE_FIXED_SUBSCRIPTION"`).
276    #[serde(skip_serializing_if = "Option::is_none")]
277    #[builder(default, setter(strip_option, into))]
278    pub command_type: Option<String>,
279}
280
281/// Redis list of Essentials subscriptions in current account
282#[derive(Debug, Clone, Serialize, Deserialize)]
283#[serde(rename_all = "camelCase")]
284pub struct FixedSubscriptions {
285    /// Account identifier owning these subscriptions.
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub account_id: Option<i32>,
288
289    /// List of Essentials subscriptions
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub subscriptions: Option<Vec<FixedSubscription>>,
292
293    /// HATEOAS links
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub links: Option<Vec<Link>>,
296}
297
298/// `RedisVersion`
299#[derive(Debug, Clone, Serialize, Deserialize)]
300#[serde(rename_all = "camelCase")]
301pub struct RedisVersion {
302    /// Redis version string (e.g. `"7.2"`).
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub version: Option<String>,
305
306    /// End-of-life date for this Redis version.
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub eol_date: Option<String>,
309
310    /// Whether this Redis version is a preview/early-access release.
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub is_preview: Option<bool>,
313
314    /// Whether this Redis version is the default for new databases.
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub is_default: Option<bool>,
317}
318
319/// Redis Essentials Subscription information
320#[derive(Debug, Clone, Serialize, Deserialize)]
321#[serde(rename_all = "camelCase")]
322pub struct FixedSubscription {
323    /// Subscription identifier.
324    #[serde(skip_serializing_if = "Option::is_none")]
325    pub id: Option<i32>,
326
327    /// Subscription name.
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub name: Option<String>,
330
331    /// Current subscription status (e.g. `"active"`, `"pending"`).
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub status: Option<String>,
334
335    /// Payment method identifier for this subscription.
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub payment_method_id: Option<i32>,
338
339    /// Payment method type (e.g. `"credit-card"`, `"marketplace"`).
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub payment_method_type: Option<String>,
342
343    /// Identifier of the Essentials plan for this subscription.
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub plan_id: Option<i32>,
346
347    /// Name of the Essentials plan for this subscription.
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub plan_name: Option<String>,
350
351    /// Plan type (e.g. `"single-region"`).
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub plan_type: Option<String>,
354
355    /// Plan size in the plan's measurement unit.
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub size: Option<f64>,
358
359    /// Measurement unit for `size` (e.g. `"GB"`, `"MB"`).
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub size_measurement_unit: Option<String>,
362
363    /// Cloud provider (e.g. `"AWS"`, `"GCP"`, `"Azure"`).
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub provider: Option<String>,
366
367    /// Cloud region for the subscription.
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub region: Option<String>,
370
371    /// Subscription price in the configured currency.
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub price: Option<i32>,
374
375    /// Billing period for the subscription price (e.g. `"Month"`).
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub price_period: Option<String>,
378
379    /// ISO currency code for the subscription price (e.g. `"USD"`).
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub price_currency: Option<String>,
382
383    /// Maximum number of databases allowed under this subscription.
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub maximum_databases: Option<i32>,
386
387    /// Availability tier (e.g. `"Single-zone"`, `"Multi-zone"`).
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub availability: Option<String>,
390
391    /// Connection limit description.
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub connections: Option<String>,
394
395    /// Number of CIDR allow rules supported by this subscription.
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub cidr_allow_rules: Option<i32>,
398
399    /// Whether data persistence is supported.
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub support_data_persistence: Option<bool>,
402
403    /// Whether instant and daily backups are supported.
404    #[serde(skip_serializing_if = "Option::is_none")]
405    pub support_instant_and_daily_backups: Option<bool>,
406
407    /// Whether replication is supported.
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub support_replication: Option<bool>,
410
411    /// Whether clustering is supported.
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub support_clustering: Option<bool>,
414
415    /// Customer support tier included with this subscription.
416    #[serde(skip_serializing_if = "Option::is_none")]
417    pub customer_support: Option<String>,
418
419    /// Timestamp when the subscription was created.
420    #[serde(skip_serializing_if = "Option::is_none")]
421    pub creation_date: Option<String>,
422
423    /// Aggregate status of databases in this subscription.
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub database_status: Option<String>,
426
427    /// HATEOAS links
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub links: Option<Vec<Link>>,
430}
431
432// ============================================================================
433// Handler
434// ============================================================================
435
436/// Handler for Essentials subscription operations
437///
438/// Manages fixed-capacity subscriptions with pre-defined plans,
439/// simplified pricing, and streamlined configuration options.
440pub struct FixedSubscriptionHandler {
441    client: CloudClient,
442}
443
444impl FixedSubscriptionHandler {
445    /// Create a new handler
446    #[must_use]
447    pub fn new(client: CloudClient) -> Self {
448        Self { client }
449    }
450
451    /// Get Essentials plans
452    /// 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.
453    ///
454    /// GET /fixed/plans
455    pub async fn list_plans(
456        &self,
457        provider: Option<String>,
458        redis_flex: Option<bool>,
459    ) -> Result<FixedSubscriptionsPlans> {
460        let mut query = Vec::new();
461        if let Some(v) = provider {
462            query.push(format!("provider={v}"));
463        }
464        if let Some(v) = redis_flex {
465            query.push(format!("redisFlex={v}"));
466        }
467        let query_string = if query.is_empty() {
468            String::new()
469        } else {
470            format!("?{}", query.join("&"))
471        };
472        self.client
473            .get(&format!("/fixed/plans{query_string}"))
474            .await
475    }
476
477    /// Get Essentials plans for a subscription
478    /// Gets a list of compatible Essentials plans for the specified Essentials subscription.
479    ///
480    /// GET /fixed/plans/subscriptions/{subscriptionId}
481    pub async fn get_plans_by_subscription_id(
482        &self,
483        subscription_id: i32,
484    ) -> Result<FixedSubscriptionsPlans> {
485        self.client
486            .get(&format!("/fixed/plans/subscriptions/{subscription_id}"))
487            .await
488    }
489
490    /// Get a single Essentials plan
491    /// Gets information on the specified Essentials plan.
492    ///
493    /// GET /fixed/plans/{planId}
494    pub async fn get_plan_by_id(&self, plan_id: i32) -> Result<FixedSubscriptionsPlan> {
495        self.client.get(&format!("/fixed/plans/{plan_id}")).await
496    }
497
498    /// Get available Redis database versions for specific Essentials subscription
499    /// Gets a list of all available Redis database versions for a specific Essentials subscription.
500    ///
501    /// GET /fixed/redis-versions
502    pub async fn get_redis_versions(&self, subscription_id: i32) -> Result<RedisVersions> {
503        let mut query = Vec::new();
504        query.push(format!("subscriptionId={subscription_id}"));
505        let query_string = if query.is_empty() {
506            String::new()
507        } else {
508            format!("?{}", query.join("&"))
509        };
510        self.client
511            .get(&format!("/fixed/redis-versions{query_string}"))
512            .await
513    }
514
515    /// Get Essentials subscriptions
516    /// Gets a list of all Essentials subscriptions in the current account.
517    ///
518    /// GET /fixed/subscriptions
519    pub async fn list(&self) -> Result<FixedSubscriptions> {
520        self.client.get("/fixed/subscriptions").await
521    }
522
523    /// Create Essentials subscription
524    /// Creates a new Essentials subscription.
525    ///
526    /// POST /fixed/subscriptions
527    pub async fn create(
528        &self,
529        request: &FixedSubscriptionCreateRequest,
530    ) -> Result<TaskStateUpdate> {
531        self.client.post("/fixed/subscriptions", request).await
532    }
533
534    /// Delete Essentials subscription
535    /// Deletes the specified Essentials subscription. All databases in the subscription must be deleted before deleting it.
536    ///
537    /// DELETE /fixed/subscriptions/{subscriptionId}
538    pub async fn delete_by_id(&self, subscription_id: i32) -> Result<TaskStateUpdate> {
539        let response = self
540            .client
541            .delete_raw(&format!("/fixed/subscriptions/{subscription_id}"))
542            .await?;
543        serde_json::from_value(response).map_err(Into::into)
544    }
545
546    /// Get a single Essentials subscription
547    /// Gets information on the specified Essentials subscription.
548    ///
549    /// GET /fixed/subscriptions/{subscriptionId}
550    pub async fn get_by_id(&self, subscription_id: i32) -> Result<FixedSubscription> {
551        self.client
552            .get(&format!("/fixed/subscriptions/{subscription_id}"))
553            .await
554    }
555
556    /// Update Essentials subscription
557    /// Updates the specified Essentials subscription.
558    ///
559    /// PUT /fixed/subscriptions/{subscriptionId}
560    pub async fn update(
561        &self,
562        subscription_id: i32,
563        request: &FixedSubscriptionUpdateRequest,
564    ) -> Result<TaskStateUpdate> {
565        self.client
566            .put(&format!("/fixed/subscriptions/{subscription_id}"), request)
567            .await
568    }
569}