1use crate::v1::{Client, error, BASE_URL};
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Deserialize, Debug)]
8pub struct ListCategoriesResponse {
9 pub data : Vec<CategoryResource>,
11}
12
13#[derive(Deserialize, Debug)]
14pub struct GetCategoryResponse {
15 pub data : CategoryResource,
17
18}
19
20#[derive(Deserialize, Debug)]
21pub struct CategoryResource {
22 pub r#type : String,
24 pub id : String,
26 pub attributes : Attributes,
27 pub relationships : Relationships,
28 pub links : Option<CategoryResourceLinks>,
29}
30
31#[derive(Deserialize, Debug)]
32pub struct Attributes {
33 pub name : String,
35}
36
37#[derive(Deserialize, Debug)]
38pub struct Relationships {
39 pub parent : Parent,
40 pub children : Children,
41}
42
43#[derive(Deserialize, Debug)]
44pub struct Parent {
45 pub data : Option<ParentData>,
46 pub links : Option<ParentLinks>,
47}
48
49#[derive(Deserialize, Debug)]
50pub struct ParentData {
51 pub r#type : String,
53 pub id : String,
55}
56
57#[derive(Deserialize, Debug)]
58pub struct ParentLinks {
59 pub related : String,
61}
62
63#[derive(Deserialize, Debug)]
64pub struct Children {
65 pub data : Vec<ChildrenData>,
66 pub links : Option<ChildrenLinks>,
67}
68
69#[derive(Deserialize, Debug)]
70pub struct ChildrenData {
71 pub r#type : String,
73 pub id : String,
75}
76
77#[derive(Deserialize, Debug)]
78pub struct ChildrenLinks {
79 pub related : String,
81}
82
83#[derive(Deserialize, Debug)]
84pub struct CategoryResourceLinks {
85 #[serde(rename = "self")]
87 pub this : String,
88}
89
90#[derive(Default)]
93pub struct ListCategoriesOptions {
94 filter_parent : Option<String>,
96}
97
98impl ListCategoriesOptions {
99 pub fn filter_parent(&mut self, value : String) {
101 self.filter_parent = Some(value);
102 }
103
104 fn add_params(&self, url : &mut reqwest::Url) {
105 let mut query = String::new();
106
107 if let Some(value) = &self.filter_parent {
108 if !query.is_empty() {
109 query.push('&');
110 }
111 query.push_str(&format!("filter[parent]={}", urlencoding::encode(value)));
112 }
113
114 if !query.is_empty() {
115 url.set_query(Some(&query));
116 }
117 }
118}
119
120#[derive(Serialize)]
123struct CategoriseTransactionRequest {
124 data : Option<CategoryInputResourceIdentifier>,
126}
127
128#[derive(Serialize)]
129struct CategoryInputResourceIdentifier {
130 r#type : String,
132 id : String,
134}
135
136impl Client {
137 pub async fn list_categories(&self, options : &ListCategoriesOptions) -> Result<ListCategoriesResponse, error::Error> {
139 let mut url = reqwest::Url::parse(&format!("{}/categories", BASE_URL)).map_err(error::Error::UrlParse)?;
140 options.add_params(&mut url);
141
142 let res = reqwest::Client::new()
143 .get(url)
144 .header("Authorization", self.auth_header())
145 .send()
146 .await
147 .map_err(error::Error::Request)?;
148
149 match res.status() {
150 reqwest::StatusCode::OK => {
151 let body = res.text().await.map_err(error::Error::BodyRead)?;
152 let category_response : ListCategoriesResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
153
154 Ok(category_response)
155 },
156 _ => {
157 let body = res.text().await.map_err(error::Error::BodyRead)?;
158 let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
159
160 Err(error::Error::Api(error))
161 }
162 }
163 }
164
165 pub async fn get_category(&self, id : &str) -> Result<GetCategoryResponse, error::Error> {
167 if id.is_empty() {
170 panic!("The provided category ID must not be empty.");
171 }
172
173 let url = reqwest::Url::parse(&format!("{}/categories/{}", BASE_URL, id)).map_err(error::Error::UrlParse)?;
174
175 let res = reqwest::Client::new()
176 .get(url)
177 .header("Authorization", self.auth_header())
178 .send()
179 .await
180 .map_err(error::Error::Request)?;
181
182 match res.status() {
183 reqwest::StatusCode::OK => {
184 let body = res.text().await.map_err(error::Error::BodyRead)?;
185 let category_response : GetCategoryResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
186
187 Ok(category_response )
188 },
189 _ => {
190 let body = res.text().await.map_err(error::Error::BodyRead)?;
191 let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
192
193 Err(error::Error::Api(error))
194 }
195 }
196 }
197
198 pub async fn categorise_transaction(&self, transaction_id : &str, category : Option<&str>) -> Result<(), error::Error> {
200 let url = reqwest::Url::parse(&format!("{}/transactions/{}/relationships/category", BASE_URL, transaction_id)).map_err(error::Error::UrlParse)?;
201
202 let category = category.map(|id| {
203 CategoryInputResourceIdentifier {
204 r#type : String::from("categories"),
205 id : String::from(id),
206 }
207 });
208
209 let body = CategoriseTransactionRequest { data : category };
210 let body = serde_json::to_string(&body).map_err(error::Error::Serialize)?;
211
212 println!("{}", body);
213
214 let res = reqwest::Client::new()
215 .patch(url)
216 .header("Authorization", self.auth_header())
217 .header("Content-Type", "application/json")
218 .body(body)
219 .send()
220 .await
221 .map_err(error::Error::Request)?;
222
223 match res.status() {
224 reqwest::StatusCode::NO_CONTENT => {
225 Ok(())
226 },
227 _ => {
228 let body = res.text().await.map_err(error::Error::BodyRead)?;
229 let error : error::ErrorResponse = serde_json::from_str(&body).map_err(error::Error::Json)?;
230
231 Err(error::Error::Api(error))
232 }
233 }
234 }
235}