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}