Switch to async reqwest and colour in functions all the way up

This commit is contained in:
2026-05-03 10:36:43 +02:00
parent deda785c2a
commit a9de66a49f
4 changed files with 53 additions and 25 deletions
+33 -20
View File
@@ -5,24 +5,27 @@ use crate::Transaction;
pub struct Ynab {
token: String,
webclient: reqwest::blocking::Client,
webclient: reqwest::Client,
}
impl Ynab {
pub fn new(token: &str) -> Ynab {
Ynab {
token: token.to_owned(),
webclient: reqwest::blocking::Client::new(),
webclient: reqwest::Client::new(),
}
}
fn resolve_plan(&self, plan: Lookup) -> Result<String, String> {
async fn resolve_plan(&self, plan: Lookup) -> Result<String, String> {
match plan {
Lookup::Id(id) => Ok(id),
Lookup::Name(name) => {
let url = "plans";
let res: types::YnabResponse<types::YnabPlanList> = serde_json::from_str(
&self.get(url).map_err(|e| format!("request error: {}", e))?,
&self
.get(url)
.await
.map_err(|e| format!("request error: {}", e))?,
)
.map_err(|e| format!("parse error: {}", e))?;
let plans = res.data.plans;
@@ -35,7 +38,7 @@ impl Ynab {
}
}
fn resolve_account(&self, plan_id: &str, account: Lookup) -> Result<String, String> {
async fn resolve_account(&self, plan_id: &str, account: Lookup) -> Result<String, String> {
match account {
Lookup::Id(id) => Ok(id),
Lookup::Name(name) => {
@@ -43,6 +46,7 @@ impl Ynab {
let res: types::YnabResponse<types::YnabAccountList> = serde_json::from_str(
&self
.get(&url)
.await
.map_err(|e| format!("request error: {}", e))?,
)
.map_err(|e| format!("parse error: {}", e))?;
@@ -56,19 +60,20 @@ impl Ynab {
}
}
pub(crate) fn get(&self, request: &str) -> Result<String, reqwest::Error> {
pub(crate) async fn get(&self, request: &str) -> Result<String, reqwest::Error> {
const URL: &str = "https://api.ynab.com/v1/";
let request = format!("{}{}", URL, request);
let res = self
.webclient
.get(request)
.bearer_auth(&self.token)
.send()?;
let text = res.text()?;
.send()
.await?;
let text = res.text().await?;
Ok(text)
}
fn post(&self, request: &str, body: &str) -> Result<(), String> {
async fn post(&self, request: &str, body: &str) -> Result<(), String> {
const URL: &str = "https://api.ynab.com/v1/";
let request = format!("{}{}", URL, request);
let res = self
@@ -78,12 +83,14 @@ impl Ynab {
.bearer_auth(&self.token)
.header("Content-Type", "application/json")
.send()
.await
.map_err(|e| format!("post error: {}", e))?;
if res.status().is_success() {
println!(
"successful api response {}: {}",
res.status(),
res.text()
.await
.map_err(|e| format!("error retrieving api error: {}", e))?
);
Ok(())
@@ -92,48 +99,54 @@ impl Ynab {
"api error: {}: {}",
res.status(),
res.text()
.await
.map_err(|e| format!("error retrieving api error: {}", e))?
))
}
}
pub fn list_plans(&self) -> Result<Vec<String>, String> {
pub async fn list_plans(&self) -> Result<Vec<String>, String> {
let url = String::from("plans");
let res: types::YnabResponse<types::YnabPlanList> = serde_json::from_str(
&self
.get(&url)
.await
.map_err(|e| format!("request error: {}", e))?,
)
.map_err(|e| format!("parse error: {}", e))?;
Ok(res.data.plans.into_iter().map(|p| p.name).collect())
}
pub fn list_accounts(&self, plan: Lookup) -> Result<Vec<String>, String> {
let plan_id = self.resolve_plan(plan)?;
pub async fn list_accounts(&self, plan: Lookup) -> Result<Vec<String>, String> {
let plan_id = self.resolve_plan(plan).await?;
let url = format!("plans/{}/accounts", plan_id);
let res: types::YnabResponse<types::YnabAccountList> = serde_json::from_str(
&self
.get(&url)
.await
.map_err(|e| format!("request error: {}", e))?,
)
.map_err(|e| format!("parse error: {}", e))?;
Ok(res.data.accounts.into_iter().map(|p| p.name).collect())
}
pub fn list_transactions(
pub async fn list_transactions(
&self,
plan: Lookup,
account: Option<Lookup>,
) -> Result<Vec<Transaction>, String> {
let plan_id = self.resolve_plan(plan)?;
let plan_id = self.resolve_plan(plan).await?;
let account_id = match account {
Some(account) => Some(self.resolve_account(&plan_id, account)?),
Some(account) => Some(self.resolve_account(&plan_id, account).await?),
None => None,
};
let since_date = chrono::Local::now().date_naive() - chrono::Duration::days(30);
let since_date = since_date.format("%Y-%m-%d");
let url = format!("plans/{}/transactions?since_date={}", plan_id, since_date);
let raw = self.get(&url).map_err(|e| format!("request error {}", e))?;
let raw = self
.get(&url)
.await
.map_err(|e| format!("request error {}", e))?;
let res: types::YnabResponse<types::YnabTransactionList> =
serde_json::from_str(&raw).map_err(|e| format!("parse error: {}", e))?;
Ok(transform::from_ynab_transactions(
@@ -142,20 +155,20 @@ impl Ynab {
))
}
pub fn upload(
pub async fn upload(
&self,
transactions: &[Transaction],
plan: Lookup,
account: Lookup,
) -> Result<(), Box<dyn std::error::Error>> {
let plan_id = self.resolve_plan(plan)?;
let account_id = self.resolve_account(&plan_id, account)?;
let plan_id = self.resolve_plan(plan).await?;
let account_id = self.resolve_account(&plan_id, account).await?;
let request = format!("plans/{}/transactions", plan_id);
let body = serde_json::to_string(&types::YnabTransactionList {
transactions: transform::to_ynab_transactions(transactions, &account_id),
})
.map_err(|e| format!("transaction format error: {}", e))?;
self.post(&request, &body)?;
self.post(&request, &body).await?;
Ok(())
}
}