From 9eeee510a54e630890b791b0fab46e5e813f0cf9 Mon Sep 17 00:00:00 2001 From: Aaron Manning Date: Thu, 13 Feb 2025 15:27:44 +1100 Subject: [PATCH] date filters strongly typed; formatting --- Cargo.toml | 1 + src/v1/accounts.rs | 120 ++++++++----- src/v1/categories.rs | 141 +++++++++------ src/v1/error.rs | 76 ++++++--- src/v1/macros.rs | 38 ++++- src/v1/mod.rs | 27 +-- src/v1/standard.rs | 44 +++-- src/v1/tags.rs | 140 ++++++++++----- src/v1/transactions.rs | 323 ++++++++++++++++++++++------------- src/v1/utilities.rs | 31 +++- src/v1/webhooks.rs | 379 +++++++++++++++++++++++++++-------------- 11 files changed, 873 insertions(+), 447 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 05c18fe..c782d41 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ url = "2.2.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" urlencoding = "2.1.3" +chrono = "0.4.39" diff --git a/src/v1/accounts.rs b/src/v1/accounts.rs index fd27ff0..6b2d202 100755 --- a/src/v1/accounts.rs +++ b/src/v1/accounts.rs @@ -7,71 +7,76 @@ use serde::Deserialize; #[derive(Deserialize, Debug)] pub struct ListAccountsResponse { /// The list of accounts returned in this response. - pub data : Vec, - pub links : ResponseLinks, + pub data: Vec, + pub links: ResponseLinks, } #[derive(Deserialize, Debug)] pub struct GetAccountResponse { /// The account returned in this response. - pub data : AccountResource, + pub data: AccountResource, } #[derive(Deserialize, Debug)] pub struct AccountResource { /// The type of this resource: `accounts`. - pub r#type : String, + pub r#type: String, /// The unique identifier for this account. - pub id : String, - pub attributes : Attributes, - pub relationships : Relationships, - pub links : Option, + pub id: String, + pub attributes: Attributes, + pub relationships: Relationships, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct AccountResourceLinks { /// The canonical link to this resource within the API. #[serde(rename = "self")] - pub this : Option, + pub this: Option, } #[derive(Deserialize, Debug)] pub struct ResponseLinks { - /// The link to the previous page in the results. If this value is `None` there is no previous page. - pub prev : Option, - /// The link to the next page in the results. If this value is `None` there is no next page. - pub next : Option, + /// The link to the previous page in the results. If this value is `None` + /// there is no previous page. + pub prev: Option, + /// The link to the next page in the results. If this value is `None` there + /// is no next page. + pub next: Option, } #[derive(Deserialize, Debug)] pub struct Relationships { - pub transactions : Transactions, + pub transactions: Transactions, } #[derive(Deserialize, Debug)] pub struct Transactions { - pub links : Option, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct TransactionLinks { /// The link to retrieve the related resource(s) in this relationship. - pub related : String, + pub related: String, } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Attributes { /// The name associated with the account in the Up application. - pub display_name : String, - /// The bank account type of this account. Possible values: SAVER, TRANSACTIONAL - pub account_type : AccountType, - /// The ownership structure for this account. Possible values: INDIVIDUAL, JOINT - pub ownership_type : OwnershipType, - /// The available balance of the account, taking into account any amounts that are currently on hold. - pub balance : standard::MoneyObject, + pub display_name: String, + /// The bank account type of this account. Possible values: SAVER, + /// TRANSACTIONAL + pub account_type: AccountType, + /// The ownership structure for this account. Possible values: INDIVIDUAL, + /// JOINT + pub ownership_type: OwnershipType, + /// The available balance of the account, taking into account any amounts + /// that are currently on hold. + pub balance: standard::MoneyObject, /// The date-time at which this account was first opened. - pub created_at : String, + pub created_at: String, } #[derive(Deserialize, Debug)] @@ -93,30 +98,32 @@ pub enum OwnershipType { #[derive(Default)] pub struct ListAccountsOptions { /// The number of records to return in each page. - page_size : Option, - /// The type of account for which to return records. This can be used to filter Savers from spending accounts. - filter_account_type : Option, - /// The account ownership structure for which to return records. This can be used to filter 2Up accounts from Up accounts. - filter_ownership_type : Option, + page_size: Option, + /// The type of account for which to return records. This can be used to + /// filter Savers from spending accounts. + filter_account_type: Option, + /// The account ownership structure for which to return records. This can + /// be used to filter 2Up accounts from Up accounts. + filter_ownership_type: Option, } impl ListAccountsOptions { /// Sets the page size. - pub fn page_size(&mut self, value : u8) { + pub fn page_size(&mut self, value: u8) { self.page_size = Some(value); } /// Sets the account type filter value. - pub fn filter_account_type(&mut self, value : String) { + pub fn filter_account_type(&mut self, value: String) { self.filter_account_type = Some(value); } /// Sets the ownership type filter value. - pub fn filter_ownership_type(&mut self, value : String) { + pub fn filter_ownership_type(&mut self, value: String) { 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 { @@ -130,14 +137,18 @@ impl ListAccountsOptions { if !query.is_empty() { query.push('&'); } - query.push_str(&format!("filter[accountType]={}", urlencoding::encode(value))); + query.push_str( + &format!("filter[accountType]={}", urlencoding::encode(value)) + ); } if let Some(value) = &self.filter_ownership_type { if !query.is_empty() { query.push('&'); } - query.push_str(&format!("filter[ownershipType]={}", urlencoding::encode(value))); + query.push_str( + &format!("filter[ownershipType]={}", urlencoding::encode(value)) + ); } if !query.is_empty() { @@ -147,9 +158,16 @@ impl ListAccountsOptions { } impl Client { - /// 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. - pub async fn list_accounts(&self, options : &ListAccountsOptions) -> Result { - let mut url = reqwest::Url::parse(&format!("{}/accounts", BASE_URL)).map_err(error::Error::UrlParse)?; + /// 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. + pub async fn list_accounts( + &self, + options: &ListAccountsOptions, + ) -> Result { + let mut url = reqwest::Url::parse( + &format!("{}/accounts", BASE_URL) + ).map_err(error::Error::UrlParse)?; options.add_params(&mut url); let res = reqwest::Client::new() @@ -162,13 +180,15 @@ impl Client { match res.status() { reqwest::StatusCode::OK => { let body = res.text().await.map_err(error::Error::BodyRead)?; - 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) }, _ => { let body = res.text().await.map_err(error::Error::BodyRead)?; - let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?; + let error: error::ErrorResponse = + serde_json::from_str(&body).map_err(error::Error::Json)?; Err(error::Error::Api(error)) } @@ -176,14 +196,20 @@ impl Client { } /// Retrieve a specific account by providing its unique identifier. - pub async fn get_account(&self, id : &str) -> Result { - // 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. + pub async fn get_account( + &self, + id: &str, + ) -> Result { + // 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!("{}/accounts/{}", BASE_URL, id)).map_err(error::Error::UrlParse)?; + let url = reqwest::Url::parse( + &format!("{}/accounts/{}", BASE_URL, id) + ).map_err(error::Error::UrlParse)?; let res = reqwest::Client::new() .get(url) @@ -195,13 +221,15 @@ impl Client { match res.status() { reqwest::StatusCode::OK => { let body = res.text().await.map_err(error::Error::BodyRead)?; - 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) }, _ => { let body = res.text().await.map_err(error::Error::BodyRead)?; - let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?; + let error: error::ErrorResponse = + serde_json::from_str(&body).map_err(error::Error::Json)?; Err(error::Error::Api(error)) } diff --git a/src/v1/categories.rs b/src/v1/categories.rs index a5d31b5..67efad9 100755 --- a/src/v1/categories.rs +++ b/src/v1/categories.rs @@ -7,108 +7,112 @@ use serde::{Deserialize, Serialize}; #[derive(Deserialize, Debug)] pub struct ListCategoriesResponse { /// The list of categories returned in this response. - pub data : Vec, + pub data: Vec, } #[derive(Deserialize, Debug)] pub struct GetCategoryResponse { /// The category returned in this response. - pub data : CategoryResource, + 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, + 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, } #[derive(Deserialize, Debug)] pub struct Attributes { /// The name of this category as seen in the Up application. - pub name : String, + pub name: String, } #[derive(Deserialize, Debug)] pub struct Relationships { - pub parent : Parent, - pub children : Children, + pub parent: Parent, + pub children: Children, } #[derive(Deserialize, Debug)] pub struct Parent { - pub data : Option, - pub links : Option, + pub data: Option, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct ParentData { /// The type of this resource: `categories` - pub r#type : String, + pub r#type: String, /// The unique identifier of the resource within its type. - pub id : String, + pub id: String, } #[derive(Deserialize, Debug)] pub struct ParentLinks { /// The link to retrieve the related resource(s) in this relationship. - pub related : String, + pub related: String, } #[derive(Deserialize, Debug)] pub struct Children { - pub data : Vec, - pub links : Option, + pub data: Vec, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct ChildrenData { /// The type of this resource: `categories` - pub r#type : String, + pub r#type: String, /// The unique identifier of the resource within its type. - pub id : String, + pub id: String, } #[derive(Deserialize, Debug)] pub struct ChildrenLinks { /// The link to retrieve the related resource(s) in this relationship. - pub related : String, + pub related: String, } #[derive(Deserialize, Debug)] pub struct CategoryResourceLinks { /// The canonical link to this resource within the API. #[serde(rename = "self")] - pub this : String, + 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, + /// The unique identifier of a parent category for which to return only its + /// children. + filter_parent: Option, } impl ListCategoriesOptions { /// Sets the parent filter value. - pub fn filter_parent(&mut self, value : String) { + pub fn filter_parent(&mut self, value: String) { self.filter_parent = 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.filter_parent { if !query.is_empty() { query.push('&'); } - query.push_str(&format!("filter[parent]={}", urlencoding::encode(value))); + query.push_str( + &format!("filter[parent]={}", urlencoding::encode(value)) + ); } if !query.is_empty() { @@ -121,22 +125,30 @@ impl ListCategoriesOptions { #[derive(Serialize)] struct CategoriseTransactionRequest { - /// The category to set on the transaction. Set this entire key to `null` de-categorize a transaction. - data : Option, + /// The category to set on the transaction. Set this entire key to `null` + /// de-categorize a transaction. + data: Option, } #[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, + 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 { - let mut url = reqwest::Url::parse(&format!("{}/categories", BASE_URL)).map_err(error::Error::UrlParse)?; + /// Retrieve a list of all categories and their ancestry. The returned list + /// is not paginated. + pub async fn list_categories( + &self, + options: &ListCategoriesOptions, + ) -> Result { + 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() @@ -149,13 +161,15 @@ impl Client { 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)?; + 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)?; + let error: error::ErrorResponse = + serde_json::from_str(&body).map_err(error::Error::Json)?; Err(error::Error::Api(error)) } @@ -163,14 +177,19 @@ impl Client { } /// Retrieve a specific category by providing its unique identifier. - pub async fn get_category(&self, id : &str) -> Result { - // This assertion is because without an ID the request is thought to be a request for - // many categories, and therefore the error messages are very unclear. + pub async fn get_category( + &self, id: &str, + ) -> Result { + // This assertion is because without an ID the request is thought to be + // a request for many categories, and therefore the error messages are + // very unclear. if id.is_empty() { panic!("The provided category ID must not be empty."); } - let url = reqwest::Url::parse(&format!("{}/categories/{}", BASE_URL, id)).map_err(error::Error::UrlParse)?; + let url = reqwest::Url::parse( + &format!("{}/categories/{}", BASE_URL, id) + ).map_err(error::Error::UrlParse)?; let res = reqwest::Client::new() .get(url) @@ -182,32 +201,51 @@ impl Client { 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)?; + 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)?; + 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)?; + /// 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), + 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)?; + let body = CategoriseTransactionRequest { data: category }; + let body = + serde_json::to_string(&body) + .map_err(error::Error::Serialize)?; println!("{}", body); @@ -226,7 +264,8 @@ impl Client { }, _ => { let body = res.text().await.map_err(error::Error::BodyRead)?; - let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?; + let error: error::ErrorResponse = + serde_json::from_str(&body).map_err(error::Error::Json)?; Err(error::Error::Api(error)) } diff --git a/src/v1/error.rs b/src/v1/error.rs index 6591490..1023f58 100755 --- a/src/v1/error.rs +++ b/src/v1/error.rs @@ -11,26 +11,44 @@ pub enum Error { Request(reqwest::Error), /// Represents errors from the API (i.e. a non `2XX` response code). Api(ErrorResponse), - /// Represents an error in deserializing JSON to the required structures. Occurances of this - /// error should be treated as a bug in the library. + /// Represents an error in deserializing JSON to the required structures. + /// Occurences of this error should be treated as a bug in the library. Json(serde_json::Error), - /// Represents an error in reading the body from the HTTP response. Occurances of this - /// error should be treated as a bug in the library. + /// Represents an error in reading the body from the HTTP response. + /// Occurences of this error should be treated as a bug in the library. BodyRead(reqwest::Error), - /// Represents an error serializing the data to be sent to the API. Occurances of this - /// error should be treated as a bug in the library. + /// Represents an error serializing the data to be sent to the API. + /// Occurences of this error should be treated as a bug in the library. Serialize(serde_json::Error), } impl fmt::Display for Error { - fn fmt(&self, f : &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { - Self::UrlParse(val) => write!(f, "Failed to parse the URL before making the request: {:?}", val), - Self::Request(val) => write!(f, "Failed to make the HTTP request to the API endpoint: {:?}", val), - Self::Api(val) => write!(f, "The API returned an error response: {:?}", val), - Self::Json(val) => write!(f, "Failed to deserialize the returned JSON to the correct format: {:?}", val), - Self::BodyRead(val) => write!(f, "Failed to read the response body as a UTF-8 string: {:?}", val), - Self::Serialize(val) => write!(f, "Failed to serialize the request data: {:?}", val), + Self::UrlParse(val) => write!(f, + "Failed to parse the URL before making the request: {:?}", + val, + ), + Self::Request(val) => write!(f, + "Failed to make the HTTP request to the API endpoint: {:?}", + val, + ), + Self::Api(val) => write!(f, + "The API returned an error response: {:?}", + val, + ), + Self::Json(val) => write!(f, + "Failed to deserialize the returned JSON to the correct format: {:?}", + val, + ), + Self::BodyRead(val) => write!(f, + "Failed to read the response body as a UTF-8 string: {:?}", + val, + ), + Self::Serialize(val) => write!(f, + "Failed to serialize the request data: {:?}", + val, + ), } } } @@ -40,25 +58,33 @@ impl std::error::Error for Error {} #[derive(Deserialize, Debug)] pub struct ErrorResponse { /// The list of errors returned in this response. - pub errors : Vec, + pub errors: Vec, } #[derive(Deserialize, Debug)] 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. - pub title : String, - /// A detailed description of this error. This should be considered unique to individual occurrences of an error and subject to change. It is useful for debugging purposes. - pub detail : String, - /// If applicable, location in the request that this error relates to. This may be a parameter in the query string, or a an attribute in the request body. - pub source : Option, + /// 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. + pub title: String, + /// A detailed description of this error. This should be considered unique + /// to individual occurrences of an error and subject to change. It is + /// useful for debugging purposes. + pub detail: String, + /// If applicable, location in the request that this error relates to. This + /// may be a parameter in the query string, or a an attribute in the + /// request body. + pub source: Option, } #[derive(Deserialize, Debug)] pub struct Source { /// If this error relates to a query parameter, the name of the parameter. - pub parameter : Option, - /// If this error relates to an attribute in the request body, a rfc-6901 JSON pointer to the attribute. - pub pointer : Option + pub parameter: Option, + /// If this error relates to an attribute in the request body, a rfc-6901 + /// JSON pointer to the attribute. + pub pointer: Option } diff --git a/src/v1/macros.rs b/src/v1/macros.rs index 075f9b9..072405b 100755 --- a/src/v1/macros.rs +++ b/src/v1/macros.rs @@ -1,7 +1,10 @@ macro_rules! implement_pagination_v1 { ($t:ty) => { impl $t { - async fn follow_link(client : &Client, url : &str) -> Result { + async fn follow_link( + client: &Client, + url: &str, + ) -> Result { let res = reqwest::Client::new() .get(url) .header("Authorization", client.auth_header()) @@ -11,22 +14,36 @@ macro_rules! implement_pagination_v1 { match res.status() { reqwest::StatusCode::OK => { - let body = res.text().await.map_err(error::Error::BodyRead)?; - let response : Self = serde_json::from_str(&body).map_err(error::Error::Json)?; + let body = + res.text() + .await + .map_err(error::Error::BodyRead)?; + let response: Self = + serde_json::from_str(&body) + .map_err(error::Error::Json)?; Ok(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)?; + 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)) } } } - /// Follows the link to the next page, returns None of the next page does not exist. - pub async fn next(&self, client : &Client) -> Option> { + /// Follows the link to the next page, returns None of the next + /// page does not exist. + pub async fn next( + &self, + client: &Client, + ) -> Option> { match self .links @@ -39,8 +56,11 @@ macro_rules! implement_pagination_v1 { } } - /// Follows the link to the previous page, returns None of the previous page does not exist. - pub async fn prev(&self, client : &Client) -> Option> { + /// Follows the link to the previous page, returns None of the + /// previous page does not exist. + pub async fn prev( + &self, client: &Client, + ) -> Option> { match self .links diff --git a/src/v1/mod.rs b/src/v1/mod.rs index 22b1733..83b0edd 100755 --- a/src/v1/mod.rs +++ b/src/v1/mod.rs @@ -3,32 +3,39 @@ mod macros; /// Error types and trait implementations. 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; -/// 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). +/// Types for modelling and interacting with +/// [tags](https://developer.up.com.au/#tags). pub mod tags; -/// 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). +/// Types for modelling and interacting with +/// [utilities](https://developer.up.com.au/#utility_endpoints). pub mod utilities; -/// Types for modelling and interacting with [webhooks](https://developer.up.com.au/#webhooks). +/// 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"; +static BASE_URL: &str = "https://api.up.com.au/api/v1"; /// A client for interacting with the Up API. pub struct Client { - access_token : String, + access_token: String, } impl Client { - /// Creates an instance of the `Client` from the access token. Visit [this page](https://api.up.com.au/getting_started) to get such a token. - pub fn new(access_token : String) -> Self { + /// Creates an instance of the `Client` from the access token. Visit + /// [this page](https://api.up.com.au/getting_started) to get such a token. + pub fn new(access_token: String) -> Self { Client { access_token } diff --git a/src/v1/standard.rs b/src/v1/standard.rs index 6339cc8..0e3abe3 100755 --- a/src/v1/standard.rs +++ b/src/v1/standard.rs @@ -11,11 +11,15 @@ pub enum AccountTypeEnum { #[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, + 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)] @@ -35,27 +39,33 @@ pub enum TransactionStatusEnum { #[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, + /// 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, } #[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, + /// 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, } #[derive(Deserialize, Debug)] pub struct CashBackObject { /// A brief description of why this cashback was paid. - pub description : String, + pub description: String, /// The total amount of cashback paid, represented as a positive value. - pub amount : MoneyObject, + pub amount: MoneyObject, } #[derive(Deserialize, Debug)] @@ -76,9 +86,9 @@ pub enum CardPurchaseMethodEnum { #[serde(rename_all = "camelCase")] pub struct CardPurchaseMethodObject { /// The type of card purchase. - pub method : CardPurchaseMethodEnum, + pub method: CardPurchaseMethodEnum, /// The last four digits of the card used for the purchase, if applicable. - pub card_number_suffix : Option, + pub card_number_suffix: Option, } diff --git a/src/v1/tags.rs b/src/v1/tags.rs index 1bd0b3a..3432543 100755 --- a/src/v1/tags.rs +++ b/src/v1/tags.rs @@ -7,39 +7,39 @@ use serde::{Deserialize, Serialize}; #[derive(Deserialize, Debug)] pub struct ListTagsResponse { /// The list of tags returned in this response. - pub data : Vec, - pub links : ResponseLinks, + pub data: Vec, + pub links: ResponseLinks, } #[derive(Deserialize, Debug)] pub struct TagResource { /// 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. - pub id : String, - pub relationships : Relationships, + pub id: String, + pub relationships: Relationships, } #[derive(Deserialize, Debug)] pub struct Relationships { - pub transactions : Transactions, + pub transactions: Transactions, } #[derive(Deserialize, Debug)] pub struct Transactions { - pub links : Option, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct TransactionLinks { /// The link to retrieve the related resource(s) in this relationship. - pub related : String, + pub related: String, } #[derive(Deserialize, Debug)] pub struct ResponseLinks { - pub prev : Option, - pub next : Option, + pub prev: Option, + pub next: Option, } // ----------------- Input Objects ----------------- @@ -47,16 +47,16 @@ pub struct ResponseLinks { #[derive(Default)] pub struct ListTagsOptions { /// The number of records to return in each page. - page_size : Option, + page_size: Option, } impl ListTagsOptions { /// Sets the page size. - pub fn page_size(&mut self, value : u8) { + pub fn page_size(&mut self, value: u8) { self.page_size = 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 { @@ -77,21 +77,30 @@ impl ListTagsOptions { #[derive(Serialize)] struct TagInputResourceIdentifier { /// The type of this resource: `tags` - r#type : String, + r#type: String, /// The label of the tag, which also acts as the tag’s unique identifier. - id : String, + id: String, } #[derive(Serialize)] struct TagRequest { /// The tags to add to or remove from the transaction. - data : Vec + data: Vec } 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 { - let mut url = reqwest::Url::parse(&format!("{}/tags", BASE_URL)).map_err(error::Error::UrlParse)?; + /// 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 { + let mut url = reqwest::Url::parse( + &format!("{}/tags", BASE_URL) + ).map_err(error::Error::UrlParse)?; options.add_params(&mut url); let res = reqwest::Client::new() @@ -103,35 +112,60 @@ impl Client { match res.status() { reqwest::StatusCode::OK => { - let body = res.text().await.map_err(error::Error::BodyRead)?; - let tags_response : ListTagsResponse = serde_json::from_str(&body).map_err(error::Error::Json)?; + let body = + res.text() + .await + .map_err(error::Error::BodyRead)?; + let tags_response: ListTagsResponse = + serde_json::from_str(&body) + .map_err(error::Error::Json)?; Ok(tags_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)?; + 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)) } } } - /// Associates one or more tags with a specific transaction. No more than 6 tags may be present on any single transaction. Duplicate tags are silently ignored. The associated tags, along with this request URL, are also exposed via the tags relationship on the transaction resource returned from `get_transaction`. - pub async fn add_tags(&self, transaction_id : &str, tags : Vec) -> Result<(), error::Error> { - let url = reqwest::Url::parse(&format!("{}/transactions/{}/relationships/tags", BASE_URL, transaction_id)).map_err(error::Error::UrlParse)?; + /// Associates one or more tags with a specific transaction. No more than 6 + /// tags may be present on any single transaction. Duplicate tags are + /// silently ignored. The associated tags, along with this request URL, are + /// also exposed via the tags relationship on the transaction resource + /// returned from `get_transaction`. + pub async fn add_tags( + &self, + transaction_id: &str, + tags: Vec, + ) -> Result<(), error::Error> { + let url = reqwest::Url::parse( + &format!("{}/transactions/{}/relationships/tags", + BASE_URL, + transaction_id, + ) + ).map_err(error::Error::UrlParse)?; let tags = tags .into_iter() .map(|t| TagInputResourceIdentifier { - r#type : String::from("tags"), - id : t + r#type: String::from("tags"), + id: t }) .collect(); - let body = TagRequest { data : tags }; - let body = serde_json::to_string(&body).map_err(error::Error::Serialize)?; + let body = TagRequest { data: tags }; + let body = + serde_json::to_string(&body) + .map_err(error::Error::Serialize)?; let res = reqwest::Client::new() .post(url) @@ -147,29 +181,48 @@ impl Client { 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)?; + 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)) } } } - /// Disassociates one or more tags from a specific transaction. Tags that are not associated are silently ignored. The associated tags, along with this request URL, are also exposed via the tags relationship on the transaction resource returned from `get_transaction`. - pub async fn delete_tags(&self, transaction_id : &str, tags : Vec) -> Result<(), error::Error> { - let url = reqwest::Url::parse(&format!("{}/transactions/{}/relationships/tags", BASE_URL, transaction_id)).map_err(error::Error::UrlParse)?; + /// Disassociates one or more tags from a specific transaction. Tags that + /// are not associated are silently ignored. The associated tags, along + /// with this request URL, are also exposed via the tags relationship on + /// the transaction resource returned from `get_transaction`. + pub async fn delete_tags( + &self, + transaction_id: &str, + tags: Vec, + ) -> Result<(), error::Error> { + let url = reqwest::Url::parse( + &format!("{}/transactions/{}/relationships/tags", + BASE_URL, + transaction_id, + ) + ).map_err(error::Error::UrlParse)?; let tags = tags .into_iter() .map(|t| TagInputResourceIdentifier { - r#type : String::from("tags"), - id : t + r#type: String::from("tags"), + id: t }) .collect(); - let body = TagRequest { data : tags }; - let body = serde_json::to_string(&body).map_err(error::Error::Serialize)?; + let body = TagRequest { data: tags }; + let body = + serde_json::to_string(&body) + .map_err(error::Error::Serialize)?; let res = reqwest::Client::new() .delete(url) @@ -185,8 +238,13 @@ impl Client { 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)?; + 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)) } diff --git a/src/v1/transactions.rs b/src/v1/transactions.rs index a675111..39e1b97 100755 --- a/src/v1/transactions.rs +++ b/src/v1/transactions.rs @@ -7,237 +7,278 @@ use serde::Deserialize; #[derive(Deserialize, Debug)] pub struct ListTransactionsResponse { /// The list of transactions returned in this response. - pub data : Vec, - pub links : ResponseLinks, + pub data: Vec, + pub links: ResponseLinks, } #[derive(Deserialize, Debug)] pub struct GetTransactionResponse { /// The transaction returned in this response. - pub data : TransactionResource, + pub data: TransactionResource, } #[derive(Deserialize, Debug)] pub struct TransactionResource { /// The type of this resource: `transactions` - pub r#type : String, + pub r#type: String, /// The unique identifier for this transaction. - pub id : String, - pub attributes : Attributes, - pub relationships : Relationships, - pub links : Option, + pub id: String, + pub attributes: Attributes, + pub relationships: Relationships, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct TransactionResourceLinks { /// The canonical link to this resource within the API. #[serde(rename = "self")] - pub this : String, + 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, + 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, + pub data: AccountData, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct AccountData { /// The type of this resource: `accounts` - pub r#type : String, + pub r#type: String, /// The unique identifier of the resource within its type. - pub id : String, + pub id: String, } #[derive(Deserialize, Debug)] pub struct AccountLinks { /// The link to retrieve the related resource(s) in this relationship. - pub related : String, + pub related: String, } #[derive(Deserialize, Debug)] pub struct TransferAccount { - pub data : Option, - pub links : Option, + pub data: Option, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct TransferAccountData { /// The type of this resource: `accounts` - pub r#type : String, + pub r#type: String, /// The unique identifier of the resource within its type. - pub id : String, + pub id: String, } #[derive(Deserialize, Debug)] pub struct TransferAccountLinks { /// The link to retrieve the related resource(s) in this relationship. - pub related : String, + pub related: String, } #[derive(Deserialize, Debug)] pub struct Category { - pub data : Option, - pub links : Option, + pub data: Option, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct CategoryData { /// The type of this resource: `categories` - pub r#type : String, + pub r#type: String, /// The unique identifier of the resource within its type. - pub id : String, + 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. + /// 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, + pub this: String, + pub related: Option, } #[derive(Deserialize, Debug)] pub struct ParentCategory { - pub data : Option, - pub links : Option, + pub data: Option, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct ParentCategoryData { /// The type of this resource: `categories` - pub r#type : String, + pub r#type: String, /// The unique identifier of the resource within its type. - pub id : String, + pub id: String, } #[derive(Deserialize, Debug)] pub struct ParentCategoryLinks { /// The link to retrieve the related resource(s) in this relationship. - pub related : String, + pub related: String, } #[derive(Deserialize, Debug)] pub struct Tags { - pub data : Vec, - pub links : Option, + pub data: Vec, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct TagsData { /// 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. - pub id : String, + 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. + /// 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 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, - /// 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, - /// 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, - /// Details of how this transaction was rounded-up. If no Round Up was applied this field will be `null`. - pub round_up : Option, - /// If all or part of this transaction was instantly reimbursed in the form of cashback, details of the reimbursement. - pub cashback : Option, - /// 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, + /// 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, + /// 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, + /// 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, + /// Details of how this transaction was rounded-up. If no Round Up was + /// applied this field will be `null`. + pub round_up: Option, + /// If all or part of this transaction was instantly reimbursed in the form + /// of cashback, details of the reimbursement. + pub cashback: Option, + /// 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, /// Information about the card used for this transaction, if applicable. - pub card_purchase_method : Option, - /// 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, + pub card_purchase_method: Option, + /// 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, /// The date-time at which this transaction was first encountered. - pub created_at : String, + 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, - /// The link to the next page in the results. If this value is null there is no next page. - pub next : Option, + /// The link to the previous page in the results. If this value is null + /// there is no previous page. + pub prev: Option, + /// The link to the next page in the results. If this value is null there is + /// no next page. + pub next: Option, } // ----------------- Input Objects ----------------- -#[derive(Default)] -pub struct ListTransactionsOptions { +pub struct ListTransactionsOptions { /// The number of records to return in each page. - page_size : Option, - /// The transaction status for which to return records. This can be used to filter `HELD` transactions from those that are `SETTLED`. - filter_status : Option, - /// The start date-time from which to return records, formatted according to rfc-3339. Not to be used for pagination purposes. - filter_since : Option, - /// 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, - /// The category identifier for which to filter transactions. Both parent and child categories can be filtered through this parameter. - filter_category : Option, - /// 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, + page_size: Option, + /// The transaction status for which to return records. This can be used to + /// filter `HELD` transactions from those that are `SETTLED`. + filter_status: Option, + /// The start date-time from which to return records, formatted according to + /// rfc-3339. Not to be used for pagination purposes. + filter_since: Option>, + /// 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>, + /// The category identifier for which to filter transactions. Both parent + /// and child categories can be filtered through this parameter. + filter_category: Option, + /// 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, } -impl ListTransactionsOptions { +impl Default for ListTransactionsOptions { + fn default() -> Self { + Self { + page_size: None, + filter_status: None, + filter_since: None, + filter_until: None, + filter_category: None, + filter_tag: None, + } + } +} + +impl ListTransactionsOptions { /// Sets the page size. - pub fn page_size(&mut self, value : u8) { + 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) { + 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) { + pub fn filter_since(&mut self, value: chrono::DateTime) { self.filter_since = Some(value); } /// Sets the until filter value. - pub fn filter_until (&mut self, value : String) { + pub fn filter_until (&mut self, value: chrono::DateTime) { self.filter_until = Some(value); } /// Sets the category filter value. - pub fn filter_category(&mut self, value : String) { + 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) { + pub fn filter_tag (&mut self, value: String) { self.filter_tag = 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 { @@ -251,35 +292,45 @@ impl ListTransactionsOptions { if !query.is_empty() { query.push('&'); } - query.push_str(&format!("filter[status]={}", urlencoding::encode(value))); + query.push_str( + &format!("filter[status]={}", urlencoding::encode(value)) + ); } if let Some(value) = &self.filter_since { if !query.is_empty() { query.push('&'); } - query.push_str(&format!("filter[since]={}", urlencoding::encode(value))); + query.push_str( + &format!("filter[since]={}", urlencoding::encode(&value.to_rfc3339())) + ); } if let Some(value) = &self.filter_until { if !query.is_empty() { query.push('&'); } - query.push_str(&format!("filter[until]={}", urlencoding::encode(value))); + query.push_str( + &format!("filter[until]={}", urlencoding::encode(&value.to_rfc3339())) + ); } if let Some(value) = &self.filter_category { if !query.is_empty() { query.push('&'); } - query.push_str(&format!("filter[category]={}", urlencoding::encode(value))); + query.push_str( + &format!("filter[category]={}", urlencoding::encode(value)) + ); } if let Some(value) = &self.filter_tag { if !query.is_empty() { query.push('&'); } - query.push_str(&format!("filter[tag]={}", urlencoding::encode(value))); + query.push_str( + &format!("filter[tag]={}", urlencoding::encode(value)) + ); } if !query.is_empty() { @@ -289,9 +340,20 @@ impl ListTransactionsOptions { } 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 { - let mut url = reqwest::Url::parse(&format!("{}/transactions", BASE_URL)).map_err(error::Error::UrlParse)?; + /// 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 { + 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() @@ -303,14 +365,24 @@ impl Client { 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)?; + 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)?; + 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)) } @@ -318,14 +390,20 @@ impl Client { } /// Retrieve a specific transaction by providing its unique identifier. - pub async fn get_transaction(&self, id : &String) -> Result { - // This assertion is because without an ID the request is thought to be a request for - // many transactions, and therefore the error messages are very unclear. + pub async fn get_transaction( + &self, + id: &String, + ) -> Result { + // This assertion is because without an ID the request is thought to be + // a request for many transactions, 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 url = reqwest::Url::parse( + &format!("{}/transactions/{}", BASE_URL, id) + ).map_err(error::Error::UrlParse)?; let res = reqwest::Client::new() .get(url) @@ -337,22 +415,35 @@ impl Client { 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)?; + 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)?; + 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 { - let mut url = reqwest::Url::parse(&format!("{}/accounts/{}/transactions", BASE_URL, account_id)).map_err(error::Error::UrlParse)?; + /// 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 { + 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() @@ -365,13 +456,15 @@ impl Client { 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)?; + 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)?; + let error: error::ErrorResponse = + serde_json::from_str(&body).map_err(error::Error::Json)?; Err(error::Error::Api(error)) } diff --git a/src/v1/utilities.rs b/src/v1/utilities.rs index 6cde2be..9861dfa 100755 --- a/src/v1/utilities.rs +++ b/src/v1/utilities.rs @@ -6,22 +6,25 @@ use serde::Deserialize; #[derive(Deserialize, Debug)] pub struct PingResponse { - pub meta : Meta, + pub meta: Meta, } #[derive(Deserialize, Debug)] pub struct Meta { /// The unique identifier of the authenticated customer. - pub id : String, + pub id: String, #[serde(rename = "statusEmoji")] /// A cute emoji that represents the response status. - pub status_emoji : String, + pub status_emoji: String, } impl Client { - /// Make a basic ping request to the API. This is useful to verify that authentication is functioning correctly. + /// Make a basic ping request to the API. This is useful to verify that + /// authentication is functioning correctly. pub async fn ping(&self) -> Result { - let url = reqwest::Url::parse(&format!("{}/util/ping", BASE_URL)).map_err(error::Error::UrlParse)?; + let url = reqwest::Url::parse( + &format!("{}/util/ping", BASE_URL) + ).map_err(error::Error::UrlParse)?; let res = reqwest::Client::new() .get(url) @@ -32,15 +35,25 @@ impl Client { match res.status() { 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 ping_response : PingResponse = serde_json::from_str(&body).map_err(error::Error::Json)?; + let ping_response: PingResponse = + serde_json::from_str(&body) + .map_err(error::Error::Json)?; Ok(ping_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)?; + 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)) } diff --git a/src/v1/webhooks.rs b/src/v1/webhooks.rs index 633478c..796db43 100755 --- a/src/v1/webhooks.rs +++ b/src/v1/webhooks.rs @@ -7,220 +7,235 @@ use serde::{Deserialize, Serialize}; #[derive(Deserialize, Debug)] pub struct ListWebhooksResponse { /// The list of webhooks returned in this response. - pub data : Vec, - pub links : ResponseLinks, + pub data: Vec, + pub links: ResponseLinks, } #[derive(Deserialize, Debug)] pub struct GetWebhookResponse { /// The webhook returned in the response. - pub data : WebhookResource, + pub data: WebhookResource, } #[derive(Deserialize, Debug)] pub struct CreateWebhookResponse { /// The webhook that was created. - pub data : WebhookResource, + pub data: WebhookResource, } #[derive(Deserialize, Debug)] pub struct WebhookResource { /// The type of this resource: `webhooks` - pub r#type : String, + pub r#type: String, /// The unique identifier for this webhook. - pub id : String, - pub attributes : Attributes, - pub relationships : Relationships, - pub links : WebhookResourceLinks, + pub id: String, + pub attributes: Attributes, + pub relationships: Relationships, + pub links: WebhookResourceLinks, } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Attributes { /// The URL that this webhook is configured to `POST` events to. - pub url : String, - /// An optional description that was provided at the time the webhook was created. - pub description : Option, - /// A shared secret key used to sign all webhook events sent to the configured webhook URL. This field is returned only once, upon the initial creation of the webhook. If lost, create a new webhook and delete this webhook. - /// The webhook URL receives a request with a `X-Up-Authenticity-Signature` header, which is the SHA-256 HMAC of the entire raw request body signed using this `secretKey`. It is advised to compute and check this signature to verify the authenticity of requests sent to the webhook URL. See Handling webhook events for full details. - pub secret_key : Option, + pub url: String, + /// An optional description that was provided at the time the webhook was + /// created. + pub description: Option, + /// A shared secret key used to sign all webhook events sent to the + /// configured webhook URL. This field is returned only once, upon the + /// initial creation of the webhook. If lost, create a new webhook and + /// delete this webhook. + /// + /// The webhook URL receives a request with a `X-Up-Authenticity-Signature` + /// header, which is the SHA-256 HMAC of the entire raw request body signed + /// using this `secretKey`. It is advised to compute and check this + /// signature to verify the authenticity of requests sent to the webhook + /// URL. See Handling webhook events for full details. + pub secret_key: Option, /// The date-time at which this webhook was created. - pub created_at : String, + pub created_at: String, } #[derive(Deserialize, Debug)] pub struct Relationships { - pub logs : Logs, + pub logs: Logs, } #[derive(Deserialize, Debug)] pub struct Logs { - pub links : Option, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct LogsLinks { /// The link to retrieve the related resource(s) in this relationship. - pub related : String, + pub related: String, } #[derive(Deserialize, Debug)] pub struct WebhookResourceLinks { /// The canonical link to this resource within the API. #[serde(rename = "self")] - pub this : String, + pub this: String, } #[derive(Deserialize, Debug)] pub struct ResponseLinks { - /// The link to the previous page in the results. If this value is `None` there is no previous page. - pub prev : Option, - /// The link to the next page in the results. If this value is `None` there is no next page. - pub next : Option, + /// The link to the previous page in the results. If this value is `None` + /// there is no previous page. + pub prev: Option, + /// The link to the next page in the results. If this value is `None` there + /// is no next page. + pub next: Option, } #[derive(Deserialize, Debug)] pub struct PingWebhookResponse { /// The webhook event data sent to the subscribed webhook. - pub data : WebhookEventResource, + pub data: WebhookEventResource, } #[derive(Deserialize, Debug)] pub struct WebhookEventResource { /// The type of this resource: `webhook-events` - pub r#type : String, - /// The unique identifier for this event. This will remain constant across delivery retries. - pub id : String, - pub attributes : EventAttributes, - pub relationships : EventRelationships, + pub r#type: String, + /// The unique identifier for this event. This will remain constant across + /// delivery retries. + pub id: String, + pub attributes: EventAttributes, + pub relationships: EventRelationships, } #[derive(Deserialize, Debug)] pub struct EventRelationships { - pub webhook : Webhook, - pub transaction : Option, + pub webhook: Webhook, + pub transaction: Option, } #[derive(Deserialize, Debug)] pub struct Transaction { - pub data : TransactionData, - pub links : Option, + pub data: TransactionData, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct Webhook { - pub data : WebhookData, - pub links : Option, + pub data: WebhookData, + pub links: Option, } #[derive(Deserialize, Debug)] pub struct WebhookData { /// The type of this resource: `webhooks` - pub r#type : String, + pub r#type: String, /// The unique identifier of the resource within its type. - pub id : String, + pub id: String, } #[derive(Deserialize, Debug)] pub struct WebhookLinks { /// The link to retrieve the related resource(s) in this relationship. - pub related : String, + pub related: String, } #[derive(Deserialize, Debug)] pub struct TransactionData { /// The type of this resource: `transactions` - pub r#type : String, + pub r#type: String, /// The unique identifier of the resource within its type. - pub id : String, + pub id: String, } #[derive(Deserialize, Debug)] pub struct TransactionLinks { /// The link to retrieve the related resource(s) in this relationship. - pub related : String, + pub related: String, } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct EventAttributes { - /// The type of this event. This can be used to determine what action to take in response to the event. - pub event_type : standard::WebhookEventTypeEnum, + /// The type of this event. This can be used to determine what action to + /// take in response to the event. + pub event_type: standard::WebhookEventTypeEnum, /// The date-time at which this event was generated. - pub created_at : String, + pub created_at: String, } #[derive(Deserialize, Debug)] pub struct ListWebhookLogsResponse { /// The list of delivery logs returned in this response. - pub data : Vec, - pub links : LogsResponseLinks, + pub data: Vec, + pub links: LogsResponseLinks, } #[derive(Deserialize, Debug)] pub struct WebhookDeliveryLogResource { /// The type of this resource: `webhook-delivery-logs` - pub r#type : String, + pub r#type: String, /// The unique identifier for this log entry. - pub id : String, - pub attributes : DeliveryLogAttributes, - pub relationships : DeliveryLogRelationships, + pub id: String, + pub attributes: DeliveryLogAttributes, + pub relationships: DeliveryLogRelationships, } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct DeliveryLogRelationships { - pub webhook_event : WebhookEvent, + pub webhook_event: WebhookEvent, } #[derive(Deserialize, Debug)] pub struct WebhookEvent { - pub data : WebhookEventData + pub data: WebhookEventData } #[derive(Deserialize, Debug)] pub struct WebhookEventData { /// The type of this resource: `webhook-events` - pub r#type : String, + pub r#type: String, /// The unique identifier of the resource within its type. - pub id : String, + pub id: String, } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct DeliveryLogAttributes { /// Information about the request that was sent to the webhook URL. - pub request : Request, + pub request: Request, /// Information about the response that was received from the webhook URL. - pub response : Option, + pub response: Option, /// The success or failure status of this delivery attempt. - pub delivery_status : standard::WebhookDeliveryStatusEnum, + pub delivery_status: standard::WebhookDeliveryStatusEnum, /// The date-time at which this log entry was created. - pub created_at : String, + pub created_at: String, } #[derive(Deserialize, Debug)] pub struct Request { /// The payload that was sent in the request body. - pub body : String, + pub body: String, } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Response { /// The HTTP status code received in the response. - pub status_code : i64, + pub status_code: i64, /// The payload that was received in the response body. - pub body : String, + pub body: String, } #[derive(Deserialize, Debug)] pub struct LogsResponseLinks { - /// The link to the previous page in the results. If this value is `None` there is no previous page. - pub prev : Option, - /// The link to the next page in the results. If this value is `None` there is no next page. - pub next : Option, + /// The link to the previous page in the results. If this value is `None` + /// there is no previous page. + pub prev: Option, + /// The link to the next page in the results. If this value is `None` there + /// is no next page. + pub next: Option, } @@ -229,16 +244,16 @@ pub struct LogsResponseLinks { #[derive(Default)] pub struct ListWebhooksOptions { /// The number of records to return in each page. - page_size : Option, + page_size: Option, } impl ListWebhooksOptions { /// Sets the page size. - pub fn page_size(&mut self, value : u8) { + pub fn page_size(&mut self, value: u8) { self.page_size = 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 { @@ -257,16 +272,16 @@ impl ListWebhooksOptions { #[derive(Default)] pub struct ListWebhookLogsOptions { /// The number of records to return in each page. - page_size : Option, + page_size: Option, } impl ListWebhookLogsOptions { /// Sets the page size. - pub fn page_size(&mut self, value : u8) { + pub fn page_size(&mut self, value: u8) { self.page_size = 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 { @@ -287,26 +302,34 @@ impl ListWebhookLogsOptions { #[derive(Serialize)] pub struct CreateWebhookRequest { /// The webhook resource to create. - pub data : WebhookInputResource, + pub data: WebhookInputResource, } #[derive(Serialize)] pub struct WebhookInputResource { - pub attributes : InputAttributes, + pub attributes: InputAttributes, } #[derive(Serialize)] pub struct InputAttributes { - /// The URL that this webhook should post events to. This must be a valid HTTP or HTTPS URL that does not exceed 300 characters in length. - pub url : String, + /// The URL that this webhook should post events to. This must be a valid + /// HTTP or HTTPS URL that does not exceed 300 characters in length. + pub url: String, /// An optional description for this webhook, up to 64 characters in length. - pub description : Option, + pub description: Option, } impl Client { - /// Retrieve a list of configured webhooks. The returned list is paginated and can be scrolled by following the `next` and `prev` links where present. Results are ordered oldest first to newest last. - pub async fn list_webhooks(&self, options : &ListWebhooksOptions) -> Result { - let mut url = reqwest::Url::parse(&format!("{}/webhooks", BASE_URL)).map_err(error::Error::UrlParse)?; + /// Retrieve a list of configured webhooks. The returned list is paginated + /// and can be scrolled by following the `next` and `prev` links where + /// present. Results are ordered oldest first to newest last. + pub async fn list_webhooks( + &self, + options: &ListWebhooksOptions, + ) -> Result { + let mut url = reqwest::Url::parse( + &format!("{}/webhooks", BASE_URL) + ).map_err(error::Error::UrlParse)?; options.add_params(&mut url); let res = reqwest::Client::new() @@ -318,14 +341,24 @@ impl Client { match res.status() { reqwest::StatusCode::OK => { - let body = res.text().await.map_err(error::Error::BodyRead)?; - let webhook_response : ListWebhooksResponse = serde_json::from_str(&body).map_err(error::Error::Json)?; + let body = + res.text() + .await + .map_err(error::Error::BodyRead)?; + let webhook_response: ListWebhooksResponse = + serde_json::from_str(&body) + .map_err(error::Error::Json)?; Ok(webhook_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)?; + 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)) } @@ -333,14 +366,20 @@ impl Client { } /// Retrieve a specific webhook by providing its unique identifier. - pub async fn get_webhook(&self, id : &str) -> Result { - // This assertion is because without an ID the request is thought to be a request for - // many webhooks, and therefore the error messages are very unclear. + pub async fn get_webhook( + &self, + id: &str, + ) -> Result { + // This assertion is because without an ID the request is thought to be + // a request for many webhooks, and therefore the error messages are + // very unclear. if id.is_empty() { panic!("The provided webhook ID must not be empty."); } - let url = reqwest::Url::parse(&format!("{}/webhooks/{}", BASE_URL, id)).map_err(error::Error::UrlParse)?; + let url = reqwest::Url::parse( + &format!("{}/webhooks/{}", BASE_URL, id) + ).map_err(error::Error::UrlParse)?; let res = reqwest::Client::new() .get(url) @@ -351,34 +390,70 @@ impl Client { match res.status() { reqwest::StatusCode::OK => { - let body = res.text().await.map_err(error::Error::BodyRead)?; - let webhook_response : GetWebhookResponse = serde_json::from_str(&body).map_err(error::Error::Json)?; + let body = + res.text() + .await + .map_err(error::Error::BodyRead)?; + let webhook_response: GetWebhookResponse = + serde_json::from_str(&body) + .map_err(error::Error::Json)?; Ok(webhook_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)?; + 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)) } } } - /// Create a new webhook with a given URL. The URL will receive webhook events as JSON-encoded `POST` requests. The URL must respond with a HTTP `200` status on success. - /// There is currently a limit of 10 webhooks at any given time. Once this limit is reached, existing webhooks will need to be deleted before new webhooks can be created. - /// Event delivery is retried with exponential backoff if the URL is unreachable or it does not respond with a `200` status. The response includes a `secretKey` attribute, which is used to sign requests sent to the webhook URL. It will not be returned from any other endpoints within the Up API. If the `secretKey` is lost, simply create a new webhook with the same URL, capture its `secretKey` and then delete the original webhook. See Handling webhook events for details on how to process webhook events. - /// It is probably a good idea to test the webhook by sending it a `PING` event after creating it. - pub async fn create_webhook(&self, webhook_url : &str, description : Option) -> Result { - let url = reqwest::Url::parse(&format!("{}/webhooks", BASE_URL)).map_err(error::Error::UrlParse)?; + /// Create a new webhook with a given URL. The URL will receive webhook + /// events as JSON-encoded `POST` requests. The URL must respond with a + /// HTTP `200` status on success. + /// + /// There is currently a limit of 10 webhooks at any given time. Once this + /// limit is reached, existing webhooks will need to be deleted before new + /// webhooks can be created. + /// + /// Event delivery is retried with exponential backoff if the URL is + /// unreachable or it does not respond with a `200` status. The response + /// includes a `secretKey` attribute, which is used to sign requests sent + /// to the webhook URL. It will not be returned from any other endpoints + /// within the Up API. If the `secretKey` is lost, simply create a new + /// webhook with the same URL, capture its `secretKey` and then delete the + /// original webhook. See Handling webhook events for details on how to + /// process webhook events. + /// + /// It is probably a good idea to test the webhook by sending it a `PING` + /// event after creating it. + pub async fn create_webhook( + &self, + webhook_url: &str, + description: Option, + ) -> Result { + let url = reqwest::Url::parse( + &format!("{}/webhooks", BASE_URL) + ).map_err(error::Error::UrlParse)?; let body = CreateWebhookRequest { - data : WebhookInputResource { - attributes : InputAttributes { url : String::from(webhook_url), description } + data: WebhookInputResource { + attributes: InputAttributes { + url: String::from(webhook_url), + description, + } } }; - let body = serde_json::to_string(&body).map_err(error::Error::Serialize)?; + let body = + serde_json::to_string(&body) + .map_err(error::Error::Serialize)?; let res = reqwest::Client::new() .post(url) @@ -391,23 +466,36 @@ impl Client { match res.status() { reqwest::StatusCode::CREATED => { - let body = res.text().await.map_err(error::Error::BodyRead)?; - let webhook_response : CreateWebhookResponse = serde_json::from_str(&body).map_err(error::Error::Json)?; + let body = + res.text() + .await + .map_err(error::Error::BodyRead)?; + let webhook_response: CreateWebhookResponse = + serde_json::from_str(&body) + .map_err(error::Error::Json)?; Ok(webhook_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)?; + 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)) } } } - /// Delete a specific webhook by providing its unique identifier. Once deleted, webhook events will no longer be sent to the configured URL. - pub async fn delete_webhook(&self, id : &str) -> Result<(), error::Error> { - let url = reqwest::Url::parse(&format!("{}/webhooks/{}", BASE_URL, id)).map_err(error::Error::UrlParse)?; + /// Delete a specific webhook by providing its unique identifier. Once + /// deleted, webhook events will no longer be sent to the configured URL. + pub async fn delete_webhook(&self, id: &str) -> Result<(), error::Error> { + let url = reqwest::Url::parse( + &format!("{}/webhooks/{}", BASE_URL, id) + ).map_err(error::Error::UrlParse)?; let res = reqwest::Client::new() .delete(url) @@ -421,17 +509,30 @@ impl Client { 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)?; + 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)) } } } - /// Send a `PING` event to a webhook by providing its unique identifier. This is useful for testing and debugging purposes. The event is delivered asynchronously and its data is returned in the response to this request. - pub async fn ping_webhook(&self, id : &str) -> Result { - let url = reqwest::Url::parse(&format!("{}/webhooks/{}/ping", BASE_URL, id)).map_err(error::Error::UrlParse)?; + /// Send a `PING` event to a webhook by providing its unique identifier. + /// This is useful for testing and debugging purposes. The event is + /// delivered asynchronously and its data is returned in the response to + /// this request. + pub async fn ping_webhook( + &self, + id: &str, + ) -> Result { + let url = reqwest::Url::parse( + &format!("{}/webhooks/{}/ping", BASE_URL, id) + ).map_err(error::Error::UrlParse)?; let res = reqwest::Client::new() .post(url) @@ -444,23 +545,43 @@ impl Client { match res.status() { reqwest::StatusCode::CREATED => { - let body = res.text().await.map_err(error::Error::BodyRead)?; - let webhook_response : PingWebhookResponse = serde_json::from_str(&body).map_err(error::Error::Json)?; + let body = + res.text() + .await + .map_err(error::Error::BodyRead)?; + let webhook_response: PingWebhookResponse = + serde_json::from_str(&body) + .map_err(error::Error::Json)?; Ok(webhook_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)?; + 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 delivery logs for a webhook by providing its unique identifier. This is useful for analysis and debugging purposes. The returned list is paginated and can be scrolled by following the `next` and `prev` links where present. Results are ordered newest first to oldest last. Logs may be automatically purged after a period of time. - pub async fn list_webhook_logs(&self, id : &str, options : &ListWebhookLogsOptions) -> Result { - let mut url = reqwest::Url::parse(&format!("{}/webhooks/{}/logs", BASE_URL, id)).map_err(error::Error::UrlParse)?; + /// Retrieve a list of delivery logs for a webhook by providing its unique + /// identifier. This is useful for analysis and debugging purposes. The + /// returned list is paginated and can be scrolled by following the `next` + /// and `prev` links where present. Results are ordered newest first to + /// oldest last. Logs may be automatically purged after a period of time. + pub async fn list_webhook_logs( + &self, + id: &str, + options: &ListWebhookLogsOptions, + ) -> Result { + let mut url = reqwest::Url::parse( + &format!("{}/webhooks/{}/logs", BASE_URL, id) + ).map_err(error::Error::UrlParse)?; options.add_params(&mut url); let res = reqwest::Client::new() @@ -472,14 +593,24 @@ impl Client { match res.status() { reqwest::StatusCode::OK => { - let body = res.text().await.map_err(error::Error::BodyRead)?; - let webhook_response : ListWebhookLogsResponse = serde_json::from_str(&body).map_err(error::Error::Json)?; + let body = + res.text() + .await + .map_err(error::Error::BodyRead)?; + let webhook_response: ListWebhookLogsResponse = + serde_json::from_str(&body) + .map_err(error::Error::Json)?; Ok(webhook_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)?; + 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)) }