up_api/v1/
accounts.rs

1use crate::v1::{Client, error, BASE_URL, standard};
2
3use serde::Deserialize;
4
5// ----------------- Response Objects -----------------
6
7#[derive(Deserialize, Debug)]
8pub struct ListAccountsResponse {
9    /// The list of accounts returned in this response.
10    pub data : Vec<AccountResource>,
11    pub links : ResponseLinks,
12}
13
14#[derive(Deserialize, Debug)]
15pub struct GetAccountResponse {
16    /// The account returned in this response.
17    pub data : AccountResource,
18}
19
20#[derive(Deserialize, Debug)]
21pub struct AccountResource {
22    /// The type of this resource: `accounts`.
23    pub r#type : String,
24    /// The unique identifier for this account.
25    pub id : String,
26    pub attributes : Attributes,
27    pub relationships : Relationships,
28    pub links : Option<AccountResourceLinks>,
29}
30
31#[derive(Deserialize, Debug)]
32pub struct AccountResourceLinks {
33    /// The canonical link to this resource within the API.
34    #[serde(rename = "self")]
35    pub this : Option<String>,
36}
37
38#[derive(Deserialize, Debug)]
39pub struct ResponseLinks {
40    /// The link to the previous page in the results. If this value is `None` there is no previous page.
41    pub prev : Option<String>,
42    /// The link to the next page in the results. If this value is `None` there is no next page.
43    pub next : Option<String>,
44}
45
46#[derive(Deserialize, Debug)]
47pub struct Relationships {
48    pub transactions : Transactions,
49}
50
51#[derive(Deserialize, Debug)]
52pub struct Transactions {
53    pub links : Option<TransactionLinks>,
54}
55
56#[derive(Deserialize, Debug)]
57pub struct TransactionLinks {
58    /// The link to retrieve the related resource(s) in this relationship.
59    pub related : String,
60}
61
62#[derive(Deserialize, Debug)]
63#[serde(rename_all = "camelCase")]
64pub struct Attributes {
65    /// The name associated with the account in the Up application.
66    pub display_name : String,
67    /// The bank account type of this account. Possible values: SAVER, TRANSACTIONAL
68    pub account_type : String,
69    /// The ownership structure for this account. Possible values: INDIVIDUAL, JOINT
70    pub ownership_type : String,
71    /// The available balance of the account, taking into account any amounts that are currently on hold.
72    pub balance : standard::MoneyObject,
73    /// The date-time at which this account was first opened.
74    pub created_at : String,
75}
76
77// ----------------- Input Objects -----------------
78
79#[derive(Default)]
80pub struct ListAccountsOptions {
81    /// The number of records to return in each page. 
82    page_size : Option<u8>,
83    /// The type of account for which to return records. This can be used to filter Savers from spending accounts.
84    filter_account_type : Option<String>,
85    /// The account ownership structure for which to return records. This can be used to filter 2Up accounts from Up accounts.
86    filter_ownership_type : Option<String>,
87}
88
89impl ListAccountsOptions {
90    /// Sets the page size.
91    pub fn page_size(&mut self, value : u8) {
92        self.page_size = Some(value);
93    }
94
95    /// Sets the account type filter value.
96    pub fn filter_account_type(&mut self, value : String) {
97        self.filter_account_type = Some(value);
98    }
99
100    /// Sets the ownership type filter value.
101    pub fn filter_ownership_type(&mut self, value : String) {
102        self.filter_ownership_type = Some(value);
103    }
104
105    fn add_params(&self, url : &mut reqwest::Url) {
106        let mut query = String::new();
107
108        if let Some(value) = &self.page_size {
109            if !query.is_empty() {
110                query.push('&');
111            }
112            query.push_str(&format!("page[size]={}", value));
113        }
114
115        if let Some(value) = &self.filter_account_type {
116            if !query.is_empty() {
117                query.push('&');
118            }
119            query.push_str(&format!("filter[accountType]={}", urlencoding::encode(value)));
120        }
121
122        if let Some(value) = &self.filter_ownership_type {
123            if !query.is_empty() {
124                query.push('&');
125            }
126            query.push_str(&format!("filter[ownershipType]={}", urlencoding::encode(value)));
127        }
128
129        if !query.is_empty() {
130            url.set_query(Some(&query));
131        }
132    }
133}
134
135impl Client {
136    /// Retrieve a paginated list of all accounts for the currently authenticated user. The returned list is paginated and can be scrolled by following the `prev` and `next` links where present. 
137    pub async fn list_accounts(&self, options : &ListAccountsOptions) -> Result<ListAccountsResponse, error::Error> {
138        let mut url = reqwest::Url::parse(&format!("{}/accounts", BASE_URL)).map_err(error::Error::UrlParse)?;
139        options.add_params(&mut url);
140
141        let res = reqwest::Client::new()
142            .get(url)
143            .header("Authorization", self.auth_header())
144            .send()
145            .await
146            .map_err(error::Error::Request)?;
147
148        match res.status() {
149            reqwest::StatusCode::OK => {
150                let body = res.text().await.map_err(error::Error::BodyRead)?;
151                let account_response : ListAccountsResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
152
153                Ok(account_response)
154            },
155            _ => {
156                let body = res.text().await.map_err(error::Error::BodyRead)?;
157                let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
158
159                Err(error::Error::Api(error))
160            }
161        }
162    }
163
164    /// Retrieve a specific account by providing its unique identifier.
165    pub async fn get_account(&self, id : &str) -> Result<GetAccountResponse, error::Error> {
166        // This assertion is because without an ID the request is thought to be a request for
167        // many accounts, and therefore the error messages are very unclear.
168        if id.is_empty() {
169            panic!("The provided account ID must not be empty.");
170        }
171
172        let url = reqwest::Url::parse(&format!("{}/accounts/{}", BASE_URL, id)).map_err(error::Error::UrlParse)?;
173
174        let res = reqwest::Client::new()
175            .get(url)
176            .header("Authorization", self.auth_header())
177            .send()
178            .await
179            .map_err(error::Error::Request)?;
180
181        match res.status() {
182            reqwest::StatusCode::OK => {
183                let body = res.text().await.map_err(error::Error::BodyRead)?;
184                let account_response : GetAccountResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
185
186                Ok(account_response)
187            },
188            _ => {
189                let body = res.text().await.map_err(error::Error::BodyRead)?;
190                let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
191
192                Err(error::Error::Api(error))
193            }
194        }
195    }
196}
197
198// ----------------- Page Navigation -----------------
199
200implement_pagination_v1!(ListAccountsResponse);