0.1.0
This commit is contained in:
parent
134dd74c31
commit
700a48e56c
@ -2,10 +2,10 @@
|
|||||||
name = "up-api"
|
name = "up-api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
#description = "A convenient and easy to use wrapper for the Up Bank API."
|
description = "A convenient and easy to use wrapper for the Up Bank API."
|
||||||
#license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
#authors = ["Aaron Manning"]
|
authors = ["Aaron Manning"]
|
||||||
#repository = "https://github.com/aaron-jack-manning/up-api"
|
repository = "https://github.com/aaron-jack-manning/up-api"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
reqwest = "0.11.11"
|
reqwest = "0.11.11"
|
||||||
|
19
README.md
19
README.md
@ -4,30 +4,37 @@ A convenient and easy to use wrapper for the [Up Bank API](https://developer.up.
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
The following example shows the calculation of the sum of all transactions after a given date.
|
The following example shows the calculation of the sum of all transactions after a given date (up to the page limit).
|
||||||
|
|
||||||
```
|
```
|
||||||
use up_api::Client;
|
use up_api::v1::Client;
|
||||||
use up_api::transactions::ListTransactionsOptions;
|
use up_api::v1::transactions::ListTransactionsOptions;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let token = std::env::var("UP_ACCESS_TOKEN").unwrap();
|
let token = std::env::var("UP_ACCESS_TOKEN").unwrap();
|
||||||
|
|
||||||
let client = Client::new(token.to_string());
|
let client = Client::new(token.to_string());
|
||||||
|
|
||||||
let mut options = ListTransactionsOptions::default();
|
let mut options = ListTransactionsOptions::default();
|
||||||
options.filter_since("2020-01-01T01:02:03+10:00".to_string());
|
options.filter_since("2020-01-01T01:02:03Z".to_string());
|
||||||
|
options.page_size(100);
|
||||||
|
|
||||||
let transactions = client.list_transactions().unwrap();
|
let transactions = client.list_transactions(&options).await.unwrap();
|
||||||
|
|
||||||
let total : f32 =
|
let total : f32 =
|
||||||
transactions
|
transactions
|
||||||
.data
|
.data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|t| t.attributes.amount.value)
|
.map(|t| t.attributes.amount.value)
|
||||||
.map(|v| v.parse::<f32>())
|
.map(|v| v.parse::<f32>().unwrap())
|
||||||
|
.filter(|a| a > &0.0)
|
||||||
.sum();
|
.sum();
|
||||||
|
|
||||||
println!("{}", total);
|
println!("{}", total);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Planned Features
|
||||||
|
|
||||||
|
Currently this API wrapper supports all of the `v1` Up API endpoints except [webhooks](https://developer.up.com.au/#webhooks). This is planned for a (hopefull soon) future release.
|
||||||
|
35
src/lib.rs
35
src/lib.rs
@ -1,8 +1,39 @@
|
|||||||
//! # Up API
|
//! # Up API
|
||||||
//!
|
//!
|
||||||
//! A convenient and easy to use wrapper for the [Up Bank API](https://developer.up.com.au).
|
//! A convenient and easy to use wrapper for the [Up Bank API](https://developer.up.com.au).
|
||||||
|
//!
|
||||||
// Include examples so that this exactly matches the README.
|
//! ## Example
|
||||||
|
//!
|
||||||
|
//! The following example shows the calculation of the sum of all transactions after a given date (up to the page limit).
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use up_api::v1::Client;
|
||||||
|
//! use up_api::v1::transactions::ListTransactionsOptions;
|
||||||
|
//!
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! async fn main() {
|
||||||
|
//! let token = std::env::var("UP_ACCESS_TOKEN").unwrap();
|
||||||
|
//!
|
||||||
|
//! let client = Client::new(token.to_string());
|
||||||
|
//!
|
||||||
|
//! let mut options = ListTransactionsOptions::default();
|
||||||
|
//! options.filter_since("2020-01-01T01:02:03Z".to_string());
|
||||||
|
//! options.page_size(100);
|
||||||
|
//!
|
||||||
|
//! let transactions = client.list_transactions(&options).await.unwrap();
|
||||||
|
//!
|
||||||
|
//! let total : f32 =
|
||||||
|
//! transactions
|
||||||
|
//! .data
|
||||||
|
//! .into_iter()
|
||||||
|
//! .map(|t| t.attributes.amount.value)
|
||||||
|
//! .map(|v| v.parse::<f32>().unwrap())
|
||||||
|
//! .filter(|a| a > &0.0)
|
||||||
|
//! .sum();
|
||||||
|
//!
|
||||||
|
//! println!("{}", total);
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
/// Module for interacting with the v1 (beta) release of the Up API.
|
/// Module for interacting with the v1 (beta) release of the Up API.
|
||||||
pub mod v1;
|
pub mod v1;
|
||||||
|
@ -1,24 +1,37 @@
|
|||||||
use crate::v1::{Client, error, BASE_URL};
|
use crate::v1::{Client, error, BASE_URL, standard};
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
// ----------------- Response Objects -----------------
|
||||||
pub struct GetAccountResponse {
|
|
||||||
/// The account returned in this response.
|
|
||||||
pub data : Data,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct ListAccountsResponse {
|
pub struct ListAccountsResponse {
|
||||||
/// The list of accounts returned in this response.
|
/// The list of accounts returned in this response.
|
||||||
pub data : Vec<Data>,
|
pub data : Vec<AccountResource>,
|
||||||
pub links : ResponseLinks,
|
pub links : ResponseLinks,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct DataLinks {
|
pub struct GetAccountResponse {
|
||||||
#[serde(rename = "self")]
|
/// The account returned in this response.
|
||||||
|
pub data : AccountResource,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct AccountResource {
|
||||||
|
/// The type of this resource: `accounts`.
|
||||||
|
pub r#type : String,
|
||||||
|
/// The unique identifier for this account.
|
||||||
|
pub id : String,
|
||||||
|
pub attributes : Attributes,
|
||||||
|
pub relationships : Relationships,
|
||||||
|
pub links : Option<AccountResourceLinks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct AccountResourceLinks {
|
||||||
/// The canonical link to this resource within the API.
|
/// The canonical link to this resource within the API.
|
||||||
|
#[serde(rename = "self")]
|
||||||
pub this : Option<String>,
|
pub this : Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,17 +43,6 @@ pub struct ResponseLinks {
|
|||||||
pub next : Option<String>,
|
pub next : Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct Data {
|
|
||||||
/// The type of this resource: `accounts`.
|
|
||||||
pub r#type : String,
|
|
||||||
/// The unique identifier for this account.
|
|
||||||
pub id : String,
|
|
||||||
pub attributes : Attributes,
|
|
||||||
pub relationships : Relationships,
|
|
||||||
pub links : Option<DataLinks>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct Relationships {
|
pub struct Relationships {
|
||||||
pub transactions : Transactions,
|
pub transactions : Transactions,
|
||||||
@ -58,34 +60,21 @@ pub struct TransactionLinks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Attributes {
|
pub struct Attributes {
|
||||||
#[serde(rename = "displayName")]
|
|
||||||
/// The name associated with the account in the Up application.
|
/// The name associated with the account in the Up application.
|
||||||
pub display_name : String,
|
pub display_name : String,
|
||||||
#[serde(rename = "accountType")]
|
|
||||||
/// The bank account type of this account. Possible values: SAVER, TRANSACTIONAL
|
/// The bank account type of this account. Possible values: SAVER, TRANSACTIONAL
|
||||||
pub account_type : String,
|
pub account_type : String,
|
||||||
#[serde(rename = "ownershipType")]
|
|
||||||
/// The ownership structure for this account. Possible values: INDIVIDUAL, JOINT
|
/// The ownership structure for this account. Possible values: INDIVIDUAL, JOINT
|
||||||
pub ownership_type : String,
|
pub ownership_type : String,
|
||||||
/// The available balance of the account, taking into account any amounts that are currently on hold.
|
/// The available balance of the account, taking into account any amounts that are currently on hold.
|
||||||
pub balance : Balance,
|
pub balance : standard::MoneyObject,
|
||||||
#[serde(rename = "createdAt")]
|
|
||||||
/// The date-time at which this account was first opened.
|
/// The date-time at which this account was first opened.
|
||||||
pub created_at : String,
|
pub created_at : String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
// ----------------- Input Objects -----------------
|
||||||
pub struct Balance {
|
|
||||||
#[serde(rename = "currencyCode")]
|
|
||||||
/// The ISO 4217 currency code.
|
|
||||||
pub currency_code : String,
|
|
||||||
/// The amount of money, formatted as a string in the relevant currency. For example, for an Australian dollar value of $10.56, this field will be `"10.56"`. The currency symbol is not included in the string
|
|
||||||
pub value : String,
|
|
||||||
#[serde(rename = "valueInBaseUnits")]
|
|
||||||
/// The amount of money in the smallest denomination for the currency, as a 64-bit integer. For example, for an Australian dollar value of $10.56, this field will be `1056`.
|
|
||||||
pub value_in_base_units : i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ListAccountsOptions {
|
pub struct ListAccountsOptions {
|
||||||
@ -94,7 +83,7 @@ pub struct ListAccountsOptions {
|
|||||||
/// The type of account for which to return records. This can be used to filter Savers from spending accounts.
|
/// The type of account for which to return records. This can be used to filter Savers from spending accounts.
|
||||||
filter_account_type : Option<String>,
|
filter_account_type : Option<String>,
|
||||||
/// The account ownership structure for which to return records. This can be used to filter 2Up accounts from Up accounts.
|
/// The account ownership structure for which to return records. This can be used to filter 2Up accounts from Up accounts.
|
||||||
filter_owership_type : Option<String>,
|
filter_ownership_type : Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListAccountsOptions {
|
impl ListAccountsOptions {
|
||||||
@ -109,21 +98,36 @@ impl ListAccountsOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the ownership type filter value.
|
/// Sets the ownership type filter value.
|
||||||
pub fn filter_owership_type(&mut self, value : String) {
|
pub fn filter_ownership_type(&mut self, value : String) {
|
||||||
self.filter_owership_type = Some(value);
|
self.filter_ownership_type = Some(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_params(&self, url : &mut reqwest::Url) {
|
fn add_params(&self, url : &mut reqwest::Url) {
|
||||||
|
let mut query = String::new();
|
||||||
|
|
||||||
if let Some(value) = &self.page_size {
|
if let Some(value) = &self.page_size {
|
||||||
url.set_query(Some(&format!("page[size]={}", value)));
|
if !query.is_empty() {
|
||||||
|
query.push('&');
|
||||||
|
}
|
||||||
|
query.push_str(&format!("page[size]={}", value));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = &self.filter_account_type {
|
if let Some(value) = &self.filter_account_type {
|
||||||
url.set_query(Some(&format!("filter[accountType]={}", value)));
|
if !query.is_empty() {
|
||||||
|
query.push('&');
|
||||||
|
}
|
||||||
|
query.push_str(&format!("filter[accountType]={}", value));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(value) = &self.filter_owership_type {
|
if let Some(value) = &self.filter_ownership_type {
|
||||||
url.set_query(Some(&format!("filter[ownershipType]={}", value)));
|
if !query.is_empty() {
|
||||||
|
query.push('&');
|
||||||
|
}
|
||||||
|
query.push_str(&format!("filter[ownershipType]={}", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !query.is_empty() {
|
||||||
|
url.set_query(Some(&query));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,7 +148,6 @@ impl Client {
|
|||||||
match res.status() {
|
match res.status() {
|
||||||
reqwest::StatusCode::OK => {
|
reqwest::StatusCode::OK => {
|
||||||
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
println!("{}", body);
|
|
||||||
let account_response : ListAccountsResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
let account_response : ListAccountsResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
Ok(account_response)
|
Ok(account_response)
|
||||||
@ -178,7 +181,6 @@ impl Client {
|
|||||||
match res.status() {
|
match res.status() {
|
||||||
reqwest::StatusCode::OK => {
|
reqwest::StatusCode::OK => {
|
||||||
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
println!("{}", body);
|
|
||||||
let account_response : GetAccountResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
let account_response : GetAccountResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
Ok(account_response)
|
Ok(account_response)
|
||||||
|
@ -0,0 +1,235 @@
|
|||||||
|
use crate::v1::{Client, error, BASE_URL};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
// ----------------- Response Objects -----------------
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ListCategoriesResponse {
|
||||||
|
/// The list of categories returned in this response.
|
||||||
|
pub data : Vec<CategoryResource>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct GetCategoryResponse {
|
||||||
|
/// The category returned in this response.
|
||||||
|
pub data : CategoryResource,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct CategoryResource {
|
||||||
|
/// The type of this resource: categories
|
||||||
|
pub r#type : String,
|
||||||
|
/// The unique identifier for this category. This is a human-readable but URL-safe value.
|
||||||
|
pub id : String,
|
||||||
|
pub attributes : Attributes,
|
||||||
|
pub relationships : Relationships,
|
||||||
|
pub links : Option<CategoryResourceLinks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Attributes {
|
||||||
|
/// The name of this category as seen in the Up application.
|
||||||
|
pub name : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Relationships {
|
||||||
|
pub parent : Parent,
|
||||||
|
pub children : Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Parent {
|
||||||
|
pub data : Option<ParentData>,
|
||||||
|
pub links : Option<ParentLinks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ParentData {
|
||||||
|
/// The type of this resource: `categories`
|
||||||
|
pub r#type : String,
|
||||||
|
/// The unique identifier of the resource within its type.
|
||||||
|
pub id : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ParentLinks {
|
||||||
|
/// The link to retrieve the related resource(s) in this relationship.
|
||||||
|
pub related : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Children {
|
||||||
|
pub data : Vec<ChildrenData>,
|
||||||
|
pub links : Option<ChildrenLinks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ChildrenData {
|
||||||
|
/// The type of this resource: `categories`
|
||||||
|
pub r#type : String,
|
||||||
|
/// The unique identifier of the resource within its type.
|
||||||
|
pub id : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ChildrenLinks {
|
||||||
|
/// The link to retrieve the related resource(s) in this relationship.
|
||||||
|
pub related : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct CategoryResourceLinks {
|
||||||
|
/// The canonical link to this resource within the API.
|
||||||
|
#[serde(rename = "self")]
|
||||||
|
pub this : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Input Objects -----------------
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ListCategoriesOptions {
|
||||||
|
/// The unique identifier of a parent category for which to return only its children.
|
||||||
|
filter_parent : Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListCategoriesOptions {
|
||||||
|
/// Sets the parent filter value.
|
||||||
|
pub fn filter_parent(&mut self, value : String) {
|
||||||
|
self.filter_parent = Some(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_params(&self, url : &mut reqwest::Url) {
|
||||||
|
let mut query = String::new();
|
||||||
|
|
||||||
|
if let Some(value) = &self.filter_parent {
|
||||||
|
if !query.is_empty() {
|
||||||
|
query.push('&');
|
||||||
|
}
|
||||||
|
query.push_str(&format!("filter[parent]={}", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !query.is_empty() {
|
||||||
|
url.set_query(Some(&query));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Input Objects -----------------
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct CategoriseTransactionRequest {
|
||||||
|
/// The category to set on the transaction. Set this entire key to `null` de-categorize a transaction.
|
||||||
|
data : Option<CategoryInputResourceIdentifier>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct CategoryInputResourceIdentifier {
|
||||||
|
/// The type of this resource: `categories`
|
||||||
|
r#type : String,
|
||||||
|
/// The unique identifier of the category, as returned by the `list_categories` method.
|
||||||
|
id : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
/// Retrieve a list of all categories and their ancestry. The returned list is not paginated.
|
||||||
|
pub async fn list_categories(&self, options : &ListCategoriesOptions) -> Result<ListCategoriesResponse, error::Error> {
|
||||||
|
let mut url = reqwest::Url::parse(&format!("{}/categories", BASE_URL)).map_err(error::Error::UrlParse)?;
|
||||||
|
options.add_params(&mut url);
|
||||||
|
|
||||||
|
let res = reqwest::Client::new()
|
||||||
|
.get(url)
|
||||||
|
.header("Authorization", self.auth_header())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(error::Error::Request)?;
|
||||||
|
|
||||||
|
match res.status() {
|
||||||
|
reqwest::StatusCode::OK => {
|
||||||
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
|
let category_response : ListCategoriesResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
|
Ok(category_response)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
|
let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
|
Err(error::Error::Api(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a specific category by providing its unique identifier.
|
||||||
|
pub async fn get_category(&self, id : &str) -> Result<GetCategoryResponse, error::Error> {
|
||||||
|
// This assertion is because without an ID the request is thought to be a request for
|
||||||
|
// many accounts, and therefore the error messages are very unclear.
|
||||||
|
if id.is_empty() {
|
||||||
|
panic!("The provided account ID must not be empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = reqwest::Url::parse(&format!("{}/categories/{}", BASE_URL, id)).map_err(error::Error::UrlParse)?;
|
||||||
|
|
||||||
|
let res = reqwest::Client::new()
|
||||||
|
.get(url)
|
||||||
|
.header("Authorization", self.auth_header())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(error::Error::Request)?;
|
||||||
|
|
||||||
|
match res.status() {
|
||||||
|
reqwest::StatusCode::OK => {
|
||||||
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
|
let category_response : GetCategoryResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
|
Ok(category_response )
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
|
let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
|
Err(error::Error::Api(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the category associated with a transaction. Only transactions for which `is_categorizable` is set to true support this operation. The `id` is taken from the list exposed on `list_categories` and cannot be one of the top-level (parent) categories. To de-categorize a transaction, set the entire `data` key to `null`. The associated category, along with its request URL is also exposed via the category relationship on the transaction resource returned from `get_transaction`.
|
||||||
|
pub async fn categorise_transaction(&self, transaction_id : &str, category : Option<&str>) -> Result<(), error::Error> {
|
||||||
|
let url = reqwest::Url::parse(&format!("{}/transactions/{}/relationships/category", BASE_URL, transaction_id)).map_err(error::Error::UrlParse)?;
|
||||||
|
|
||||||
|
let category = category.map(|id| {
|
||||||
|
CategoryInputResourceIdentifier {
|
||||||
|
r#type : String::from("categories"),
|
||||||
|
id : String::from(id),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let body = CategoriseTransactionRequest { data : category };
|
||||||
|
let body = serde_json::to_string(&body).map_err(error::Error::Serialize)?;
|
||||||
|
|
||||||
|
println!("{}", body);
|
||||||
|
|
||||||
|
let res = reqwest::Client::new()
|
||||||
|
.patch(url)
|
||||||
|
.header("Authorization", self.auth_header())
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.body(body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(error::Error::Request)?;
|
||||||
|
|
||||||
|
match res.status() {
|
||||||
|
reqwest::StatusCode::NO_CONTENT => {
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
|
let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
|
Err(error::Error::Api(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -40,11 +40,11 @@ impl std::error::Error for Error {}
|
|||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct ErrorResponse {
|
pub struct ErrorResponse {
|
||||||
/// The list of errors returned in this response.
|
/// The list of errors returned in this response.
|
||||||
pub errors : Vec<UpError>,
|
pub errors : Vec<ErrorObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct UpError {
|
pub struct ErrorObject {
|
||||||
/// The HTTP status code associated with this error. The status indicates the broad type of error according to HTTP semantics.
|
/// The HTTP status code associated with this error. The status indicates the broad type of error according to HTTP semantics.
|
||||||
pub status : String,
|
pub status : String,
|
||||||
/// A short description of this error. This should be stable across multiple occurrences of this type of error and typically expands on the reason for the status code.
|
/// A short description of this error. This should be stable across multiple occurrences of this type of error and typically expands on the reason for the status code.
|
||||||
|
@ -2,16 +2,16 @@
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
/// Types for modelling and interacting with [accounts](https://developer.up.com.au/#accounts).
|
/// Types for modelling and interacting with [accounts](https://developer.up.com.au/#accounts).
|
||||||
pub mod accounts;
|
pub mod accounts;
|
||||||
/// INCOMPLETE: Types for modelling and interacting with [categories](https://developer.up.com.au/#categories).
|
/// Types for modelling and interacting with [categories](https://developer.up.com.au/#categories).
|
||||||
pub mod categories;
|
pub mod categories;
|
||||||
/// Types for modelling and interacting with [tags](https://developer.up.com.au/#tags).
|
/// Types for modelling and interacting with [tags](https://developer.up.com.au/#tags).
|
||||||
pub mod tags;
|
pub mod tags;
|
||||||
/// INCOMPLETE: Types for modelling and interacting with [transactions](https://developer.up.com.au/#transactions).
|
/// Types for modelling and interacting with [transactions](https://developer.up.com.au/#transactions).
|
||||||
pub mod transactions;
|
pub mod transactions;
|
||||||
/// Types for modelling and interacting with [utilities](https://developer.up.com.au/#utility_endpoints).
|
/// Types for modelling and interacting with [utilities](https://developer.up.com.au/#utility_endpoints).
|
||||||
pub mod utilities;
|
pub mod utilities;
|
||||||
/// INCOMPLETE: Types for modelling and interacting with [webhooks](https://developer.up.com.au/#webhooks).
|
/// Types which are stardized (and named) across many resources.
|
||||||
pub mod webhooks;
|
pub mod standard;
|
||||||
|
|
||||||
static BASE_URL : &str = "https://api.up.com.au/api/v1";
|
static BASE_URL : &str = "https://api.up.com.au/api/v1";
|
||||||
|
|
||||||
|
82
src/v1/standard.rs
Normal file
82
src/v1/standard.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum AccountTypeEnum {
|
||||||
|
Saver,
|
||||||
|
Transactional,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct MoneyObject {
|
||||||
|
/// The ISO 4217 currency code.
|
||||||
|
pub currency_code : String,
|
||||||
|
/// The amount of money, formatted as a string in the relevant currency. For example, for an Australian dollar value of $10.56, this field will be `"10.56"`. The currency symbol is not included in the string
|
||||||
|
pub value : String,
|
||||||
|
/// The amount of money in the smallest denomination for the currency, as a 64-bit integer. For example, for an Australian dollar value of $10.56, this field will be `1056`.
|
||||||
|
pub value_in_base_units : i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum OwnershipTypeEnum {
|
||||||
|
Individual,
|
||||||
|
Joint,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum TransactionStatusEnum {
|
||||||
|
Held,
|
||||||
|
Settled,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct HoldInfoObject {
|
||||||
|
/// The amount of this transaction while in the `HELD` status, in Australian dollars.
|
||||||
|
pub amount : MoneyObject,
|
||||||
|
/// The foreign currency amount of this transaction while in the `HELD` status. This field will be `null` for domestic transactions. The amount was converted to the AUD amount reflected in the `amount` field.
|
||||||
|
pub foreign_amount : Option<MoneyObject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RoundUpObject {
|
||||||
|
/// The total amount of this Round Up, including any boosts, represented as a negative value.
|
||||||
|
pub amount : MoneyObject,
|
||||||
|
/// The portion of the Round Up `amount` owing to boosted Round Ups, represented as a negative value. If no boost was added to the Round Up this field will be `null`.
|
||||||
|
pub boost_portion : Option<MoneyObject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct CashBackObject {
|
||||||
|
/// A brief description of why this cashback was paid.
|
||||||
|
pub description : String,
|
||||||
|
/// The total amount of cashback paid, represented as a positive value.
|
||||||
|
pub amount : MoneyObject,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum CardPurchaseMethodEnum {
|
||||||
|
BarCode,
|
||||||
|
OCR,
|
||||||
|
CardPin,
|
||||||
|
CardDetails,
|
||||||
|
CardOnFile,
|
||||||
|
#[serde(rename = "ECOMMERCE")]
|
||||||
|
ECommerce,
|
||||||
|
MagneticStripe,
|
||||||
|
Contactless,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CardPurchaseMethodObject {
|
||||||
|
/// The type of card purchase.
|
||||||
|
pub method : CardPurchaseMethodEnum,
|
||||||
|
/// The last four digits of the card used for the purchase, if applicable.
|
||||||
|
pub card_number_suffix : Option<String>,
|
||||||
|
}
|
@ -2,15 +2,17 @@ use crate::v1::{Client, error, BASE_URL};
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
// ----------------- Response Objects -----------------
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct ListTagsResponse {
|
pub struct ListTagsResponse {
|
||||||
/// The list of tags returned in this response.
|
/// The list of tags returned in this response.
|
||||||
pub data : Vec<Data>,
|
pub data : Vec<TagResource>,
|
||||||
pub links : ResponseLinks,
|
pub links : ResponseLinks,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct Data {
|
pub struct TagResource {
|
||||||
/// The type of this resource: `tags`
|
/// The type of this resource: `tags`
|
||||||
pub r#type : String,
|
pub r#type : String,
|
||||||
/// The label of the tag, which also acts as the tag’s unique identifier.
|
/// The label of the tag, which also acts as the tag’s unique identifier.
|
||||||
@ -40,6 +42,8 @@ pub struct ResponseLinks {
|
|||||||
pub next : Option<String>,
|
pub next : Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------- Input Objects -----------------
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ListTagsOptions {
|
pub struct ListTagsOptions {
|
||||||
/// The number of records to return in each page.
|
/// The number of records to return in each page.
|
||||||
@ -53,12 +57,23 @@ impl ListTagsOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_params(&self, url : &mut reqwest::Url) {
|
fn add_params(&self, url : &mut reqwest::Url) {
|
||||||
|
let mut query = String::new();
|
||||||
|
|
||||||
if let Some(value) = &self.page_size {
|
if let Some(value) = &self.page_size {
|
||||||
url.set_query(Some(&format!("page[size]={}", value)));
|
if !query.is_empty() {
|
||||||
|
query.push('&');
|
||||||
|
}
|
||||||
|
query.push_str(&format!("page[size]={}", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !query.is_empty() {
|
||||||
|
url.set_query(Some(&query))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------- Request Objects -----------------
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct TagInputResourceIdentifier {
|
struct TagInputResourceIdentifier {
|
||||||
/// The type of this resource: `tags`
|
/// The type of this resource: `tags`
|
||||||
@ -73,7 +88,6 @@ struct TagRequest {
|
|||||||
data : Vec<TagInputResourceIdentifier>
|
data : Vec<TagInputResourceIdentifier>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
/// Retrieve a list of all tags currently in use. The returned list is paginated and can be scrolled by following the `next` and `prev` links where present. Results are ordered lexicographically. The transactions relationship for each tag exposes a link to get the transactions with the given tag.
|
/// Retrieve a list of all tags currently in use. The returned list is paginated and can be scrolled by following the `next` and `prev` links where present. Results are ordered lexicographically. The transactions relationship for each tag exposes a link to get the transactions with the given tag.
|
||||||
pub async fn list_tags(&self, options : &ListTagsOptions) -> Result<ListTagsResponse, error::Error> {
|
pub async fn list_tags(&self, options : &ListTagsOptions) -> Result<ListTagsResponse, error::Error> {
|
||||||
@ -90,7 +104,6 @@ impl Client {
|
|||||||
match res.status() {
|
match res.status() {
|
||||||
reqwest::StatusCode::OK => {
|
reqwest::StatusCode::OK => {
|
||||||
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
println!("{}", body);
|
|
||||||
let tags_response : ListTagsResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
let tags_response : ListTagsResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
Ok(tags_response)
|
Ok(tags_response)
|
||||||
@ -123,6 +136,7 @@ impl Client {
|
|||||||
let res = reqwest::Client::new()
|
let res = reqwest::Client::new()
|
||||||
.post(url)
|
.post(url)
|
||||||
.header("Authorization", self.auth_header())
|
.header("Authorization", self.auth_header())
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
.body(body)
|
.body(body)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
@ -160,6 +174,7 @@ impl Client {
|
|||||||
let res = reqwest::Client::new()
|
let res = reqwest::Client::new()
|
||||||
.delete(url)
|
.delete(url)
|
||||||
.header("Authorization", self.auth_header())
|
.header("Authorization", self.auth_header())
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
.body(body)
|
.body(body)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
|
@ -0,0 +1,380 @@
|
|||||||
|
use crate::v1::{Client, error, BASE_URL, standard};
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
// ----------------- Response Objects -----------------
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ListTransactionsResponse {
|
||||||
|
/// The list of transactions returned in this response.
|
||||||
|
pub data : Vec<TransactionResource>,
|
||||||
|
pub links : ResponseLinks,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct GetTransactionResponse {
|
||||||
|
/// The transaction returned in this response.
|
||||||
|
pub data : TransactionResource,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct TransactionResource {
|
||||||
|
/// The type of this resource: `transactions`
|
||||||
|
pub r#type : String,
|
||||||
|
/// The unique identifier for this transaction.
|
||||||
|
pub id : String,
|
||||||
|
pub attributes : Attributes,
|
||||||
|
pub relationships : Relationships,
|
||||||
|
pub links : Option<TransactionResourceLinks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct TransactionResourceLinks {
|
||||||
|
/// The canonical link to this resource within the API.
|
||||||
|
#[serde(rename = "self")]
|
||||||
|
pub this : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Relationships {
|
||||||
|
pub account : Account,
|
||||||
|
/// If this transaction is a transfer between accounts, this relationship will contain the account the transaction went to/came from. The `amount` field can be used to determine the direction of the transfer.
|
||||||
|
pub transfer_account : TransferAccount,
|
||||||
|
pub category : Category,
|
||||||
|
pub parent_category : ParentCategory,
|
||||||
|
pub tags : Tags,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Account {
|
||||||
|
pub data : AccountData,
|
||||||
|
pub links : Option<AccountLinks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct AccountData {
|
||||||
|
/// The type of this resource: `accounts`
|
||||||
|
pub r#type : String,
|
||||||
|
/// The unique identifier of the resource within its type.
|
||||||
|
pub id : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct AccountLinks {
|
||||||
|
/// The link to retrieve the related resource(s) in this relationship.
|
||||||
|
pub related : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct TransferAccount {
|
||||||
|
pub data : Option<AccountData>,
|
||||||
|
pub links : Option<AccountLinks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct TransferAccountData {
|
||||||
|
/// The type of this resource: `accounts`
|
||||||
|
pub r#type : String,
|
||||||
|
/// The unique identifier of the resource within its type.
|
||||||
|
pub id : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct TransferAccountLinks {
|
||||||
|
/// The link to retrieve the related resource(s) in this relationship.
|
||||||
|
pub related : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Category {
|
||||||
|
pub data : Option<CategoryData>,
|
||||||
|
pub links : Option<CategoryLinks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct CategoryData {
|
||||||
|
/// The type of this resource: `categories`
|
||||||
|
pub r#type : String,
|
||||||
|
/// The unique identifier of the resource within its type.
|
||||||
|
pub id : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct CategoryLinks {
|
||||||
|
/// The link to retrieve or modify linkage between this resources and the related resource(s) in this relationship.
|
||||||
|
#[serde(rename = "self")]
|
||||||
|
pub this : String,
|
||||||
|
pub related : Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ParentCategory {
|
||||||
|
pub data : Option<ParentCategoryData>,
|
||||||
|
pub links : Option<ParentCategoryLinks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ParentCategoryData {
|
||||||
|
/// The type of this resource: `categories`
|
||||||
|
pub r#type : String,
|
||||||
|
/// The unique identifier of the resource within its type.
|
||||||
|
pub id : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ParentCategoryLinks {
|
||||||
|
/// The link to retrieve the related resource(s) in this relationship.
|
||||||
|
pub related : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Tags {
|
||||||
|
pub data : Vec<TagsData>,
|
||||||
|
pub links : Option<TagsLinks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct TagsData {
|
||||||
|
/// The type of this resource: `tags`
|
||||||
|
pub r#type : String,
|
||||||
|
/// The label of the tag, which also acts as the tag’s unique identifier.
|
||||||
|
pub id : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct TagsLinks {
|
||||||
|
/// The link to retrieve or modify linkage between this resources and the related resource(s) in this relationship.
|
||||||
|
#[serde(rename = "self")]
|
||||||
|
pub this : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Attributes {
|
||||||
|
/// The current processing status of this transaction, according to whether or not this transaction has settled or is still held. Possible values: `HELD`, `SETTLED`
|
||||||
|
pub status : standard::TransactionStatusEnum,
|
||||||
|
/// The original, unprocessed text of the transaction. This is often not a perfect indicator of the actual merchant, but it is useful for reconciliation purposes in some cases.
|
||||||
|
pub raw_text : Option<String>,
|
||||||
|
/// A short description for this transaction. Usually the merchant name for purchases.
|
||||||
|
pub description : String,
|
||||||
|
/// Attached message for this transaction, such as a payment message, or a transfer note.
|
||||||
|
pub message : Option<String>,
|
||||||
|
/// Boolean flag set to true on transactions that support the use of categories.
|
||||||
|
pub is_categorizable : bool,
|
||||||
|
/// If this transaction is currently in the `HELD` status, or was ever in the `HELD` status, the `amount` and `foreignAmount` of the transaction while `HELD`.
|
||||||
|
pub hold_info : Option<standard::HoldInfoObject>,
|
||||||
|
/// Details of how this transaction was rounded-up. If no Round Up was applied this field will be `null`.
|
||||||
|
pub round_up : Option<standard::RoundUpObject>,
|
||||||
|
/// If all or part of this transaction was instantly reimbursed in the form of cashback, details of the reimbursement.
|
||||||
|
pub cashback : Option<standard::CashBackObject>,
|
||||||
|
/// The amount of this transaction in Australian dollars. For transactions that were once `HELD` but are now `SETTLED`, refer to the `holdInfo` field for the original `amount` the transaction was `HELD` at.
|
||||||
|
pub amount : standard::MoneyObject,
|
||||||
|
/// The foreign currency amount of this transaction. This field will be `null` for domestic transactions. The amount was converted to the AUD amount reflected in the `amount` of this transaction. Refer to the `holdInfo` field for the original foreignAmount the transaction was `HELD` at.
|
||||||
|
pub foreign_amount : Option<standard::MoneyObject>,
|
||||||
|
/// Information about the card used for this transaction, if applicable.
|
||||||
|
pub card_purchase_method : Option<standard::CardPurchaseMethodObject>,
|
||||||
|
/// The date-time at which this transaction settled. This field will be `null` for transactions that are currently in the `HELD` status.
|
||||||
|
pub settled_at : Option<String>,
|
||||||
|
/// The date-time at which this transaction was first encountered.
|
||||||
|
pub created_at : String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ResponseLinks {
|
||||||
|
/// The link to the previous page in the results. If this value is null there is no previous page.
|
||||||
|
pub prev : Option<String>,
|
||||||
|
/// The link to the next page in the results. If this value is null there is no next page.
|
||||||
|
pub next : Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Input Objects -----------------
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ListTransactionsOptions {
|
||||||
|
/// The number of records to return in each page.
|
||||||
|
page_size : Option<u8>,
|
||||||
|
/// The transaction status for which to return records. This can be used to filter `HELD` transactions from those that are `SETTLED`.
|
||||||
|
filter_status : Option<String>,
|
||||||
|
/// The start date-time from which to return records, formatted according to rfc-3339. Not to be used for pagination purposes.
|
||||||
|
filter_since : Option<String>,
|
||||||
|
/// The end date-time up to which to return records, formatted according to rfc-3339. Not to be used for pagination purposes.
|
||||||
|
filter_until : Option<String>,
|
||||||
|
/// The category identifier for which to filter transactions. Both parent and child categories can be filtered through this parameter.
|
||||||
|
filter_category : Option<String>,
|
||||||
|
/// A transaction tag to filter for which to return records. If the tag does not exist, zero records are returned and a success response is given.
|
||||||
|
filter_tag : Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListTransactionsOptions {
|
||||||
|
/// Sets the page size.
|
||||||
|
pub fn page_size(&mut self, value : u8) {
|
||||||
|
self.page_size = Some(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the status filter value.
|
||||||
|
pub fn filter_status(&mut self, value : String) {
|
||||||
|
self.filter_status = Some(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the since filter value.
|
||||||
|
pub fn filter_since(&mut self, value : String) {
|
||||||
|
self.filter_since = Some(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the until filter value.
|
||||||
|
pub fn filter_until (&mut self, value : String) {
|
||||||
|
self.filter_until = Some(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the category filter value.
|
||||||
|
pub fn filter_category(&mut self, value : String) {
|
||||||
|
self.filter_category = Some(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the tag filter value.
|
||||||
|
pub fn filter_tag (&mut self, value : String) {
|
||||||
|
self.filter_tag = Some(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_params(&self, url : &mut reqwest::Url) {
|
||||||
|
let mut query = String::new();
|
||||||
|
|
||||||
|
if let Some(value) = &self.page_size {
|
||||||
|
if !query.is_empty() {
|
||||||
|
query.push('&');
|
||||||
|
}
|
||||||
|
query.push_str(&format!("page[size]={}", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = &self.filter_status {
|
||||||
|
if !query.is_empty() {
|
||||||
|
query.push('&');
|
||||||
|
}
|
||||||
|
query.push_str(&format!("filter[status]={}", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = &self.filter_since {
|
||||||
|
if !query.is_empty() {
|
||||||
|
query.push('&');
|
||||||
|
}
|
||||||
|
query.push_str(&format!("filter[since]={}", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = &self.filter_until {
|
||||||
|
if !query.is_empty() {
|
||||||
|
query.push('&');
|
||||||
|
}
|
||||||
|
query.push_str(&format!("filter[until]={}", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = &self.filter_category {
|
||||||
|
if !query.is_empty() {
|
||||||
|
query.push('&');
|
||||||
|
}
|
||||||
|
query.push_str(&format!("filter[category]={}", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = &self.filter_tag {
|
||||||
|
if !query.is_empty() {
|
||||||
|
query.push('&');
|
||||||
|
}
|
||||||
|
query.push_str(&format!("filter[tag]={}", value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !query.is_empty() {
|
||||||
|
url.set_query(Some(&query));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
/// Retrieve a list of all transactions across all accounts for the currently authenticated user. The returned list is paginated and can be scrolled by following the `next` and `prev` links where present. To narrow the results to a specific date range pass one or both of `filter[since]` and `filter[until]` in the query string. These filter parameters should not be used for pagination. Results are ordered newest first to oldest last.
|
||||||
|
pub async fn list_transactions(&self, options : &ListTransactionsOptions) -> Result<ListTransactionsResponse, error::Error> {
|
||||||
|
let mut url = reqwest::Url::parse(&format!("{}/transactions", BASE_URL)).map_err(error::Error::UrlParse)?;
|
||||||
|
options.add_params(&mut url);
|
||||||
|
|
||||||
|
let res = reqwest::Client::new()
|
||||||
|
.get(url)
|
||||||
|
.header("Authorization", self.auth_header())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(error::Error::Request)?;
|
||||||
|
|
||||||
|
match res.status() {
|
||||||
|
reqwest::StatusCode::OK => {
|
||||||
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
|
let transaction_response : ListTransactionsResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
|
Ok(transaction_response)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
|
let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
|
Err(error::Error::Api(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a specific transaction by providing its unique identifier.
|
||||||
|
pub async fn get_transaction(&self, id : &String) -> Result<GetTransactionResponse, error::Error> {
|
||||||
|
// This assertion is because without an ID the request is thought to be a request for
|
||||||
|
// many accounts, and therefore the error messages are very unclear.
|
||||||
|
if id.is_empty() {
|
||||||
|
panic!("The provided transaction ID must not be empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = reqwest::Url::parse(&format!("{}/transactions/{}", BASE_URL, id)).map_err(error::Error::UrlParse)?;
|
||||||
|
|
||||||
|
let res = reqwest::Client::new()
|
||||||
|
.get(url)
|
||||||
|
.header("Authorization", self.auth_header())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(error::Error::Request)?;
|
||||||
|
|
||||||
|
match res.status() {
|
||||||
|
reqwest::StatusCode::OK => {
|
||||||
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
|
let transaction_response : GetTransactionResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
|
Ok(transaction_response)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
|
let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
|
Err(error::Error::Api(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a list of all transactions for a specific account. The returned list is paginated and can be scrolled by following the `next` and `prev` links where present. To narrow the results to a specific date range pass one or both of `filter[since]` and `filter[until]` in the query string. These filter parameters should not be used for pagination. Results are ordered newest first to oldest last.
|
||||||
|
pub async fn list_transactions_by_account(&self, account_id : &String, options : &ListTransactionsOptions) -> Result<ListTransactionsResponse, error::Error> {
|
||||||
|
let mut url = reqwest::Url::parse(&format!("{}/accounts/{}/transactions", BASE_URL, account_id)).map_err(error::Error::UrlParse)?;
|
||||||
|
options.add_params(&mut url);
|
||||||
|
|
||||||
|
let res = reqwest::Client::new()
|
||||||
|
.get(url)
|
||||||
|
.header("Authorization", self.auth_header())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(error::Error::Request)?;
|
||||||
|
|
||||||
|
match res.status() {
|
||||||
|
reqwest::StatusCode::OK => {
|
||||||
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
|
let transaction_response : ListTransactionsResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
|
Ok(transaction_response)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let body = res.text().await.map_err(error::Error::BodyRead)?;
|
||||||
|
let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
|
||||||
|
|
||||||
|
Err(error::Error::Api(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@ use crate::v1::{Client, error, BASE_URL};
|
|||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
// ----------------- Request Objects -----------------
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct PingResponse {
|
pub struct PingResponse {
|
||||||
pub meta : Meta,
|
pub meta : Meta,
|
||||||
|
Loading…
Reference in New Issue
Block a user