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}