coinbase_v3/
accounts.rs

1//! Structures & Enums to store Coinbase's Accounts
2
3use bigdecimal::BigDecimal;
4use serde_derive::Deserialize;
5use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str};
6use uuid::Uuid;
7
8use crate::DateTime;
9
10/// Possible types for Coinbase's accounts.
11#[derive(Deserialize_enum_str, Serialize_enum_str, Debug, PartialEq, Eq)]
12#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
13pub enum AccountType {
14    AccountTypeUnspecified,
15    AccountTypeCrypto,
16    AccountTypeFiat,
17    AccountTypeVault,
18}
19
20/// Structure to deserialize Coinbase's accounts.
21#[derive(Deserialize, Debug, PartialEq, Eq)]
22pub struct Account {
23    pub uuid: Uuid,
24    pub name: String,
25    pub currency: String,
26    pub available_balance: Balance,
27    pub default: bool,
28    pub active: bool,
29    pub created_at: Option<DateTime>,
30    pub updated_at: Option<DateTime>,
31    pub deleted_at: Option<DateTime>,
32    pub r#type: AccountType,
33    pub ready: bool,
34    pub hold: Balance,
35}
36
37/// Structure to deserialize balances stored in a Coinbase's account.
38#[derive(Deserialize, Debug, PartialEq, Eq)]
39pub struct Balance {
40    /// Not store as an `f64` as number of decimals might be currency dependant and arbitrary
41    pub value: BigDecimal,
42    pub currency: String,
43}
44
45/// Structure to deserialize CB's response to a request for multiple accounts.
46///
47/// Calls to this [Client][`crate::client::CbClient`]'s API will not return this type. It will unpack the
48/// inner `accounts` and return it.
49///
50/// `has_next` and `cursor` are used for pagination.
51#[derive(Deserialize, Debug)]
52pub struct AccountsResponse {
53    pub accounts: Vec<Account>,
54    pub has_next: bool,
55    pub cursor: String,
56    pub size: i32, // i32 as per api reference
57}
58
59/// Structure to deserialize CB's response to a request for a single account.
60///
61/// Calls to this [Client][`crate::client::CbClient`]'s API will not return this type. It will unpack the
62/// inner `accounts` and return it.
63#[derive(Deserialize, Debug)]
64pub struct AccountResponse {
65    pub account: Account,
66}
67
68//=========== TESTS ===========================================================
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use bigdecimal::ToPrimitive;
74
75    #[test]
76    fn test_account_deserialize() {
77        let input = r##"[
78      {
79        "uuid": "9dd482e4-d8ce-46f7-a261-281843bd2855",
80        "name": "SOL Wallet",
81        "currency": "SOL",
82        "available_balance": { "value": "70.313593992", "currency": "SOL" },
83        "default": true,
84        "active": true,
85        "created_at": "2023-06-07T17:30:40.425Z",
86        "deleted_at": null,
87        "type": "ACCOUNT_TYPE_CRYPTO",
88        "ready": true,
89        "hold": { "value": "0", "currency": "SOL" }
90      }
91]"##;
92
93        let accounts: Vec<Account> = serde_json::from_slice(input.as_bytes()).unwrap();
94        assert_eq!(accounts.len(), 1);
95        assert_eq!(
96            accounts[0].uuid.to_string(),
97            "9dd482e4-d8ce-46f7-a261-281843bd2855"
98        );
99    }
100
101    #[test]
102    fn test_balance_deserialize() {
103        let input = r##"{ "value": "70.313593992", "currency": "SOL" }"##;
104        let balance: Balance = serde_json::from_slice(input.as_bytes()).unwrap();
105        assert!((balance.value.to_f64().unwrap() - 70.313593992f64).abs() < 0.000000001);
106    }
107
108    #[test]
109    fn test_account_type_deserialize() {
110        let input = r##""ACCOUNT_TYPE_UNSPECIFIED""##;
111        let result: AccountType = serde_json::from_slice(input.as_bytes()).unwrap();
112        assert_eq!(result, AccountType::AccountTypeUnspecified);
113
114        let input = r##""ACCOUNT_TYPE_CRYPTO""##;
115        let result: AccountType = serde_json::from_slice(input.as_bytes()).unwrap();
116        assert_eq!(result, AccountType::AccountTypeCrypto);
117
118        let input = r##""ACCOUNT_TYPE_FIAT""##;
119        let result: AccountType = serde_json::from_slice(input.as_bytes()).unwrap();
120        assert_eq!(result, AccountType::AccountTypeFiat);
121
122        let input = r##""ACCOUNT_TYPE_VAULT""##;
123        let result: AccountType = serde_json::from_slice(input.as_bytes()).unwrap();
124        assert_eq!(result, AccountType::AccountTypeVault);
125    }
126
127    #[test]
128    fn test_account_type_serialize() {
129        let expected = r##""ACCOUNT_TYPE_CRYPTO""##;
130        assert_eq!(
131            expected,
132            serde_json::to_string(&AccountType::AccountTypeCrypto).unwrap()
133        );
134    }
135}