Start refactor of ynab by splitting it up
This commit is contained in:
+3
-3
@@ -115,7 +115,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
plan,
|
||||
plan_id,
|
||||
} => {
|
||||
let plan = Lookup::try_from((plan, plan_id))?;
|
||||
let plan = Lookup::from_options(plan, plan_id)?;
|
||||
let accounts = Ynab::new(&token).list_accounts(plan)?;
|
||||
println!("Available accounts in plan:");
|
||||
for account in accounts {
|
||||
@@ -132,8 +132,8 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
format,
|
||||
inputs,
|
||||
} => {
|
||||
let plan = Lookup::try_from((plan, plan_id))?;
|
||||
let account = Lookup::try_from((account, account_id))?;
|
||||
let plan = Lookup::from_options(plan, plan_id)?;
|
||||
let account = Lookup::from_options(account, account_id)?;
|
||||
|
||||
let transactions = read_transactions_from(&inputs, &format)?;
|
||||
|
||||
|
||||
@@ -1,65 +1,7 @@
|
||||
use super::lookup::Lookup;
|
||||
use super::transform;
|
||||
use super::types;
|
||||
use crate::Transaction;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct YnabTransaction {
|
||||
import_id: String,
|
||||
date: String,
|
||||
amount: i64,
|
||||
payee_name: String,
|
||||
account_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct YnabResponse<T> {
|
||||
data: T,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct YnabPlanList {
|
||||
plans: Vec<YnabPlan>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct YnabPlan {
|
||||
id: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct YnabAccountList {
|
||||
accounts: Vec<YnabAccount>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct YnabAccount {
|
||||
id: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct YnabTransactionList {
|
||||
transactions: Vec<YnabTransaction>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Lookup {
|
||||
Name(String),
|
||||
Id(String),
|
||||
}
|
||||
|
||||
impl TryFrom<(Option<String>, Option<String>)> for Lookup {
|
||||
type Error = String;
|
||||
|
||||
fn try_from((name, id): (Option<String>, Option<String>)) -> Result<Self, Self::Error> {
|
||||
match (name, id) {
|
||||
(Some(name), _) => Ok(Lookup::Name(name)),
|
||||
(_, Some(id)) => Ok(Lookup::Id(id)),
|
||||
_ => Err("must provide name or id".to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Ynab {
|
||||
token: String,
|
||||
@@ -79,7 +21,7 @@ impl Ynab {
|
||||
Lookup::Id(id) => Ok(id),
|
||||
Lookup::Name(name) => {
|
||||
let url = "plans";
|
||||
let res: YnabResponse<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))?,
|
||||
)
|
||||
.map_err(|e| format!("parse error: {}", e))?;
|
||||
@@ -98,7 +40,7 @@ impl Ynab {
|
||||
Lookup::Id(id) => Ok(id),
|
||||
Lookup::Name(name) => {
|
||||
let url = format!("plans/{}/accounts", plan_id);
|
||||
let res: YnabResponse<YnabAccountList> = serde_json::from_str(
|
||||
let res: types::YnabResponse<types::YnabAccountList> = serde_json::from_str(
|
||||
&self
|
||||
.get(&url)
|
||||
.map_err(|e| format!("request error: {}", e))?,
|
||||
@@ -114,7 +56,7 @@ impl Ynab {
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, request: &str) -> Result<String, reqwest::Error> {
|
||||
pub(crate) 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
|
||||
@@ -151,7 +93,7 @@ impl Ynab {
|
||||
|
||||
pub fn list_plans(&self) -> Result<Vec<String>, String> {
|
||||
let url = String::from("plans");
|
||||
let res: YnabResponse<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))?,
|
||||
@@ -163,7 +105,7 @@ impl Ynab {
|
||||
pub fn list_accounts(&self, plan: Lookup) -> Result<Vec<String>, String> {
|
||||
let plan_id = self.resolve_plan(plan)?;
|
||||
let url = format!("plans/{}/accounts", plan_id);
|
||||
let res: YnabResponse<YnabAccountList> = serde_json::from_str(
|
||||
let res: types::YnabResponse<types::YnabAccountList> = serde_json::from_str(
|
||||
&self
|
||||
.get(&url)
|
||||
.map_err(|e| format!("request error: {}", e))?,
|
||||
@@ -172,32 +114,6 @@ impl Ynab {
|
||||
Ok(res.data.accounts.into_iter().map(|p| p.name).collect())
|
||||
}
|
||||
|
||||
fn ynab_transactions(
|
||||
&self,
|
||||
transactions: &[Transaction],
|
||||
account_id: &str,
|
||||
) -> Vec<YnabTransaction> {
|
||||
let mut result = Vec::new();
|
||||
let mut idmap: HashMap<(String, i64), u32> = HashMap::new();
|
||||
for t in transactions {
|
||||
let key = (t.date.clone(), t.amount);
|
||||
let id = {
|
||||
let n = idmap.entry(key).or_insert(0);
|
||||
*n += 1;
|
||||
*n
|
||||
};
|
||||
let y = YnabTransaction {
|
||||
import_id: format!("YNAB:{}:{}:{}", t.amount, t.date, id),
|
||||
date: t.date.clone(),
|
||||
amount: t.amount,
|
||||
payee_name: t.payee.clone(),
|
||||
account_id: account_id.to_string(),
|
||||
};
|
||||
result.push(y);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn upload(
|
||||
&self,
|
||||
transactions: &[Transaction],
|
||||
@@ -207,8 +123,8 @@ impl Ynab {
|
||||
let plan_id = self.resolve_plan(plan)?;
|
||||
let account_id = self.resolve_account(&plan_id, account)?;
|
||||
let request = format!("plans/{}/transactions", plan_id);
|
||||
let body = serde_json::to_string(&YnabTransactionList {
|
||||
transactions: self.ynab_transactions(transactions, &account_id),
|
||||
let body = serde_json::to_string(&types::YnabTransactionList {
|
||||
transactions: transform::ynab_transactions(transactions, &account_id),
|
||||
})
|
||||
.map_err(|e| format!("transaction format error: {}", e))?;
|
||||
println!("{}", body);
|
||||
@@ -0,0 +1,14 @@
|
||||
pub enum Lookup {
|
||||
Name(String),
|
||||
Id(String),
|
||||
}
|
||||
|
||||
impl Lookup {
|
||||
pub fn from_options(name: Option<String>, id: Option<String>) -> Result<Self, String> {
|
||||
match (name, id) {
|
||||
(Some(name), _) => Ok(Self::Name(name)),
|
||||
(_, Some(id)) => Ok(Self::Id(id)),
|
||||
_ => Err("must provide name or id".to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
mod lookup;
|
||||
pub use lookup::Lookup;
|
||||
|
||||
mod types;
|
||||
|
||||
mod client;
|
||||
pub use client::Ynab;
|
||||
|
||||
mod transform;
|
||||
@@ -0,0 +1,28 @@
|
||||
use super::types;
|
||||
use crate::Transaction;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub(crate) fn ynab_transactions(
|
||||
transactions: &[Transaction],
|
||||
account_id: &str,
|
||||
) -> Vec<types::YnabTransaction> {
|
||||
let mut result = Vec::new();
|
||||
let mut idmap: HashMap<(String, i64), u32> = HashMap::new();
|
||||
for t in transactions {
|
||||
let key = (t.date.clone(), t.amount);
|
||||
let id = {
|
||||
let n = idmap.entry(key).or_insert(0);
|
||||
*n += 1;
|
||||
*n
|
||||
};
|
||||
let y = types::YnabTransaction {
|
||||
import_id: format!("YNAB:{}:{}:{}", t.amount, t.date, id),
|
||||
date: t.date.clone(),
|
||||
amount: t.amount,
|
||||
payee_name: t.payee.clone(),
|
||||
account_id: account_id.to_string(),
|
||||
};
|
||||
result.push(y);
|
||||
}
|
||||
result
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct YnabTransaction {
|
||||
pub(crate) import_id: String,
|
||||
pub(crate) date: String,
|
||||
pub(crate) amount: i64,
|
||||
pub(crate) payee_name: String,
|
||||
pub(crate) account_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub(crate) struct YnabResponse<T> {
|
||||
pub(crate) data: T,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub(crate) struct YnabPlanList {
|
||||
pub(crate) plans: Vec<YnabPlan>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub(crate) struct YnabPlan {
|
||||
pub(crate) id: String,
|
||||
pub(crate) name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub(crate) struct YnabAccountList {
|
||||
pub(crate) accounts: Vec<YnabAccount>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub(crate) struct YnabAccount {
|
||||
pub(crate) id: String,
|
||||
pub(crate) name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub(crate) struct YnabTransactionList {
|
||||
pub(crate) transactions: Vec<YnabTransaction>,
|
||||
}
|
||||
Reference in New Issue
Block a user