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