redis_cloud/account.rs
1//! Account management operations and models
2//!
3//! This module provides comprehensive account management functionality for Redis Cloud,
4//! including account information retrieval, settings management, API keys, owners,
5//! payment methods, SSO/SAML configuration, and billing address management.
6//!
7//! # Overview
8//!
9//! The account module is the central point for managing organization-wide settings and
10//! configurations in Redis Cloud. It handles everything from basic account information
11//! to advanced features like SSO integration and API key management.
12//!
13//! # Key Features
14//!
15//! - **Account Information**: Get current account details and metadata
16//! - **API Key Management**: Create, list, and manage API keys for programmatic access
17//! - **Owner Management**: Manage account owners and their permissions
18//! - **Payment Methods**: Handle payment methods and billing configuration
19//! - **SSO/SAML**: Configure single sign-on and SAML integration
20//! - **Billing Address**: Manage billing address information
21//!
22//! # Example Usage
23//!
24//! ```no_run
25//! use redis_cloud::{CloudClient, AccountHandler};
26//!
27//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
28//! let client = CloudClient::builder()
29//! .api_key("your-api-key")
30//! .api_secret("your-api-secret")
31//! .build()?;
32//!
33//! let handler = AccountHandler::new(client);
34//!
35//! // Get current account information
36//! let account = handler.get_current_account().await?;
37//! println!("Account info: {:?}", account);
38//!
39//! // Get payment methods
40//! let payment_methods = handler.get_account_payment_methods().await?;
41//! println!("Payment methods: {:?}", payment_methods);
42//! # Ok(())
43//! # }
44//! ```
45
46use crate::types::Link;
47use crate::{CloudClient, Result};
48use serde::{Deserialize, Serialize};
49
50// ============================================================================
51// Models
52// ============================================================================
53
54/// Database modules/capabilities response
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct ModulesData {
57 /// Database modules supported on this account.
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub modules: Option<Vec<Module>>,
60
61 /// HATEOAS links
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub links: Option<Vec<Link>>,
64}
65
66/// Root account response from GET /
67#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct RootAccount {
70 /// Account information
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub account: Option<Account>,
73
74 /// HATEOAS links
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub links: Option<Vec<Link>>,
77}
78
79/// Account information
80#[derive(Debug, Clone, Serialize, Deserialize)]
81#[serde(rename_all = "camelCase")]
82pub struct Account {
83 /// Account ID
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub id: Option<i32>,
86
87 /// Account name
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub name: Option<String>,
90
91 /// Timestamp when the account was created
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub created_timestamp: Option<String>,
94
95 /// Timestamp when the account was last updated
96 #[serde(skip_serializing_if = "Option::is_none")]
97 pub updated_timestamp: Option<String>,
98
99 /// Marketplace status (e.g., "active", "deleted")
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub marketplace_status: Option<String>,
102
103 /// API key information used for this request
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub key: Option<AccountApiKeyInfo>,
106}
107
108/// API key information returned in account response
109#[derive(Debug, Clone, Serialize, Deserialize)]
110#[serde(rename_all = "camelCase")]
111pub struct AccountApiKeyInfo {
112 /// API key name
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub name: Option<String>,
115
116 /// Account ID this key belongs to
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub account_id: Option<i32>,
119
120 /// Account name
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub account_name: Option<String>,
123
124 /// Allowed source IP addresses/CIDRs
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub allowed_source_ips: Option<Vec<String>>,
127
128 /// Owner information
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub owner: Option<AccountApiKeyOwner>,
131
132 /// User account ID
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub user_account_id: Option<i32>,
135
136 /// HTTP source IP of the current request
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub http_source_ip: Option<String>,
139
140 /// Account marketplace ID
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub account_marketplace_id: Option<String>,
143}
144
145/// API key owner information
146#[derive(Debug, Clone, Serialize, Deserialize)]
147#[serde(rename_all = "camelCase")]
148pub struct AccountApiKeyOwner {
149 /// Owner's name
150 #[serde(skip_serializing_if = "Option::is_none")]
151 pub name: Option<String>,
152
153 /// Owner's email
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub email: Option<String>,
156}
157
158/// Account system log entry
159#[derive(Debug, Clone, Serialize, Deserialize)]
160#[serde(rename_all = "camelCase")]
161pub struct AccountSystemLogEntry {
162 /// Unique log entry ID.
163 #[serde(skip_serializing_if = "Option::is_none")]
164 pub id: Option<i32>,
165
166 /// Timestamp the event was recorded (ISO-8601).
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub time: Option<String>,
169
170 /// Originator of the event (user, system, etc.).
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub originator: Option<String>,
173
174 /// Name of the API key that initiated the action, if any.
175 #[serde(skip_serializing_if = "Option::is_none")]
176 pub api_key_name: Option<String>,
177
178 /// Resource category the event applies to (e.g. `"database"`).
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub resource: Option<String>,
181
182 /// Resource ID associated with this log entry
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub resource_id: Option<i32>,
185
186 /// Event type (e.g. `"info"`, `"warning"`, `"error"`).
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub r#type: Option<String>,
189
190 /// Human-readable description of the event.
191 #[serde(skip_serializing_if = "Option::is_none")]
192 pub description: Option<String>,
193}
194
195/// Available regions response
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct Regions {
198 /// Regions available on the account.
199 #[serde(skip_serializing_if = "Option::is_none")]
200 pub regions: Option<Vec<Region>>,
201
202 /// HATEOAS links
203 #[serde(skip_serializing_if = "Option::is_none")]
204 pub links: Option<Vec<Link>>,
205}
206
207/// Region information
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct Region {
210 /// Region ID
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub id: Option<i32>,
213
214 /// Region name (e.g., "us-east-1")
215 #[serde(skip_serializing_if = "Option::is_none")]
216 pub name: Option<String>,
217
218 /// Cloud provider (e.g., "AWS", "GCP", "Azure")
219 #[serde(skip_serializing_if = "Option::is_none")]
220 pub provider: Option<String>,
221}
222
223/// Account payment methods response
224#[derive(Debug, Clone, Serialize, Deserialize)]
225#[serde(rename_all = "camelCase")]
226pub struct PaymentMethods {
227 /// Account ID the payment methods belong to.
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub account_id: Option<i32>,
230
231 /// List of payment methods
232 #[serde(skip_serializing_if = "Option::is_none")]
233 pub payment_methods: Option<Vec<PaymentMethod>>,
234
235 /// HATEOAS links
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub links: Option<Vec<Link>>,
238}
239
240/// Deserialize a field the API may send as either a JSON number or a string
241/// into an `Option<String>`.
242///
243/// Used for `creditCardEndsWith`, which the OpenAPI schema documents as a
244/// string but the live API returns as a number (see #120). A `null` or absent
245/// field yields `None`; a number is stringified; a string is kept verbatim.
246fn deserialize_opt_string_or_number<'de, D>(
247 deserializer: D,
248) -> std::result::Result<Option<String>, D::Error>
249where
250 D: serde::Deserializer<'de>,
251{
252 match serde_json::Value::deserialize(deserializer)? {
253 serde_json::Value::Null => Ok(None),
254 serde_json::Value::String(s) => Ok(Some(s)),
255 serde_json::Value::Number(n) => Ok(Some(n.to_string())),
256 other => Err(serde::de::Error::custom(format!(
257 "expected a string or number for creditCardEndsWith, got {other}"
258 ))),
259 }
260}
261
262/// Payment method information
263#[derive(Debug, Clone, Serialize, Deserialize)]
264#[serde(rename_all = "camelCase")]
265pub struct PaymentMethod {
266 /// Payment method ID
267 #[serde(skip_serializing_if = "Option::is_none")]
268 pub id: Option<i32>,
269
270 /// Card type (e.g., "Mastercard", "Visa")
271 #[serde(skip_serializing_if = "Option::is_none")]
272 pub r#type: Option<String>,
273
274 /// Last digits of the credit card.
275 ///
276 /// Kept as `Option<String>`, but deserialized with a number-or-string
277 /// helper: the OpenAPI schema documents a string, yet the live API returns
278 /// `creditCardEndsWith` as a JSON number. Accepting both keeps the public
279 /// type stable while tolerating the real response (a plain `String` failed
280 /// to deserialize — see #120). A string is preserved as-is, so a value with
281 /// leading zeros (`"0042"`) is not lost if the API ever sends one.
282 #[serde(
283 default,
284 deserialize_with = "deserialize_opt_string_or_number",
285 skip_serializing_if = "Option::is_none"
286 )]
287 pub credit_card_ends_with: Option<String>,
288
289 /// Name on the card
290 #[serde(skip_serializing_if = "Option::is_none")]
291 pub name_on_card: Option<String>,
292
293 /// Expiration month (1-12)
294 #[serde(skip_serializing_if = "Option::is_none")]
295 pub expiration_month: Option<i32>,
296
297 /// Expiration year
298 #[serde(skip_serializing_if = "Option::is_none")]
299 pub expiration_year: Option<i32>,
300
301 /// HATEOAS links
302 #[serde(skip_serializing_if = "Option::is_none")]
303 pub links: Option<Vec<Link>>,
304}
305
306/// Database module/capability information
307#[derive(Debug, Clone, Serialize, Deserialize)]
308#[serde(rename_all = "camelCase")]
309pub struct Module {
310 /// Module name (e.g., "`RedisJSON`", "`RediSearch`")
311 #[serde(skip_serializing_if = "Option::is_none")]
312 pub name: Option<String>,
313
314 /// Capability name (e.g., "JSON", "Search and query")
315 #[serde(skip_serializing_if = "Option::is_none")]
316 pub capability_name: Option<String>,
317
318 /// Module description
319 #[serde(skip_serializing_if = "Option::is_none")]
320 pub description: Option<String>,
321
322 /// Module parameters configuration
323 #[serde(skip_serializing_if = "Option::is_none")]
324 pub parameters: Option<Vec<ModuleParameter>>,
325}
326
327/// Module parameter configuration
328#[derive(Debug, Clone, Serialize, Deserialize)]
329#[serde(rename_all = "camelCase")]
330pub struct ModuleParameter {
331 /// Parameter name
332 #[serde(skip_serializing_if = "Option::is_none")]
333 pub name: Option<String>,
334
335 /// Parameter description
336 #[serde(skip_serializing_if = "Option::is_none")]
337 pub description: Option<String>,
338
339 /// Parameter type (e.g., "integer")
340 #[serde(skip_serializing_if = "Option::is_none")]
341 pub r#type: Option<String>,
342
343 /// Default value for the parameter
344 #[serde(skip_serializing_if = "Option::is_none")]
345 pub default_value: Option<i64>,
346
347 /// Whether this parameter is required
348 #[serde(skip_serializing_if = "Option::is_none")]
349 pub required: Option<bool>,
350}
351
352/// Account system log entries response
353#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct AccountSystemLogEntries {
355 /// System log entries returned by the server.
356 #[serde(skip_serializing_if = "Option::is_none")]
357 pub entries: Option<Vec<AccountSystemLogEntry>>,
358
359 /// HATEOAS links
360 #[serde(skip_serializing_if = "Option::is_none")]
361 pub links: Option<Vec<Link>>,
362}
363
364/// Query performance factors (search scaling) response
365#[derive(Debug, Clone, Serialize, Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct SearchScalingFactorsData {
368 /// Available query performance factors (e.g., "Standard", "2x", "4x")
369 #[serde(skip_serializing_if = "Option::is_none")]
370 pub query_performance_factors: Option<Vec<String>>,
371
372 /// HATEOAS links
373 #[serde(skip_serializing_if = "Option::is_none")]
374 pub links: Option<Vec<Link>>,
375}
376
377/// Account session log entry
378#[derive(Debug, Clone, Serialize, Deserialize)]
379#[serde(rename_all = "camelCase")]
380pub struct AccountSessionLogEntry {
381 /// Session log entry ID (UUID)
382 #[serde(skip_serializing_if = "Option::is_none")]
383 pub id: Option<String>,
384
385 /// Timestamp of the session event
386 #[serde(skip_serializing_if = "Option::is_none")]
387 pub time: Option<String>,
388
389 /// User who performed the action
390 #[serde(skip_serializing_if = "Option::is_none")]
391 pub user: Option<String>,
392
393 /// User agent string
394 #[serde(skip_serializing_if = "Option::is_none")]
395 pub user_agent: Option<String>,
396
397 /// IP address of the session
398 #[serde(skip_serializing_if = "Option::is_none")]
399 pub ip_address: Option<String>,
400
401 /// User role (e.g., "owner")
402 #[serde(skip_serializing_if = "Option::is_none")]
403 pub user_role: Option<String>,
404
405 /// Session type (e.g., "sso")
406 #[serde(skip_serializing_if = "Option::is_none")]
407 pub r#type: Option<String>,
408
409 /// Action performed (e.g., "Successful login", "Successful logout")
410 #[serde(skip_serializing_if = "Option::is_none")]
411 pub action: Option<String>,
412}
413
414/// Data persistence option entry
415#[derive(Debug, Clone, Serialize, Deserialize)]
416pub struct DataPersistenceEntry {
417 /// Persistence option name (e.g., "none", "aof-every-1-second")
418 #[serde(skip_serializing_if = "Option::is_none")]
419 pub name: Option<String>,
420
421 /// Human-readable description
422 #[serde(skip_serializing_if = "Option::is_none")]
423 pub description: Option<String>,
424}
425
426/// Data persistence options response
427#[derive(Debug, Clone, Serialize, Deserialize)]
428#[serde(rename_all = "camelCase")]
429pub struct DataPersistenceOptions {
430 /// Available data persistence options
431 #[serde(skip_serializing_if = "Option::is_none")]
432 pub data_persistence: Option<Vec<DataPersistenceEntry>>,
433
434 /// HATEOAS links
435 #[serde(skip_serializing_if = "Option::is_none")]
436 pub links: Option<Vec<Link>>,
437}
438
439/// Account session log entries response
440#[derive(Debug, Clone, Serialize, Deserialize)]
441pub struct AccountSessionLogEntries {
442 /// Session log entries returned by the server.
443 #[serde(skip_serializing_if = "Option::is_none")]
444 pub entries: Option<Vec<AccountSessionLogEntry>>,
445
446 /// HATEOAS links
447 #[serde(skip_serializing_if = "Option::is_none")]
448 pub links: Option<Vec<Link>>,
449}
450
451// ============================================================================
452// Handler
453// ============================================================================
454
455/// Account operations handler
456/// Handler for account management operations
457///
458/// Provides methods for managing account information, API keys, owners,
459/// payment methods, SSO/SAML configuration, and billing addresses.
460pub struct AccountHandler {
461 client: CloudClient,
462}
463
464impl AccountHandler {
465 /// Create a new handler
466 #[must_use]
467 pub fn new(client: CloudClient) -> Self {
468 Self { client }
469 }
470
471 /// Get current account
472 /// Gets information on this account.
473 ///
474 /// GET /
475 ///
476 /// # Example
477 ///
478 /// ```no_run
479 /// use redis_cloud::CloudClient;
480 ///
481 /// # async fn example() -> redis_cloud::Result<()> {
482 /// let client = CloudClient::builder()
483 /// .api_key("your-api-key")
484 /// .api_secret("your-api-secret")
485 /// .build()?;
486 ///
487 /// let root = client.account().get_current_account().await?;
488 /// if let Some(account) = &root.account {
489 /// println!("Account ID: {:?}", account.id);
490 /// }
491 /// # Ok(())
492 /// # }
493 /// ```
494 pub async fn get_current_account(&self) -> Result<RootAccount> {
495 self.client.get("/").await
496 }
497
498 /// Get data persistence options
499 /// Gets a list of all [data persistence](https://redis.io/docs/latest/operate/rc/databases/configuration/data-persistence/) options for this account.
500 ///
501 /// GET /data-persistence
502 pub async fn get_data_persistence_options(&self) -> Result<DataPersistenceOptions> {
503 self.client.get("/data-persistence").await
504 }
505
506 /// Get advanced capabilities
507 /// Gets a list of Redis [advanced capabilities](https://redis.io/docs/latest/operate/rc/databases/configuration/advanced-capabilities/) (also known as modules) available for this account. Advanced capability support may differ based on subscription and database settings.
508 ///
509 /// GET /database-modules
510 pub async fn get_supported_database_modules(&self) -> Result<ModulesData> {
511 self.client.get("/database-modules").await
512 }
513
514 /// Get system logs
515 /// Gets [system logs](https://redis.io/docs/latest/operate/rc/api/examples/audit-system-logs/) for this account.
516 ///
517 /// GET /logs
518 pub async fn get_account_system_logs(
519 &self,
520 offset: Option<i32>,
521 limit: Option<i32>,
522 ) -> Result<AccountSystemLogEntries> {
523 let mut query = Vec::new();
524 if let Some(v) = offset {
525 query.push(format!("offset={v}"));
526 }
527 if let Some(v) = limit {
528 query.push(format!("limit={v}"));
529 }
530 let query_string = if query.is_empty() {
531 String::new()
532 } else {
533 format!("?{}", query.join("&"))
534 };
535 self.client.get(&format!("/logs{query_string}")).await
536 }
537
538 /// Get payment methods
539 /// Gets a list of all payment methods for this account.
540 ///
541 /// GET /payment-methods
542 pub async fn get_account_payment_methods(&self) -> Result<PaymentMethods> {
543 self.client.get("/payment-methods").await
544 }
545
546 /// Get query performance factors
547 /// Gets a list of available [query performance factors](https://redis.io/docs/latest/operate/rc/databases/configuration/advanced-capabilities/#query-performance-factor).
548 ///
549 /// GET /query-performance-factors
550 pub async fn get_supported_search_scaling_factors(&self) -> Result<SearchScalingFactorsData> {
551 self.client.get("/query-performance-factors").await
552 }
553
554 /// Get available Pro plan regions
555 /// Gets a list of available regions for Pro subscriptions. For Essentials subscriptions, use 'GET /fixed/plans'.
556 ///
557 /// GET /regions
558 pub async fn get_supported_regions(&self, provider: Option<String>) -> Result<Regions> {
559 let mut query = Vec::new();
560 if let Some(v) = provider {
561 query.push(format!("provider={v}"));
562 }
563 let query_string = if query.is_empty() {
564 String::new()
565 } else {
566 format!("?{}", query.join("&"))
567 };
568 self.client.get(&format!("/regions{query_string}")).await
569 }
570
571 /// Get session logs
572 /// Gets session logs for this account.
573 ///
574 /// GET /session-logs
575 pub async fn get_account_session_logs(
576 &self,
577 offset: Option<i32>,
578 limit: Option<i32>,
579 ) -> Result<AccountSessionLogEntries> {
580 let mut query = Vec::new();
581 if let Some(v) = offset {
582 query.push(format!("offset={v}"));
583 }
584 if let Some(v) = limit {
585 query.push(format!("limit={v}"));
586 }
587 let query_string = if query.is_empty() {
588 String::new()
589 } else {
590 format!("?{}", query.join("&"))
591 };
592 self.client
593 .get(&format!("/session-logs{query_string}"))
594 .await
595 }
596
597 // ============================================================================
598 // Simplified aliases
599 // ============================================================================
600
601 /// Get the current account (simplified)
602 ///
603 /// Alias for [`get_current_account`](Self::get_current_account).
604 ///
605 /// # Example
606 ///
607 /// ```no_run
608 /// use redis_cloud::CloudClient;
609 ///
610 /// # async fn example() -> redis_cloud::Result<()> {
611 /// let client = CloudClient::builder()
612 /// .api_key("your-api-key")
613 /// .api_secret("your-api-secret")
614 /// .build()?;
615 ///
616 /// let root = client.account().get().await?;
617 /// # Ok(())
618 /// # }
619 /// ```
620 pub async fn get(&self) -> Result<RootAccount> {
621 self.get_current_account().await
622 }
623
624 /// Get system logs (simplified)
625 ///
626 /// Alias for [`get_account_system_logs`](Self::get_account_system_logs).
627 ///
628 /// # Arguments
629 ///
630 /// * `offset` - Optional pagination offset
631 /// * `limit` - Optional page size limit
632 ///
633 /// # Example
634 ///
635 /// ```no_run
636 /// use redis_cloud::CloudClient;
637 ///
638 /// # async fn example() -> redis_cloud::Result<()> {
639 /// let client = CloudClient::builder()
640 /// .api_key("your-api-key")
641 /// .api_secret("your-api-secret")
642 /// .build()?;
643 ///
644 /// let logs = client.account().system_logs(None, None).await?;
645 /// # Ok(())
646 /// # }
647 /// ```
648 pub async fn system_logs(
649 &self,
650 offset: Option<i32>,
651 limit: Option<i32>,
652 ) -> Result<AccountSystemLogEntries> {
653 self.get_account_system_logs(offset, limit).await
654 }
655
656 /// Get session logs (simplified)
657 ///
658 /// Alias for [`get_account_session_logs`](Self::get_account_session_logs).
659 ///
660 /// # Arguments
661 ///
662 /// * `offset` - Optional pagination offset
663 /// * `limit` - Optional page size limit
664 ///
665 /// # Example
666 ///
667 /// ```no_run
668 /// use redis_cloud::CloudClient;
669 ///
670 /// # async fn example() -> redis_cloud::Result<()> {
671 /// let client = CloudClient::builder()
672 /// .api_key("your-api-key")
673 /// .api_secret("your-api-secret")
674 /// .build()?;
675 ///
676 /// let logs = client.account().session_logs(None, None).await?;
677 /// # Ok(())
678 /// # }
679 /// ```
680 pub async fn session_logs(
681 &self,
682 offset: Option<i32>,
683 limit: Option<i32>,
684 ) -> Result<AccountSessionLogEntries> {
685 self.get_account_session_logs(offset, limit).await
686 }
687
688 /// Get payment methods (simplified)
689 ///
690 /// Alias for [`get_account_payment_methods`](Self::get_account_payment_methods).
691 ///
692 /// # Example
693 ///
694 /// ```no_run
695 /// use redis_cloud::CloudClient;
696 ///
697 /// # async fn example() -> redis_cloud::Result<()> {
698 /// let client = CloudClient::builder()
699 /// .api_key("your-api-key")
700 /// .api_secret("your-api-secret")
701 /// .build()?;
702 ///
703 /// let methods = client.account().payment_methods().await?;
704 /// # Ok(())
705 /// # }
706 /// ```
707 pub async fn payment_methods(&self) -> Result<PaymentMethods> {
708 self.get_account_payment_methods().await
709 }
710}