Handle errors more gracefully than panicking
parent
f35cb57a79
commit
999c9922b5
|
@ -32,6 +32,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.65"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-channel"
|
name = "async-channel"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
@ -2303,6 +2309,7 @@ dependencies = [
|
||||||
name = "ynabifier"
|
name = "ynabifier"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"async-imap",
|
"async-imap",
|
||||||
"async-native-tls",
|
"async-native-tls",
|
||||||
"async-std",
|
"async-std",
|
||||||
|
|
|
@ -24,6 +24,7 @@ chrono = "0.4.22"
|
||||||
regex = "1.6"
|
regex = "1.6"
|
||||||
reqwest = { version = "0.11.12", features = ["json"] }
|
reqwest = { version = "0.11.12", features = ["json"] }
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
|
anyhow = "1.0"
|
||||||
|
|
||||||
# For annoying reasons, we must pin exactly the same versions as async-imap if we want to use
|
# For annoying reasons, we must pin exactly the same versions as async-imap if we want to use
|
||||||
# their types.
|
# their types.
|
||||||
|
|
|
@ -190,9 +190,15 @@ where
|
||||||
debug!("idling");
|
debug!("idling");
|
||||||
let sequence_number = Self::idle_for_email(idle_handle).await?;
|
let sequence_number = Self::idle_for_email(idle_handle).await?;
|
||||||
debug!("got email");
|
debug!("got email");
|
||||||
// TODO: handle these failures
|
let send_res = sender.send(sequence_number).await;
|
||||||
sender.send(sequence_number).await.expect("failed to send");
|
if let Err(err) = send_res {
|
||||||
debug!("sent");
|
error!(
|
||||||
|
"Successfully fetched, but failed to dispatch email with sequence number {}: {}",
|
||||||
|
sequence_number, err
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
debug!("sent");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
debug!("done");
|
debug!("done");
|
||||||
|
|
||||||
|
|
81
src/main.rs
81
src/main.rs
|
@ -3,7 +3,7 @@ extern crate log;
|
||||||
|
|
||||||
use futures::{stream::StreamExt, Future};
|
use futures::{stream::StreamExt, Future};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use std::{fs::File, sync::Arc};
|
use std::{fs::File, process, sync::Arc};
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use ynabifier::parse::Transaction;
|
use ynabifier::parse::Transaction;
|
||||||
use ynabifier::{
|
use ynabifier::{
|
||||||
|
@ -14,39 +14,28 @@ use ynabifier::{
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let config_file = File::open("config.yml").expect("failed to open config file");
|
let config_res = load_config("config.yml");
|
||||||
let config =
|
if let Err(err) = config_res {
|
||||||
serde_yaml::from_reader::<_, Config>(config_file).expect("failed to parse config file");
|
eprintln!("Failed to load configuration: {}", err);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
setup_logger(config.log_level()).expect("failed to seutp logger");
|
let config = config_res.unwrap();
|
||||||
|
if let Err(err) = setup_logger(config.log_level()) {
|
||||||
|
eprintln!("Failed to setup logger: {}", err);
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
let runtime = Runtime::new().expect("failed to create runtime");
|
if let Err(err) = listen_for_transactions(&config) {
|
||||||
runtime.block_on(async move {
|
error!("Failed to listen for transaction emails: {}", err);
|
||||||
let ynab_client = YNABClient::new(config.ynab().personal_access_token().to_string());
|
process::exit(2);
|
||||||
let mut stream =
|
}
|
||||||
ynabifier::stream_new_messages(Arc::new(TokioSpawner), config.imap().clone())
|
}
|
||||||
.await
|
|
||||||
.expect("failed to setup stream");
|
|
||||||
|
|
||||||
let accounts = config.ynab().accounts();
|
fn load_config(filename: &str) -> Result<Config, anyhow::Error> {
|
||||||
while let Some(msg) = stream.next().await {
|
let config_file = File::open(filename)?;
|
||||||
if let Some((account, transaction)) = try_parse_email(accounts.iter(), &msg) {
|
let config = serde_yaml::from_reader(config_file)?;
|
||||||
info!(
|
Ok(config)
|
||||||
"Parsed transaction for {} to {} with parser {}",
|
|
||||||
transaction.amount(),
|
|
||||||
transaction.payee(),
|
|
||||||
account.parser_name(),
|
|
||||||
);
|
|
||||||
submit_transaction(
|
|
||||||
&ynab_client,
|
|
||||||
&transaction,
|
|
||||||
config.ynab().budeget_id(),
|
|
||||||
account.id(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_logger(log_level: LevelFilter) -> Result<(), fern::InitError> {
|
fn setup_logger(log_level: LevelFilter) -> Result<(), fern::InitError> {
|
||||||
|
@ -68,6 +57,36 @@ fn setup_logger(log_level: LevelFilter) -> Result<(), fern::InitError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn listen_for_transactions(config: &Config) -> Result<(), anyhow::Error> {
|
||||||
|
let runtime = Runtime::new()?;
|
||||||
|
runtime.block_on(async move {
|
||||||
|
let ynab_client = YNABClient::new(config.ynab().personal_access_token().to_string());
|
||||||
|
let mut stream =
|
||||||
|
ynabifier::stream_new_messages(Arc::new(TokioSpawner), config.imap().clone()).await?;
|
||||||
|
|
||||||
|
let accounts = config.ynab().accounts();
|
||||||
|
while let Some(msg) = stream.next().await {
|
||||||
|
if let Some((account, transaction)) = try_parse_email(accounts.iter(), &msg) {
|
||||||
|
info!(
|
||||||
|
"Parsed transaction for {} to {} with parser {}",
|
||||||
|
transaction.amount(),
|
||||||
|
transaction.payee(),
|
||||||
|
account.parser_name(),
|
||||||
|
);
|
||||||
|
submit_transaction(
|
||||||
|
&ynab_client,
|
||||||
|
&transaction,
|
||||||
|
config.ynab().budeget_id(),
|
||||||
|
account.id(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn try_parse_email<'a, I>(ynab_accounts: I, msg: &Message) -> Option<(&'a YNABAccount, Transaction)>
|
fn try_parse_email<'a, I>(ynab_accounts: I, msg: &Message) -> Option<(&'a YNABAccount, Transaction)>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = &'a YNABAccount>,
|
I: Iterator<Item = &'a YNABAccount>,
|
||||||
|
|
Loading…
Reference in New Issue