Handle errors more gracefully than panicking

master
Nick Krichevsky 2022-10-09 15:58:48 -04:00
parent f35cb57a79
commit 999c9922b5
4 changed files with 67 additions and 34 deletions

7
Cargo.lock generated
View File

@ -32,6 +32,12 @@ dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
[[package]]
name = "async-channel"
version = "1.6.1"
@ -2303,6 +2309,7 @@ dependencies = [
name = "ynabifier"
version = "0.1.0"
dependencies = [
"anyhow",
"async-imap",
"async-native-tls",
"async-std",

View File

@ -24,6 +24,7 @@ chrono = "0.4.22"
regex = "1.6"
reqwest = { version = "0.11.12", features = ["json"] }
url = "2.2"
anyhow = "1.0"
# For annoying reasons, we must pin exactly the same versions as async-imap if we want to use
# their types.

View File

@ -190,9 +190,15 @@ where
debug!("idling");
let sequence_number = Self::idle_for_email(idle_handle).await?;
debug!("got email");
// TODO: handle these failures
sender.send(sequence_number).await.expect("failed to send");
debug!("sent");
let send_res = sender.send(sequence_number).await;
if let Err(err) = send_res {
error!(
"Successfully fetched, but failed to dispatch email with sequence number {}: {}",
sequence_number, err
);
} else {
debug!("sent");
}
}
debug!("done");

View File

@ -3,7 +3,7 @@ extern crate log;
use futures::{stream::StreamExt, Future};
use log::LevelFilter;
use std::{fs::File, sync::Arc};
use std::{fs::File, process, sync::Arc};
use tokio::runtime::Runtime;
use ynabifier::parse::Transaction;
use ynabifier::{
@ -14,39 +14,28 @@ use ynabifier::{
};
fn main() {
let config_file = File::open("config.yml").expect("failed to open config file");
let config =
serde_yaml::from_reader::<_, Config>(config_file).expect("failed to parse config file");
let config_res = load_config("config.yml");
if let Err(err) = config_res {
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");
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
.expect("failed to setup stream");
if let Err(err) = listen_for_transactions(&config) {
error!("Failed to listen for transaction emails: {}", err);
process::exit(2);
}
}
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;
}
}
});
fn load_config(filename: &str) -> Result<Config, anyhow::Error> {
let config_file = File::open(filename)?;
let config = serde_yaml::from_reader(config_file)?;
Ok(config)
}
fn setup_logger(log_level: LevelFilter) -> Result<(), fern::InitError> {
@ -68,6 +57,36 @@ fn setup_logger(log_level: LevelFilter) -> Result<(), fern::InitError> {
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)>
where
I: Iterator<Item = &'a YNABAccount>,