use crate::v1::{Client, error, BASE_URL};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Debug)]
pub struct ListCategoriesResponse {
pub data: Vec<CategoryResource>,
}
#[derive(Deserialize, Debug)]
pub struct GetCategoryResponse {
pub data: CategoryResource,
}
#[derive(Deserialize, Debug)]
pub struct CategoryResource {
pub r#type: String,
pub id: String,
pub attributes: Attributes,
pub relationships: Relationships,
pub links: Option<CategoryResourceLinks>,
}
#[derive(Deserialize, Debug)]
pub struct Attributes {
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 {
pub r#type: String,
pub id: String,
}
#[derive(Deserialize, Debug)]
pub struct ParentLinks {
pub related: String,
}
#[derive(Deserialize, Debug)]
pub struct Children {
pub data: Vec<ChildrenData>,
pub links: Option<ChildrenLinks>,
}
#[derive(Deserialize, Debug)]
pub struct ChildrenData {
pub r#type: String,
pub id: String,
}
#[derive(Deserialize, Debug)]
pub struct ChildrenLinks {
pub related: String,
}
#[derive(Deserialize, Debug)]
pub struct CategoryResourceLinks {
#[serde(rename = "self")]
pub this: String,
}
#[derive(Default)]
pub struct ListCategoriesOptions {
filter_parent: Option<String>,
}
impl ListCategoriesOptions {
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]={}", urlencoding::encode(value))
);
}
if !query.is_empty() {
url.set_query(Some(&query));
}
}
}
#[derive(Serialize)]
struct CategoriseTransactionRequest {
data: Option<CategoryInputResourceIdentifier>,
}
#[derive(Serialize)]
struct CategoryInputResourceIdentifier {
r#type: String,
id: String,
}
impl Client {
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))
}
}
}
pub async fn get_category(
&self, id: &str,
) -> Result<GetCategoryResponse, error::Error> {
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 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))
}
}
}
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))
}
}
}
}