Skip to main content

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/// Payment method information
241#[derive(Debug, Clone, Serialize, Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct PaymentMethod {
244    /// Payment method ID
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub id: Option<i32>,
247
248    /// Card type (e.g., "Mastercard", "Visa")
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub r#type: Option<String>,
251
252    /// Last digits of the credit card as a masked string.
253    ///
254    /// Typed as `Option<String>` because the OpenAPI example shows a string
255    /// value and real responses can include leading zeros (`"0042"`) or
256    /// non-numeric formatting that would be lost or fail to parse as `i32`.
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub credit_card_ends_with: Option<String>,
259
260    /// Name on the card
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub name_on_card: Option<String>,
263
264    /// Expiration month (1-12)
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub expiration_month: Option<i32>,
267
268    /// Expiration year
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub expiration_year: Option<i32>,
271
272    /// HATEOAS links
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub links: Option<Vec<Link>>,
275}
276
277/// Database module/capability information
278#[derive(Debug, Clone, Serialize, Deserialize)]
279#[serde(rename_all = "camelCase")]
280pub struct Module {
281    /// Module name (e.g., "`RedisJSON`", "`RediSearch`")
282    #[serde(skip_serializing_if = "Option::is_none")]
283    pub name: Option<String>,
284
285    /// Capability name (e.g., "JSON", "Search and query")
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub capability_name: Option<String>,
288
289    /// Module description
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub description: Option<String>,
292
293    /// Module parameters configuration
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub parameters: Option<Vec<ModuleParameter>>,
296}
297
298/// Module parameter configuration
299#[derive(Debug, Clone, Serialize, Deserialize)]
300#[serde(rename_all = "camelCase")]
301pub struct ModuleParameter {
302    /// Parameter name
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub name: Option<String>,
305
306    /// Parameter description
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub description: Option<String>,
309
310    /// Parameter type (e.g., "integer")
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub r#type: Option<String>,
313
314    /// Default value for the parameter
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub default_value: Option<i64>,
317
318    /// Whether this parameter is required
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub required: Option<bool>,
321}
322
323/// Account system log entries response
324#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct AccountSystemLogEntries {
326    /// System log entries returned by the server.
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub entries: Option<Vec<AccountSystemLogEntry>>,
329
330    /// HATEOAS links
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub links: Option<Vec<Link>>,
333}
334
335/// Query performance factors (search scaling) response
336#[derive(Debug, Clone, Serialize, Deserialize)]
337#[serde(rename_all = "camelCase")]
338pub struct SearchScalingFactorsData {
339    /// Available query performance factors (e.g., "Standard", "2x", "4x")
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub query_performance_factors: Option<Vec<String>>,
342
343    /// HATEOAS links
344    #[serde(skip_serializing_if = "Option::is_none")]
345    pub links: Option<Vec<Link>>,
346}
347
348/// Account session log entry
349#[derive(Debug, Clone, Serialize, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct AccountSessionLogEntry {
352    /// Session log entry ID (UUID)
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub id: Option<String>,
355
356    /// Timestamp of the session event
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub time: Option<String>,
359
360    /// User who performed the action
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub user: Option<String>,
363
364    /// User agent string
365    #[serde(skip_serializing_if = "Option::is_none")]
366    pub user_agent: Option<String>,
367
368    /// IP address of the session
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub ip_address: Option<String>,
371
372    /// User role (e.g., "owner")
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub user_role: Option<String>,
375
376    /// Session type (e.g., "sso")
377    #[serde(skip_serializing_if = "Option::is_none")]
378    pub r#type: Option<String>,
379
380    /// Action performed (e.g., "Successful login", "Successful logout")
381    #[serde(skip_serializing_if = "Option::is_none")]
382    pub action: Option<String>,
383}
384
385/// Data persistence option entry
386#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct DataPersistenceEntry {
388    /// Persistence option name (e.g., "none", "aof-every-1-second")
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub name: Option<String>,
391
392    /// Human-readable description
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub description: Option<String>,
395}
396
397/// Data persistence options response
398#[derive(Debug, Clone, Serialize, Deserialize)]
399#[serde(rename_all = "camelCase")]
400pub struct DataPersistenceOptions {
401    /// Available data persistence options
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub data_persistence: Option<Vec<DataPersistenceEntry>>,
404
405    /// HATEOAS links
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub links: Option<Vec<Link>>,
408}
409
410/// Account session log entries response
411#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct AccountSessionLogEntries {
413    /// Session log entries returned by the server.
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub entries: Option<Vec<AccountSessionLogEntry>>,
416
417    /// HATEOAS links
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub links: Option<Vec<Link>>,
420}
421
422// ============================================================================
423// Handler
424// ============================================================================
425
426/// Account operations handler
427/// Handler for account management operations
428///
429/// Provides methods for managing account information, API keys, owners,
430/// payment methods, SSO/SAML configuration, and billing addresses.
431pub struct AccountHandler {
432    client: CloudClient,
433}
434
435impl AccountHandler {
436    /// Create a new handler
437    #[must_use]
438    pub fn new(client: CloudClient) -> Self {
439        Self { client }
440    }
441
442    /// Get current account
443    /// Gets information on this account.
444    ///
445    /// GET /
446    ///
447    /// # Example
448    ///
449    /// ```no_run
450    /// use redis_cloud::CloudClient;
451    ///
452    /// # async fn example() -> redis_cloud::Result<()> {
453    /// let client = CloudClient::builder()
454    ///     .api_key("your-api-key")
455    ///     .api_secret("your-api-secret")
456    ///     .build()?;
457    ///
458    /// let root = client.account().get_current_account().await?;
459    /// if let Some(account) = &root.account {
460    ///     println!("Account ID: {:?}", account.id);
461    /// }
462    /// # Ok(())
463    /// # }
464    /// ```
465    pub async fn get_current_account(&self) -> Result<RootAccount> {
466        self.client.get("/").await
467    }
468
469    /// Get data persistence options
470    /// Gets a list of all [data persistence](https://redis.io/docs/latest/operate/rc/databases/configuration/data-persistence/) options for this account.
471    ///
472    /// GET /data-persistence
473    pub async fn get_data_persistence_options(&self) -> Result<DataPersistenceOptions> {
474        self.client.get("/data-persistence").await
475    }
476
477    /// Get advanced capabilities
478    /// 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.
479    ///
480    /// GET /database-modules
481    pub async fn get_supported_database_modules(&self) -> Result<ModulesData> {
482        self.client.get("/database-modules").await
483    }
484
485    /// Get system logs
486    /// Gets [system logs](https://redis.io/docs/latest/operate/rc/api/examples/audit-system-logs/) for this account.
487    ///
488    /// GET /logs
489    pub async fn get_account_system_logs(
490        &self,
491        offset: Option<i32>,
492        limit: Option<i32>,
493    ) -> Result<AccountSystemLogEntries> {
494        let mut query = Vec::new();
495        if let Some(v) = offset {
496            query.push(format!("offset={v}"));
497        }
498        if let Some(v) = limit {
499            query.push(format!("limit={v}"));
500        }
501        let query_string = if query.is_empty() {
502            String::new()
503        } else {
504            format!("?{}", query.join("&"))
505        };
506        self.client.get(&format!("/logs{query_string}")).await
507    }
508
509    /// Get payment methods
510    /// Gets a list of all payment methods for this account.
511    ///
512    /// GET /payment-methods
513    pub async fn get_account_payment_methods(&self) -> Result<PaymentMethods> {
514        self.client.get("/payment-methods").await
515    }
516
517    /// Get query performance factors
518    /// Gets a list of available [query performance factors](https://redis.io/docs/latest/operate/rc/databases/configuration/advanced-capabilities/#query-performance-factor).
519    ///
520    /// GET /query-performance-factors
521    pub async fn get_supported_search_scaling_factors(&self) -> Result<SearchScalingFactorsData> {
522        self.client.get("/query-performance-factors").await
523    }
524
525    /// Get available Pro plan regions
526    /// Gets a list of available regions for Pro subscriptions. For Essentials subscriptions, use 'GET /fixed/plans'.
527    ///
528    /// GET /regions
529    pub async fn get_supported_regions(&self, provider: Option<String>) -> Result<Regions> {
530        let mut query = Vec::new();
531        if let Some(v) = provider {
532            query.push(format!("provider={v}"));
533        }
534        let query_string = if query.is_empty() {
535            String::new()
536        } else {
537            format!("?{}", query.join("&"))
538        };
539        self.client.get(&format!("/regions{query_string}")).await
540    }
541
542    /// Get session logs
543    /// Gets session logs for this account.
544    ///
545    /// GET /session-logs
546    pub async fn get_account_session_logs(
547        &self,
548        offset: Option<i32>,
549        limit: Option<i32>,
550    ) -> Result<AccountSessionLogEntries> {
551        let mut query = Vec::new();
552        if let Some(v) = offset {
553            query.push(format!("offset={v}"));
554        }
555        if let Some(v) = limit {
556            query.push(format!("limit={v}"));
557        }
558        let query_string = if query.is_empty() {
559            String::new()
560        } else {
561            format!("?{}", query.join("&"))
562        };
563        self.client
564            .get(&format!("/session-logs{query_string}"))
565            .await
566    }
567
568    // ============================================================================
569    // Simplified aliases
570    // ============================================================================
571
572    /// Get the current account (simplified)
573    ///
574    /// Alias for [`get_current_account`](Self::get_current_account).
575    ///
576    /// # Example
577    ///
578    /// ```no_run
579    /// use redis_cloud::CloudClient;
580    ///
581    /// # async fn example() -> redis_cloud::Result<()> {
582    /// let client = CloudClient::builder()
583    ///     .api_key("your-api-key")
584    ///     .api_secret("your-api-secret")
585    ///     .build()?;
586    ///
587    /// let root = client.account().get().await?;
588    /// # Ok(())
589    /// # }
590    /// ```
591    pub async fn get(&self) -> Result<RootAccount> {
592        self.get_current_account().await
593    }
594
595    /// Get system logs (simplified)
596    ///
597    /// Alias for [`get_account_system_logs`](Self::get_account_system_logs).
598    ///
599    /// # Arguments
600    ///
601    /// * `offset` - Optional pagination offset
602    /// * `limit` - Optional page size limit
603    ///
604    /// # Example
605    ///
606    /// ```no_run
607    /// use redis_cloud::CloudClient;
608    ///
609    /// # async fn example() -> redis_cloud::Result<()> {
610    /// let client = CloudClient::builder()
611    ///     .api_key("your-api-key")
612    ///     .api_secret("your-api-secret")
613    ///     .build()?;
614    ///
615    /// let logs = client.account().system_logs(None, None).await?;
616    /// # Ok(())
617    /// # }
618    /// ```
619    pub async fn system_logs(
620        &self,
621        offset: Option<i32>,
622        limit: Option<i32>,
623    ) -> Result<AccountSystemLogEntries> {
624        self.get_account_system_logs(offset, limit).await
625    }
626
627    /// Get session logs (simplified)
628    ///
629    /// Alias for [`get_account_session_logs`](Self::get_account_session_logs).
630    ///
631    /// # Arguments
632    ///
633    /// * `offset` - Optional pagination offset
634    /// * `limit` - Optional page size limit
635    ///
636    /// # Example
637    ///
638    /// ```no_run
639    /// use redis_cloud::CloudClient;
640    ///
641    /// # async fn example() -> redis_cloud::Result<()> {
642    /// let client = CloudClient::builder()
643    ///     .api_key("your-api-key")
644    ///     .api_secret("your-api-secret")
645    ///     .build()?;
646    ///
647    /// let logs = client.account().session_logs(None, None).await?;
648    /// # Ok(())
649    /// # }
650    /// ```
651    pub async fn session_logs(
652        &self,
653        offset: Option<i32>,
654        limit: Option<i32>,
655    ) -> Result<AccountSessionLogEntries> {
656        self.get_account_session_logs(offset, limit).await
657    }
658
659    /// Get payment methods (simplified)
660    ///
661    /// Alias for [`get_account_payment_methods`](Self::get_account_payment_methods).
662    ///
663    /// # Example
664    ///
665    /// ```no_run
666    /// use redis_cloud::CloudClient;
667    ///
668    /// # async fn example() -> redis_cloud::Result<()> {
669    /// let client = CloudClient::builder()
670    ///     .api_key("your-api-key")
671    ///     .api_secret("your-api-secret")
672    ///     .build()?;
673    ///
674    /// let methods = client.account().payment_methods().await?;
675    /// # Ok(())
676    /// # }
677    /// ```
678    pub async fn payment_methods(&self) -> Result<PaymentMethods> {
679        self.get_account_payment_methods().await
680    }
681}