Skip to main content

namecheap_client/
users.rs

1//! The `namecheap.users` command namespace.
2
3use serde::Deserialize;
4
5use crate::client::Client;
6use crate::error::Result;
7
8/// Accessor for `namecheap.users.*` commands, returned by
9/// [`Client::users`](crate::Client::users).
10#[derive(Debug, Clone, Copy)]
11pub struct Users<'a> {
12    client: &'a Client,
13}
14
15impl<'a> Users<'a> {
16    pub(crate) fn new(client: &'a Client) -> Self {
17        Self { client }
18    }
19
20    /// Returns the account balances (`namecheap.users.getBalances`).
21    ///
22    /// # Example
23    ///
24    /// ```no_run
25    /// # async fn run(client: namecheap_client::Client) -> Result<(), namecheap_client::Error> {
26    /// let balances = client.users().get_balances().await?;
27    /// println!("{} {}", balances.available_balance, balances.currency);
28    /// # Ok(())
29    /// # }
30    /// ```
31    ///
32    /// # Errors
33    ///
34    /// Returns an [`Error`](crate::Error) on transport failure, an API error
35    /// response, or a decode failure.
36    pub async fn get_balances(&self) -> Result<Balances> {
37        let payload: BalancesPayload = self
38            .client
39            .send("namecheap.users.getBalances", Vec::new())
40            .await?;
41        Ok(payload.result)
42    }
43}
44
45#[derive(Debug, Deserialize)]
46struct BalancesPayload {
47    #[serde(rename = "UserGetBalancesResult")]
48    result: Balances,
49}
50
51/// Account balances returned by [`Users::get_balances`].
52///
53/// Amounts are parsed into [`f64`]. Because the API returns them as decimal
54/// strings, treat these values as display figures rather than as inputs to
55/// exact financial arithmetic.
56#[derive(Debug, Clone, Deserialize)]
57#[non_exhaustive]
58pub struct Balances {
59    /// The account currency (for example `"USD"`).
60    #[serde(rename = "@Currency")]
61    pub currency: String,
62    /// The balance available to spend.
63    #[serde(rename = "@AvailableBalance")]
64    pub available_balance: f64,
65    /// The total account balance.
66    #[serde(rename = "@AccountBalance")]
67    pub account_balance: f64,
68    /// The amount earned (for reseller/affiliate accounts).
69    #[serde(rename = "@EarnedAmount")]
70    pub earned_amount: f64,
71    /// The amount that can currently be withdrawn.
72    #[serde(rename = "@WithdrawableAmount")]
73    pub withdrawable_amount: f64,
74    /// The funds required to cover upcoming auto-renewals.
75    #[serde(rename = "@FundsRequiredForAutoRenew")]
76    pub funds_required_for_auto_renew: f64,
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use reqwest::StatusCode;
83
84    #[test]
85    fn parses_balances_response() {
86        let body = r#"<?xml version="1.0" encoding="utf-8"?>
87        <ApiResponse Status="OK" xmlns="http://api.namecheap.com/xml.response">
88          <Errors />
89          <CommandResponse Type="namecheap.users.getBalances">
90            <UserGetBalancesResult Currency="USD" AvailableBalance="4932.96" AccountBalance="4932.96" EarnedAmount="381.30" WithdrawableAmount="1500.00" FundsRequiredForAutoRenew="0.00" />
91          </CommandResponse>
92        </ApiResponse>"#;
93
94        let payload: BalancesPayload = crate::response::parse(StatusCode::OK, body).unwrap();
95        let balances = payload.result;
96        assert_eq!(balances.currency, "USD");
97        assert_eq!(balances.available_balance, 4932.96);
98        assert_eq!(balances.earned_amount, 381.30);
99        assert_eq!(balances.funds_required_for_auto_renew, 0.0);
100    }
101}