This commit is contained in:
aaron-jack-manning 2022-07-10 18:39:53 +10:00
parent 134dd74c31
commit 700a48e56c
12 changed files with 822 additions and 68 deletions

View File

@ -2,10 +2,10 @@
name = "up-api"
version = "0.1.0"
edition = "2021"
#description = "A convenient and easy to use wrapper for the Up Bank API."
#license = "MIT OR Apache-2.0"
#authors = ["Aaron Manning"]
#repository = "https://github.com/aaron-jack-manning/up-api"
description = "A convenient and easy to use wrapper for the Up Bank API."
license = "MIT OR Apache-2.0"
authors = ["Aaron Manning"]
repository = "https://github.com/aaron-jack-manning/up-api"
[dependencies]
reqwest = "0.11.11"

View File

@ -4,30 +4,37 @@ A convenient and easy to use wrapper for the [Up Bank API](https://developer.up.
## 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::transactions::ListTransactionsOptions;
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: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 =
transactions
.data
.into_iter()
.map(|t| t.attributes.amount.value)
.map(|v| v.parse::<f32>())
.map(|v| v.parse::<f32>().unwrap())
.filter(|a| a > &0.0)
.sum();
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.

View File

@ -1,8 +1,39 @@
//! # Up API
//!
//! 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.
pub mod v1;

View File

@ -1,24 +1,37 @@
use crate::v1::{Client, error, BASE_URL};
use crate::v1::{Client, error, BASE_URL, standard};
use serde::Deserialize;
#[derive(Deserialize, Debug)]
pub struct GetAccountResponse {
/// The account returned in this response.
pub data : Data,
}
// ----------------- Response Objects -----------------
#[derive(Deserialize, Debug)]
pub struct ListAccountsResponse {
/// The list of accounts returned in this response.
pub data : Vec<Data>,
pub data : Vec<AccountResource>,
pub links : ResponseLinks,
}
#[derive(Deserialize, Debug)]
pub struct DataLinks {
#[serde(rename = "self")]
pub struct GetAccountResponse {
/// 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.
#[serde(rename = "self")]
pub this : Option<String>,
}
@ -30,17 +43,6 @@ pub struct ResponseLinks {
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)]
pub struct Relationships {
pub transactions : Transactions,
@ -58,34 +60,21 @@ pub struct TransactionLinks {
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Attributes {
#[serde(rename = "displayName")]
/// The name associated with the account in the Up application.
pub display_name : String,
#[serde(rename = "accountType")]
/// The bank account type of this account. Possible values: SAVER, TRANSACTIONAL
pub account_type : String,
#[serde(rename = "ownershipType")]
/// The ownership structure for this account. Possible values: INDIVIDUAL, JOINT
pub ownership_type : String,
/// The available balance of the account, taking into account any amounts that are currently on hold.
pub balance : Balance,
#[serde(rename = "createdAt")]
pub balance : standard::MoneyObject,
/// The date-time at which this account was first opened.
pub created_at : String,
}
#[derive(Deserialize, Debug)]
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,
}
// ----------------- Input Objects -----------------
#[derive(Default)]
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.
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.
filter_owership_type : Option<String>,
filter_ownership_type : Option<String>,
}
impl ListAccountsOptions {
@ -109,21 +98,36 @@ impl ListAccountsOptions {
}
/// Sets the ownership type filter value.
pub fn filter_owership_type(&mut self, value : String) {
self.filter_owership_type = Some(value);
pub fn filter_ownership_type(&mut self, value : String) {
self.filter_ownership_type = Some(value);
}
fn add_params(&self, url : &mut reqwest::Url) {
let mut query = String::new();
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 {
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 {
url.set_query(Some(&format!("filter[ownershipType]={}", value)));
if let Some(value) = &self.filter_ownership_type {
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() {
reqwest::StatusCode::OK => {
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)?;
Ok(account_response)
@ -178,7 +181,6 @@ impl Client {
match res.status() {
reqwest::StatusCode::OK => {
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)?;
Ok(account_response)

View File

@ -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))
}
}
}
}

View File

@ -40,11 +40,11 @@ impl std::error::Error for Error {}
#[derive(Deserialize, Debug)]
pub struct ErrorResponse {
/// The list of errors returned in this response.
pub errors : Vec<UpError>,
pub errors : Vec<ErrorObject>,
}
#[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.
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.

View File

@ -2,16 +2,16 @@
pub mod error;
/// Types for modelling and interacting with [accounts](https://developer.up.com.au/#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;
/// Types for modelling and interacting with [tags](https://developer.up.com.au/#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;
/// Types for modelling and interacting with [utilities](https://developer.up.com.au/#utility_endpoints).
pub mod utilities;
/// INCOMPLETE: Types for modelling and interacting with [webhooks](https://developer.up.com.au/#webhooks).
pub mod webhooks;
/// Types which are stardized (and named) across many resources.
pub mod standard;
static BASE_URL : &str = "https://api.up.com.au/api/v1";

82
src/v1/standard.rs Normal file
View 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>,
}

View File

@ -2,15 +2,17 @@ use crate::v1::{Client, error, BASE_URL};
use serde::{Deserialize, Serialize};
// ----------------- Response Objects -----------------
#[derive(Deserialize, Debug)]
pub struct ListTagsResponse {
/// The list of tags returned in this response.
pub data : Vec<Data>,
pub data : Vec<TagResource>,
pub links : ResponseLinks,
}
#[derive(Deserialize, Debug)]
pub struct Data {
pub struct TagResource {
/// The type of this resource: `tags`
pub r#type : String,
/// The label of the tag, which also acts as the tags unique identifier.
@ -40,6 +42,8 @@ pub struct ResponseLinks {
pub next : Option<String>,
}
// ----------------- Input Objects -----------------
#[derive(Default)]
pub struct ListTagsOptions {
/// The number of records to return in each page.
@ -53,12 +57,23 @@ impl ListTagsOptions {
}
fn add_params(&self, url : &mut reqwest::Url) {
let mut query = String::new();
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)]
struct TagInputResourceIdentifier {
/// The type of this resource: `tags`
@ -73,7 +88,6 @@ struct TagRequest {
data : Vec<TagInputResourceIdentifier>
}
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.
pub async fn list_tags(&self, options : &ListTagsOptions) -> Result<ListTagsResponse, error::Error> {
@ -90,7 +104,6 @@ impl Client {
match res.status() {
reqwest::StatusCode::OK => {
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)?;
Ok(tags_response)
@ -123,6 +136,7 @@ impl Client {
let res = reqwest::Client::new()
.post(url)
.header("Authorization", self.auth_header())
.header("Content-Type", "application/json")
.body(body)
.send()
.await
@ -160,6 +174,7 @@ impl Client {
let res = reqwest::Client::new()
.delete(url)
.header("Authorization", self.auth_header())
.header("Content-Type", "application/json")
.body(body)
.send()
.await

View File

@ -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 tags 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))
}
}
}
}

View File

@ -2,6 +2,8 @@ use crate::v1::{Client, error, BASE_URL};
use serde::Deserialize;
// ----------------- Request Objects -----------------
#[derive(Deserialize, Debug)]
pub struct PingResponse {
pub meta : Meta,

View File