Switch to async reqwest and colour function all the way up
This commit is contained in:
Generated
+13
@@ -1446,9 +1446,21 @@ dependencies = [
|
|||||||
"mio",
|
"mio",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.26.4"
|
version = "0.26.4"
|
||||||
@@ -1954,6 +1966,7 @@ dependencies = [
|
|||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ directories = "6.0.0"
|
|||||||
reqwest = { version = "0.13.3", features = ["blocking"] }
|
reqwest = { version = "0.13.3", features = ["blocking"] }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
|
tokio = { version = "1.52.1", features = ["macros", "rt-multi-thread"] }
|
||||||
|
|||||||
+7
-6
@@ -111,7 +111,7 @@ fn read_transactions_from(
|
|||||||
vec![Box::new(stdin()) as Box<dyn Read>]
|
vec![Box::new(stdin()) as Box<dyn Read>]
|
||||||
} else {
|
} else {
|
||||||
inputs
|
inputs
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|p| -> Result<Box<dyn Read>, _> { Ok(Box::new(File::open(p)?)) })
|
.map(|p| -> Result<Box<dyn Read>, _> { Ok(Box::new(File::open(p)?)) })
|
||||||
.collect::<Result<Vec<_>, std::io::Error>>()?
|
.collect::<Result<Vec<_>, std::io::Error>>()?
|
||||||
}
|
}
|
||||||
@@ -123,12 +123,13 @@ fn read_transactions_from(
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Command::Plans { token } => {
|
Command::Plans { token } => {
|
||||||
let plans = Ynab::new(&token).list_plans()?;
|
let plans = Ynab::new(&token).list_plans().await?;
|
||||||
println!("Available plans:");
|
println!("Available plans:");
|
||||||
for plan in plans {
|
for plan in plans {
|
||||||
println!(" - {}", plan);
|
println!(" - {}", plan);
|
||||||
@@ -141,7 +142,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
plan_id,
|
plan_id,
|
||||||
} => {
|
} => {
|
||||||
let plan = Lookup::from_options(plan, plan_id)?;
|
let plan = Lookup::from_options(plan, plan_id)?;
|
||||||
let accounts = Ynab::list_accounts(&Ynab::new(&token), plan)?;
|
let accounts = Ynab::list_accounts(&Ynab::new(&token), plan).await?;
|
||||||
println!("Available accounts in plan:");
|
println!("Available accounts in plan:");
|
||||||
for account in accounts {
|
for account in accounts {
|
||||||
println!(" - {}", account);
|
println!(" - {}", account);
|
||||||
@@ -169,7 +170,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
Some(Lookup::from_options(name, id)?)
|
Some(Lookup::from_options(name, id)?)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let transactions = Ynab::new(&token).list_transactions(plan, account)?;
|
let transactions = Ynab::new(&token).list_transactions(plan, account).await?;
|
||||||
println!("Transactions{} in the last 30 days:", accountstr);
|
println!("Transactions{} in the last 30 days:", accountstr);
|
||||||
for t in transactions {
|
for t in transactions {
|
||||||
println!("{},{},{}", t.date, t.payee, t.amount);
|
println!("{},{},{}", t.date, t.payee, t.amount);
|
||||||
@@ -191,7 +192,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
|
|
||||||
let transactions = read_transactions_from(&inputs, &format)?;
|
let transactions = read_transactions_from(&inputs, &format)?;
|
||||||
|
|
||||||
Ynab::new(&token).upload(&transactions, plan, account)
|
Ynab::new(&token).upload(&transactions, plan, account).await
|
||||||
}
|
}
|
||||||
Command::Convert { format, inputs } => {
|
Command::Convert { format, inputs } => {
|
||||||
let transactions = read_transactions_from(&inputs, &format)?;
|
let transactions = read_transactions_from(&inputs, &format)?;
|
||||||
|
|||||||
+33
-20
@@ -5,24 +5,27 @@ use crate::Transaction;
|
|||||||
|
|
||||||
pub struct Ynab {
|
pub struct Ynab {
|
||||||
token: String,
|
token: String,
|
||||||
webclient: reqwest::blocking::Client,
|
webclient: reqwest::Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ynab {
|
impl Ynab {
|
||||||
pub fn new(token: &str) -> Ynab {
|
pub fn new(token: &str) -> Ynab {
|
||||||
Ynab {
|
Ynab {
|
||||||
token: token.to_owned(),
|
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 {
|
match plan {
|
||||||
Lookup::Id(id) => Ok(id),
|
Lookup::Id(id) => Ok(id),
|
||||||
Lookup::Name(name) => {
|
Lookup::Name(name) => {
|
||||||
let url = "plans";
|
let url = "plans";
|
||||||
let res: types::YnabResponse<types::YnabPlanList> = serde_json::from_str(
|
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))?;
|
.map_err(|e| format!("parse error: {}", e))?;
|
||||||
let plans = res.data.plans;
|
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 {
|
match account {
|
||||||
Lookup::Id(id) => Ok(id),
|
Lookup::Id(id) => Ok(id),
|
||||||
Lookup::Name(name) => {
|
Lookup::Name(name) => {
|
||||||
@@ -43,6 +46,7 @@ impl Ynab {
|
|||||||
let res: types::YnabResponse<types::YnabAccountList> = serde_json::from_str(
|
let res: types::YnabResponse<types::YnabAccountList> = serde_json::from_str(
|
||||||
&self
|
&self
|
||||||
.get(&url)
|
.get(&url)
|
||||||
|
.await
|
||||||
.map_err(|e| format!("request error: {}", e))?,
|
.map_err(|e| format!("request error: {}", e))?,
|
||||||
)
|
)
|
||||||
.map_err(|e| format!("parse 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/";
|
const URL: &str = "https://api.ynab.com/v1/";
|
||||||
let request = format!("{}{}", URL, request);
|
let request = format!("{}{}", URL, request);
|
||||||
let res = self
|
let res = self
|
||||||
.webclient
|
.webclient
|
||||||
.get(request)
|
.get(request)
|
||||||
.bearer_auth(&self.token)
|
.bearer_auth(&self.token)
|
||||||
.send()?;
|
.send()
|
||||||
let text = res.text()?;
|
.await?;
|
||||||
|
let text = res.text().await?;
|
||||||
Ok(text)
|
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/";
|
const URL: &str = "https://api.ynab.com/v1/";
|
||||||
let request = format!("{}{}", URL, request);
|
let request = format!("{}{}", URL, request);
|
||||||
let res = self
|
let res = self
|
||||||
@@ -78,12 +83,14 @@ impl Ynab {
|
|||||||
.bearer_auth(&self.token)
|
.bearer_auth(&self.token)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.send()
|
.send()
|
||||||
|
.await
|
||||||
.map_err(|e| format!("post error: {}", e))?;
|
.map_err(|e| format!("post error: {}", e))?;
|
||||||
if res.status().is_success() {
|
if res.status().is_success() {
|
||||||
println!(
|
println!(
|
||||||
"successful api response {}: {}",
|
"successful api response {}: {}",
|
||||||
res.status(),
|
res.status(),
|
||||||
res.text()
|
res.text()
|
||||||
|
.await
|
||||||
.map_err(|e| format!("error retrieving api error: {}", e))?
|
.map_err(|e| format!("error retrieving api error: {}", e))?
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -92,48 +99,54 @@ impl Ynab {
|
|||||||
"api error: {}: {}",
|
"api error: {}: {}",
|
||||||
res.status(),
|
res.status(),
|
||||||
res.text()
|
res.text()
|
||||||
|
.await
|
||||||
.map_err(|e| format!("error retrieving api error: {}", e))?
|
.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 url = String::from("plans");
|
||||||
let res: types::YnabResponse<types::YnabPlanList> = serde_json::from_str(
|
let res: types::YnabResponse<types::YnabPlanList> = serde_json::from_str(
|
||||||
&self
|
&self
|
||||||
.get(&url)
|
.get(&url)
|
||||||
|
.await
|
||||||
.map_err(|e| format!("request error: {}", e))?,
|
.map_err(|e| format!("request error: {}", e))?,
|
||||||
)
|
)
|
||||||
.map_err(|e| format!("parse error: {}", e))?;
|
.map_err(|e| format!("parse error: {}", e))?;
|
||||||
Ok(res.data.plans.into_iter().map(|p| p.name).collect())
|
Ok(res.data.plans.into_iter().map(|p| p.name).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_accounts(&self, plan: Lookup) -> Result<Vec<String>, String> {
|
pub async fn list_accounts(&self, plan: Lookup) -> Result<Vec<String>, String> {
|
||||||
let plan_id = self.resolve_plan(plan)?;
|
let plan_id = self.resolve_plan(plan).await?;
|
||||||
let url = format!("plans/{}/accounts", plan_id);
|
let url = format!("plans/{}/accounts", plan_id);
|
||||||
let res: types::YnabResponse<types::YnabAccountList> = serde_json::from_str(
|
let res: types::YnabResponse<types::YnabAccountList> = serde_json::from_str(
|
||||||
&self
|
&self
|
||||||
.get(&url)
|
.get(&url)
|
||||||
|
.await
|
||||||
.map_err(|e| format!("request error: {}", e))?,
|
.map_err(|e| format!("request error: {}", e))?,
|
||||||
)
|
)
|
||||||
.map_err(|e| format!("parse error: {}", e))?;
|
.map_err(|e| format!("parse error: {}", e))?;
|
||||||
Ok(res.data.accounts.into_iter().map(|p| p.name).collect())
|
Ok(res.data.accounts.into_iter().map(|p| p.name).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_transactions(
|
pub async fn list_transactions(
|
||||||
&self,
|
&self,
|
||||||
plan: Lookup,
|
plan: Lookup,
|
||||||
account: Option<Lookup>,
|
account: Option<Lookup>,
|
||||||
) -> Result<Vec<Transaction>, String> {
|
) -> Result<Vec<Transaction>, String> {
|
||||||
let plan_id = self.resolve_plan(plan)?;
|
let plan_id = self.resolve_plan(plan).await?;
|
||||||
let account_id = match account {
|
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,
|
None => None,
|
||||||
};
|
};
|
||||||
let since_date = chrono::Local::now().date_naive() - chrono::Duration::days(30);
|
let since_date = chrono::Local::now().date_naive() - chrono::Duration::days(30);
|
||||||
let since_date = since_date.format("%Y-%m-%d");
|
let since_date = since_date.format("%Y-%m-%d");
|
||||||
let url = format!("plans/{}/transactions?since_date={}", plan_id, since_date);
|
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> =
|
let res: types::YnabResponse<types::YnabTransactionList> =
|
||||||
serde_json::from_str(&raw).map_err(|e| format!("parse error: {}", e))?;
|
serde_json::from_str(&raw).map_err(|e| format!("parse error: {}", e))?;
|
||||||
Ok(transform::from_ynab_transactions(
|
Ok(transform::from_ynab_transactions(
|
||||||
@@ -142,20 +155,20 @@ impl Ynab {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upload(
|
pub async fn upload(
|
||||||
&self,
|
&self,
|
||||||
transactions: &[Transaction],
|
transactions: &[Transaction],
|
||||||
plan: Lookup,
|
plan: Lookup,
|
||||||
account: Lookup,
|
account: Lookup,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let plan_id = self.resolve_plan(plan)?;
|
let plan_id = self.resolve_plan(plan).await?;
|
||||||
let account_id = self.resolve_account(&plan_id, account)?;
|
let account_id = self.resolve_account(&plan_id, account).await?;
|
||||||
let request = format!("plans/{}/transactions", plan_id);
|
let request = format!("plans/{}/transactions", plan_id);
|
||||||
let body = serde_json::to_string(&types::YnabTransactionList {
|
let body = serde_json::to_string(&types::YnabTransactionList {
|
||||||
transactions: transform::to_ynab_transactions(transactions, &account_id),
|
transactions: transform::to_ynab_transactions(transactions, &account_id),
|
||||||
})
|
})
|
||||||
.map_err(|e| format!("transaction format error: {}", e))?;
|
.map_err(|e| format!("transaction format error: {}", e))?;
|
||||||
self.post(&request, &body)?;
|
self.post(&request, &body).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user