From c619f06206495bcb4cd0f7d737b218e4c520ca11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Mon, 13 Sep 2021 11:52:20 +0200 Subject: [PATCH] replace error-chain by anyhow (#152) --- CHANGELOG.md | 2 + Cargo.lock | 83 ++---------------- Cargo.toml | 3 +- src/comp/cli.rs | 5 +- src/config/model.rs | 29 +++--- src/flag/cli.rs | 8 +- src/imap/cli.rs | 9 +- src/imap/model.rs | 71 +++++++-------- src/input.rs | 48 +++++----- src/main.rs | 45 +--------- src/mbox/cli.rs | 9 +- src/msg/attachment.rs | 18 +--- src/msg/body.rs | 13 +-- src/msg/cli.rs | 35 +++----- src/msg/headers.rs | 36 ++------ src/msg/model.rs | 200 +++++++++++++++--------------------------- src/output/utils.rs | 9 +- src/smtp.rs | 11 +-- 18 files changed, 180 insertions(+), 454 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f20ba47..78751bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Pagination for list and search cmd starts from 1 instead of 0 [#186] +- Errors management with `anyhow` [#152] ### Fixed @@ -309,6 +310,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#141]: https://github.com/soywod/himalaya/issues/141 [#144]: https://github.com/soywod/himalaya/issues/144 [#146]: https://github.com/soywod/himalaya/issues/146 +[#152]: https://github.com/soywod/himalaya/issues/152 [#160]: https://github.com/soywod/himalaya/issues/160 [#176]: https://github.com/soywod/himalaya/issues/176 [#186]: https://github.com/soywod/himalaya/issues/186 diff --git a/Cargo.lock b/Cargo.lock index b59e75f..71a7a9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "aho-corasick" version = "0.7.15" @@ -35,6 +20,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" + [[package]] name = "ascii_utils" version = "0.9.3" @@ -64,21 +55,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "backtrace" -version = "0.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" -dependencies = [ - "addr2line", - "cc", - "cfg-if 1.0.0", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base64" version = "0.9.3" @@ -198,12 +174,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "colorful" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bca1619ff57dd7a56b58a8e25ef4199f123e78e503fe1653410350a1b98ae65" - [[package]] name = "core-foundation" version = "0.9.1" @@ -342,16 +312,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "backtrace", - "version_check 0.9.3", -] - [[package]] name = "fast_chemail" version = "0.9.6" @@ -464,12 +424,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gimli" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" - [[package]] name = "hashbrown" version = "0.11.2" @@ -489,12 +443,11 @@ dependencies = [ name = "himalaya" version = "0.4.0" dependencies = [ + "anyhow", "atty", "chrono", "clap", - "colorful", "env_logger", - "error-chain", "imap", "imap-proto", "lettre 0.10.0-rc.3", @@ -732,16 +685,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6595bb28ed34f43c3fe088e48f6cfb2e033cab45f25a5384d5fdf564fbc8c4b2" -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg 1.0.1", -] - [[package]] name = "native-tls" version = "0.2.8" @@ -811,12 +754,6 @@ dependencies = [ "autocfg 1.0.1", ] -[[package]] -name = "object" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" - [[package]] name = "once_cell" version = "1.8.0" @@ -1216,12 +1153,6 @@ dependencies = [ "quoted_printable", ] -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - [[package]] name = "ryu" version = "1.0.5" diff --git a/Cargo.toml b/Cargo.toml index b623eaf..9db6442 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,12 +6,11 @@ authors = ["soywod "] edition = "2018" [dependencies] +anyhow = "1.0.44" atty = "0.2.14" chrono = "0.4.19" clap = { version = "2.33.3", default-features = false, features = ["suggestions", "color"] } -colorful = "0.2.1" env_logger = "0.8.3" -error-chain = "0.12.4" imap = "3.0.0-alpha.4" imap-proto = "0.14.3" # This commit includes the de/serialization of the ContentType diff --git a/src/comp/cli.rs b/src/comp/cli.rs index 2aa9e78..a64a331 100644 --- a/src/comp/cli.rs +++ b/src/comp/cli.rs @@ -1,11 +1,8 @@ +use anyhow::Result; use clap::{self, App, Arg, ArgMatches, Shell, SubCommand}; -use error_chain::error_chain; use log::debug; use std::io; -error_chain! {} - -// == Main functions == pub fn subcmds<'s>() -> Vec> { vec![SubCommand::with_name("completion") .about("Generates the completion script for the given shell") diff --git a/src/config/model.rs b/src/config/model.rs index b7944fd..c7af4eb 100644 --- a/src/config/model.rs +++ b/src/config/model.rs @@ -1,4 +1,4 @@ -use error_chain::error_chain; +use anyhow::{anyhow, Context, Result}; use lettre::transport::smtp::authentication::Credentials as SmtpCredentials; use log::debug; use serde::Deserialize; @@ -15,8 +15,6 @@ use toml; use crate::output::utils::run_cmd; -error_chain! {} - const DEFAULT_PAGE_SIZE: usize = 10; // --- Account --- @@ -79,7 +77,7 @@ impl Account { /// Runs the given command in your password string and returns it. pub fn imap_passwd(&self) -> Result { - let passwd = run_cmd(&self.imap_passwd_cmd).chain_err(|| "Cannot run IMAP passwd cmd")?; + let passwd = run_cmd(&self.imap_passwd_cmd).context("cannot run IMAP passwd cmd")?; let passwd = passwd .trim_end_matches(|c| c == '\r' || c == '\n') .to_owned(); @@ -108,7 +106,7 @@ impl Account { } pub fn smtp_creds(&self) -> Result { - let passwd = run_cmd(&self.smtp_passwd_cmd).chain_err(|| "Cannot run SMTP passwd cmd")?; + let passwd = run_cmd(&self.smtp_passwd_cmd).context("cannot run SMTP passwd cmd")?; let passwd = passwd .trim_end_matches(|c| c == '\r' || c == '\n') .to_owned(); @@ -254,8 +252,7 @@ pub struct Config { impl Config { fn path_from_xdg() -> Result { - let path = - env::var("XDG_CONFIG_HOME").chain_err(|| "Cannot find `XDG_CONFIG_HOME` env var")?; + let path = env::var("XDG_CONFIG_HOME").context("cannot find `XDG_CONFIG_HOME` env var")?; let mut path = PathBuf::from(path); path.push("himalaya"); path.push("config.toml"); @@ -270,7 +267,7 @@ impl Config { "HOME" }; let mut path: PathBuf = env::var(home_var) - .chain_err(|| format!("Cannot find `{}` env var", home_var))? + .context(format!("cannot find `{}` env var", home_var))? .into(); path.push(".config"); path.push("himalaya"); @@ -286,7 +283,7 @@ impl Config { "HOME" }; let mut path: PathBuf = env::var(home_var) - .chain_err(|| format!("Cannot find `{}` env var", home_var))? + .context(format!("cannot find `{}` env var", home_var))? .into(); path.push(".himalayarc"); @@ -300,15 +297,15 @@ impl Config { None => Self::path_from_xdg() .or_else(|_| Self::path_from_xdg_alt()) .or_else(|_| Self::path_from_home()) - .chain_err(|| "Cannot find config path")?, + .context("cannot find config path")?, }; - let mut file = File::open(path).chain_err(|| "Cannot open config file")?; + let mut file = File::open(path).context("cannot open config file")?; let mut content = vec![]; file.read_to_end(&mut content) - .chain_err(|| "Cannot read config file")?; + .context("cannot read config file")?; - Ok(toml::from_slice(&content).chain_err(|| "Cannot parse config file")?) + Ok(toml::from_slice(&content).context("cannot parse config file")?) } /// Returns the account by the given name. @@ -320,11 +317,11 @@ impl Config { .iter() .find(|(_, account)| account.default.unwrap_or(false)) .map(|(_, account)| account) - .ok_or_else(|| "Cannot find default account".into()), + .ok_or_else(|| anyhow!("cannot find default account")), Some(name) => self .accounts .get(name) - .ok_or_else(|| format!("Cannot find account `{}`", name).into()), + .ok_or_else(|| anyhow!(format!("cannot find account `{}`", name))), } } @@ -414,7 +411,7 @@ impl Config { .map(|cmd| format!(r#"{} {:?} {:?}"#, cmd, subject, sender)) .unwrap_or(default_cmd); - run_cmd(&cmd).chain_err(|| "Cannot run notify cmd")?; + run_cmd(&cmd).context("cannot run notify cmd")?; Ok(()) } diff --git a/src/flag/cli.rs b/src/flag/cli.rs index 75da1b0..8139550 100644 --- a/src/flag/cli.rs +++ b/src/flag/cli.rs @@ -1,15 +1,9 @@ +use anyhow::Result; use clap; -use error_chain::error_chain; use log::debug; use crate::{ctx::Ctx, flag::model::Flags, imap::model::ImapConnector, msg::cli::uid_arg}; -error_chain! { - links { - Imap(crate::imap::model::Error, crate::imap::model::ErrorKind); - } -} - fn flags_arg<'a>() -> clap::Arg<'a, 'a> { clap::Arg::with_name("flags") .help("IMAP flags (see https://tools.ietf.org/html/rfc3501#page-11). Just write the flag name without the backslash. Example: --flags \"Seen Answered\"") diff --git a/src/imap/cli.rs b/src/imap/cli.rs index 4f14bd8..bd36ce0 100644 --- a/src/imap/cli.rs +++ b/src/imap/cli.rs @@ -1,16 +1,9 @@ +use anyhow::Result; use clap; -use error_chain::error_chain; use log::debug; use crate::{ctx::Ctx, imap::model::ImapConnector}; -error_chain! { - links { - Config(crate::config::model::Error, crate::config::model::ErrorKind); - Imap(crate::imap::model::Error, crate::imap::model::ErrorKind); - } -} - pub fn subcmds<'a>() -> Vec> { vec![ clap::SubCommand::with_name("notify") diff --git a/src/imap/model.rs b/src/imap/model.rs index 442fb27..94f3f1e 100644 --- a/src/imap/model.rs +++ b/src/imap/model.rs @@ -1,20 +1,10 @@ -use error_chain::error_chain; +use anyhow::{anyhow, Context, Result}; use imap; use log::{debug, trace}; use native_tls::{self, TlsConnector, TlsStream}; use std::{collections::HashSet, convert::TryFrom, iter::FromIterator, net::TcpStream}; -use crate::config::model::Account; -use crate::ctx::Ctx; -use crate::flag::model::Flags; -use crate::msg::model::Msg; - -error_chain! { - links { - Config(crate::config::model::Error, crate::config::model::ErrorKind); - MessageError(crate::msg::model::Error, crate::msg::model::ErrorKind); - } -} +use crate::{config::model::Account, ctx::Ctx, flag::model::Flags, msg::model::Msg}; /// A little helper function to create a similiar error output. (to avoid duplicated code) fn format_err_msg(description: &str, account: &Account) -> String { @@ -56,7 +46,7 @@ impl<'a> ImapConnector<'a> { .danger_accept_invalid_certs(insecure) .danger_accept_invalid_hostnames(insecure) .build() - .chain_err(|| format_err_msg("Could not create TLS connector", account))?; + .context(format_err_msg("cannot create TLS connector", account))?; debug!("create client"); let mut client_builder = imap::ClientBuilder::new(&account.imap_host, account.imap_port); @@ -66,13 +56,13 @@ impl<'a> ImapConnector<'a> { } let client = client_builder .connect(|domain, tcp| Ok(TlsConnector::connect(&ssl_conn, domain, tcp)?)) - .chain_err(|| format_err_msg("Could not connect to IMAP server", account))?; + .context(format_err_msg("cannot connect to IMAP server", account))?; debug!("create session"); let sess = client .login(&account.imap_login, &account.imap_passwd()?) .map_err(|res| res.0) - .chain_err(|| format_err_msg("Could not login to IMAP server", account))?; + .context(format_err_msg("cannot login to IMAP server", account))?; Ok(Self { account, sess }) } @@ -113,11 +103,11 @@ impl<'a> ImapConnector<'a> { self.sess .select(mbox) - .chain_err(|| format!("Could not select mailbox `{}`", mbox))?; + .context(format!("cannot select mailbox `{}`", mbox))?; self.sess .uid_store(uid_seq, format!("FLAGS ({})", flags)) - .chain_err(|| format!("Could not set flags `{}`", &flags))?; + .context(format!("cannot set flags `{}`", &flags))?; Ok(()) } @@ -147,11 +137,11 @@ impl<'a> ImapConnector<'a> { self.sess .select(mbox) - .chain_err(|| format!("Could not select mailbox `{}`", mbox))?; + .context(format!("cannot select mailbox `{}`", mbox))?; self.sess .uid_store(uid_seq, format!("+FLAGS ({})", flags)) - .chain_err(|| format!("Could not add flags `{}`", &flags))?; + .context(format!("cannot add flags `{}`", &flags))?; Ok(()) } @@ -163,11 +153,11 @@ impl<'a> ImapConnector<'a> { self.sess .select(mbox) - .chain_err(|| format!("Could not select mailbox `{}`", mbox))?; + .context(format!("cannot select mailbox `{}`", mbox))?; self.sess .uid_store(uid_seq, format!("-FLAGS ({})", flags)) - .chain_err(|| format!("Could not remove flags `{}`", &flags))?; + .context(format!("cannot remove flags `{}`", &flags))?; Ok(()) } @@ -176,7 +166,7 @@ impl<'a> ImapConnector<'a> { let uids: Vec = self .sess .uid_search("NEW") - .chain_err(|| "Could not search new messages")? + .context("cannot search new messages")? .into_iter() .collect(); debug!("found {} new messages", uids.len()); @@ -189,7 +179,7 @@ impl<'a> ImapConnector<'a> { debug!("examine mailbox: {}", &ctx.mbox); self.sess .examine(&ctx.mbox) - .chain_err(|| format!("Could not examine mailbox `{}`", &ctx.mbox))?; + .context(format!("cannot examine mailbox `{}`", &ctx.mbox))?; debug!("init messages hashset"); let mut msgs_set: HashSet = @@ -208,7 +198,7 @@ impl<'a> ImapConnector<'a> { false }) }) - .chain_err(|| "Could not start the idle mode")?; + .context("cannot start the idle mode")?; let uids: Vec = self .search_new_msgs()? @@ -227,12 +217,12 @@ impl<'a> ImapConnector<'a> { let fetches = self .sess .uid_fetch(uids, "(ENVELOPE)") - .chain_err(|| "Could not fetch new messages enveloppe")?; + .context("cannot fetch new messages enveloppe")?; for fetch in fetches.iter() { let msg = Msg::try_from(fetch)?; let uid = fetch.uid.ok_or_else(|| { - format!("Could not retrieve message {}'s UID", fetch.message) + anyhow!(format!("cannot retrieve message {}'s UID", fetch.message)) })?; let subject = msg.headers.subject.clone().unwrap_or_default(); @@ -255,7 +245,7 @@ impl<'a> ImapConnector<'a> { debug!("examine mailbox: {}", &ctx.mbox); self.sess .examine(&ctx.mbox) - .chain_err(|| format!("Could not examine mailbox `{}`", &ctx.mbox))?; + .context(format!("cannot examine mailbox `{}`", &ctx.mbox))?; loop { debug!("begin loop"); @@ -269,7 +259,7 @@ impl<'a> ImapConnector<'a> { false }) }) - .chain_err(|| "Could not start the idle mode")?; + .context("cannot start the idle mode")?; ctx.config.exec_watch_cmds(&ctx.account)?; debug!("end loop"); } @@ -279,7 +269,7 @@ impl<'a> ImapConnector<'a> { let names = self .sess .list(Some(""), Some("*")) - .chain_err(|| "Could not list mailboxes")?; + .context("cannot list mailboxes")?; Ok(names) } @@ -293,7 +283,7 @@ impl<'a> ImapConnector<'a> { let last_seq = self .sess .select(mbox) - .chain_err(|| format!("Could not select mailbox `{}`", mbox))? + .context(format!("cannot select mailbox `{}`", mbox))? .exists as i64; if last_seq == 0 { @@ -313,7 +303,7 @@ impl<'a> ImapConnector<'a> { let fetches = self .sess .fetch(range, "(UID FLAGS ENVELOPE INTERNALDATE)") - .chain_err(|| "Could not fetch messages")?; + .context("cannot fetch messages")?; Ok(Some(fetches)) } @@ -327,14 +317,17 @@ impl<'a> ImapConnector<'a> { ) -> Result>>> { self.sess .select(mbox) - .chain_err(|| format!("Could not select mailbox `{}`", mbox))?; + .context(format!("cannot select mailbox `{}`", mbox))?; let begin = page * page_size; let end = begin + (page_size - 1); let uids: Vec = self .sess .search(query) - .chain_err(|| format!("Could not search in `{}` with query `{}`", mbox, query))? + .context(format!( + "cannot search in `{}` with query `{}`", + mbox, query + ))? .iter() .map(|seq| seq.to_string()) .collect(); @@ -347,7 +340,7 @@ impl<'a> ImapConnector<'a> { let fetches = self .sess .fetch(&range, "(UID FLAGS ENVELOPE INTERNALDATE)") - .chain_err(|| format!("Could not fetch range `{}`", &range))?; + .context(format!("cannot fetch range `{}`", &range))?; Ok(Some(fetches)) } @@ -356,15 +349,15 @@ impl<'a> ImapConnector<'a> { pub fn get_msg(&mut self, mbox: &str, uid: &str) -> Result { self.sess .select(mbox) - .chain_err(|| format!("Could not select mailbox `{}`", mbox))?; + .context(format!("cannot select mailbox `{}`", mbox))?; match self .sess .uid_fetch(uid, "(FLAGS BODY[] ENVELOPE INTERNALDATE)") - .chain_err(|| "Could not fetch bodies")? + .context("cannot fetch bodies")? .first() { - None => Err(format!("Could not find message `{}`", uid).into()), + None => Err(anyhow!(format!("cannot find message `{}`", uid))), Some(fetch) => Ok(Msg::try_from(fetch)?), } } @@ -378,7 +371,7 @@ impl<'a> ImapConnector<'a> { .append(mbox, &body) .flags(flags) .finish() - .chain_err(|| format!("Could not append message to `{}`", mbox))?; + .context(format!("cannot append message to `{}`", mbox))?; Ok(()) } @@ -386,7 +379,7 @@ impl<'a> ImapConnector<'a> { pub fn expunge(&mut self, mbox: &str) -> Result<()> { self.sess .expunge() - .chain_err(|| format!("Could not expunge `{}`", mbox))?; + .context(format!("cannot expunge `{}`", mbox))?; Ok(()) } diff --git a/src/input.rs b/src/input.rs index bd7df3c..502f0d4 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,4 +1,4 @@ -use error_chain::error_chain; +use anyhow::{anyhow, Context, Result}; use log::{debug, error, trace}; use std::{ env, @@ -8,12 +8,6 @@ use std::{ process::Command, }; -error_chain! { - foreign_links { - Utf8(std::string::FromUtf8Error); - } -} - fn draft_path() -> PathBuf { env::temp_dir().join("himalaya-draft.mail") } @@ -25,7 +19,7 @@ pub fn remove_draft() -> Result<()> { debug!("[input] draft path: {:?}", draft_path); fs::remove_file(&draft_path) - .chain_err(|| format!("Could not delete draft file {:?}", draft_path)) + .with_context(|| format!("cannot delete draft file {:?}", draft_path)) } pub fn open_editor_with_tpl(tpl: &[u8]) -> Result { @@ -42,7 +36,7 @@ pub fn open_editor_with_tpl(tpl: &[u8]) -> Result { Ok(choice) => match choice { PreEditChoice::Edit => return open_editor_with_draft(), PreEditChoice::Discard => break, - PreEditChoice::Quit => return Err("Edition aborted".into()), + PreEditChoice::Quit => return Err(anyhow!("Edition aborted")), }, Err(err) => error!("{}", err), } @@ -51,22 +45,22 @@ pub fn open_editor_with_tpl(tpl: &[u8]) -> Result { debug!("[Input] create draft"); File::create(&draft_path) - .chain_err(|| format!("Could not create draft file {:?}", draft_path))? + .with_context(|| format!("cannot create draft file {:?}", draft_path))? .write(tpl) - .chain_err(|| format!("Could not write draft file {:?}", draft_path))?; + .with_context(|| format!("cannot write draft file {:?}", draft_path))?; debug!("[Input] open editor"); - Command::new(env::var("EDITOR").chain_err(|| "Could not find `EDITOR` env var")?) + Command::new(env::var("EDITOR").with_context(|| "cannot find `EDITOR` env var")?) .arg(&draft_path) .status() - .chain_err(|| "Could not launch editor")?; + .with_context(|| "cannot launch editor")?; debug!("[Input] read draft"); let mut draft = String::new(); File::open(&draft_path) - .chain_err(|| format!("Could not open draft file {:?}", draft_path))? + .with_context(|| format!("cannot open draft file {:?}", draft_path))? .read_to_string(&mut draft) - .chain_err(|| format!("Could not read draft file {:?}", draft_path))?; + .with_context(|| format!("cannot read draft file {:?}", draft_path))?; Ok(draft) } @@ -78,17 +72,17 @@ pub fn open_editor_with_draft() -> Result { debug!("[input] draft path: {:?}", draft_path); // Opens editor and saves user input to draft file - Command::new(env::var("EDITOR").chain_err(|| "Could not find `EDITOR` env var")?) + Command::new(env::var("EDITOR").with_context(|| "cannot find `EDITOR` env var")?) .arg(&draft_path) .status() - .chain_err(|| "Could not launch editor")?; + .with_context(|| "cannot launch editor")?; // Extracts draft file content let mut draft = String::new(); File::open(&draft_path) - .chain_err(|| format!("Could not open file {:?}", draft_path))? + .with_context(|| format!("cannot open file {:?}", draft_path))? .read_to_string(&mut draft) - .chain_err(|| format!("Could not read file {:?}", draft_path))?; + .with_context(|| format!("cannot read file {:?}", draft_path))?; Ok(draft) } @@ -106,12 +100,12 @@ pub fn pre_edit_choice() -> Result { print!("(e)dit, (d)iscard or (q)uit? "); io::stdout() .flush() - .chain_err(|| "Could not flush stdout")?; + .with_context(|| "cannot flush stdout")?; let mut buf = String::new(); io::stdin() .read_line(&mut buf) - .chain_err(|| "Could not read stdin")?; + .with_context(|| "cannot read stdin")?; match buf.bytes().next().map(|bytes| bytes as char) { Some('e') => { @@ -128,11 +122,11 @@ pub fn pre_edit_choice() -> Result { } Some(choice) => { debug!("[input] pre edit choice: invalid choice {}", choice); - Err(format!("Invalid choice `{}`", choice).into()) + Err(anyhow!(format!("Invalid choice `{}`", choice))) } None => { debug!("[input] pre edit choice: empty choice"); - Err("Empty choice".into()) + Err(anyhow!("Empty choice")) } } } @@ -149,12 +143,12 @@ pub fn post_edit_choice() -> Result { print!("(s)end, (e)dit, (l)ocal/(r)emote draft or (d)iscard? "); io::stdout() .flush() - .chain_err(|| "Could not flush stdout")?; + .with_context(|| "cannot flush stdout")?; let mut buf = String::new(); io::stdin() .read_line(&mut buf) - .chain_err(|| "Could not read stdin")?; + .with_context(|| "cannot read stdin")?; match buf.bytes().next().map(|bytes| bytes as char) { Some('s') => Ok(PostEditChoice::Send), @@ -162,7 +156,7 @@ pub fn post_edit_choice() -> Result { Some('r') => Ok(PostEditChoice::RemoteDraft), Some('e') => Ok(PostEditChoice::Edit), Some('d') => Ok(PostEditChoice::Discard), - Some(choice) => Err(format!("Invalid choice `{}`", choice).into()), - None => Err("Empty choice".into()), + Some(choice) => Err(anyhow!(format!("Invalid choice `{}`", choice))), + None => Err(anyhow!("Empty choice")), } } diff --git a/src/main.rs b/src/main.rs index faf41e7..6f13a54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ +use anyhow::Result; use clap::{self, ArgMatches}; use env_logger; -use error_chain::error_chain; -use log::{debug, error, trace}; -use std::{env, path::PathBuf, process::exit}; +use log::{debug, trace}; +use std::{env, path::PathBuf}; use url::{self, Url}; use himalaya::{ @@ -14,20 +14,6 @@ use himalaya::{ output::{cli::output_args, model::Output}, }; -error_chain! { - links { - CompletionCli(himalaya::comp::cli::Error, himalaya::comp::cli::ErrorKind); - Config(himalaya::config::model::Error, himalaya::config::model::ErrorKind); - FlagCli(himalaya::flag::cli::Error, himalaya::flag::cli::ErrorKind); - ImapCli(himalaya::imap::cli::Error, himalaya::imap::cli::ErrorKind); - MboxCli(himalaya::mbox::cli::Error, himalaya::mbox::cli::ErrorKind); - MsgCli(himalaya::msg::cli::Error, himalaya::msg::cli::ErrorKind); - } - foreign_links { - Url(url::ParseError); - } -} - fn parse_args<'a>() -> clap::App<'a, 'a> { clap::App::new(env!("CARGO_PKG_NAME")) .version(env!("CARGO_PKG_VERSION")) @@ -44,7 +30,7 @@ fn parse_args<'a>() -> clap::App<'a, 'a> { .subcommands(comp::cli::subcmds()) } -fn run() -> Result<()> { +fn main() -> Result<()> { env_logger::init_from_env( env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "off"), ); @@ -99,26 +85,3 @@ fn run() -> Result<()> { Ok(()) } - -fn main() { - if let Err(ref errs) = run() { - let mut errs = errs.iter(); - - match errs.next() { - None => (), - Some(err) => { - error!("{}", err); - eprintln!("{}", err); - - errs.for_each(|err| { - error!("{}", err); - eprintln!(" ↳ {}", err); - }); - } - } - - exit(1); - } else { - exit(0); - } -} diff --git a/src/mbox/cli.rs b/src/mbox/cli.rs index bbbc813..7066587 100644 --- a/src/mbox/cli.rs +++ b/src/mbox/cli.rs @@ -1,16 +1,9 @@ +use anyhow::Result; use clap; -use error_chain::error_chain; use log::{debug, trace}; use crate::{ctx::Ctx, imap::model::ImapConnector, mbox::model::Mboxes}; -error_chain! { - links { - Imap(crate::imap::model::Error, crate::imap::model::ErrorKind); - } -} - -// == Main functions == pub fn subcmds<'a>() -> Vec> { vec![clap::SubCommand::with_name("mailboxes") .aliases(&["mailbox", "mboxes", "mbox", "m"]) diff --git a/src/msg/attachment.rs b/src/msg/attachment.rs index 1744397..c8a84ae 100644 --- a/src/msg/attachment.rs +++ b/src/msg/attachment.rs @@ -1,23 +1,9 @@ +use anyhow::{Error, Result}; use lettre::message::header::ContentType; - use mailparse::{DispositionType, ParsedMail}; - -use std::convert::TryFrom; -use std::fs; -use std::path::Path; - use serde::Serialize; +use std::{convert::TryFrom, fs, path::Path}; -use error_chain::error_chain; - -error_chain! { - foreign_links { - ContentType(lettre::message::header::ContentTypeErr); - FileSytem(std::io::Error); - } -} - -// == Structs == /// This struct represents an attachment. #[derive(Debug, Serialize, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] diff --git a/src/msg/body.rs b/src/msg/body.rs index 185335c..d32575f 100644 --- a/src/msg/body.rs +++ b/src/msg/body.rs @@ -1,17 +1,6 @@ -use error_chain::error_chain; - +use serde::Serialize; use std::fmt; -use serde::Serialize; - -// == Macros == -error_chain! { - foreign_links { - ParseContentType(lettre::message::header::ContentTypeErr); - } -} - -// == Structs == /// This struct represents the body/content of a msg. For example: /// /// ```text diff --git a/src/msg/cli.rs b/src/msg/cli.rs index 300268a..9eebf37 100644 --- a/src/msg/cli.rs +++ b/src/msg/cli.rs @@ -1,14 +1,9 @@ -use super::body::Body; -use super::headers::Headers; -use super::model::{Msg, MsgSerialized, Msgs}; -use url::Url; - +use anyhow::{anyhow, Context, Result}; use atty::Stream; use clap; -use error_chain::error_chain; +use imap::types::Flag; use lettre::message::header::ContentTransferEncoding; use log::{debug, error, trace}; - use std::{ borrow::Cow, collections::HashMap, @@ -16,26 +11,18 @@ use std::{ fs, io::{self, BufRead}, }; +use url::Url; -use imap::types::Flag; - +use super::{ + body::Body, + headers::Headers, + model::{Msg, MsgSerialized, Msgs}, +}; use crate::{ ctx::Ctx, flag::model::Flags, imap::model::ImapConnector, input, mbox::cli::mbox_target_arg, smtp, }; -error_chain! { - links { - Imap(crate::imap::model::Error, crate::imap::model::ErrorKind); - Input(crate::input::Error, crate::input::ErrorKind); - MsgModel(super::model::Error, super::model::ErrorKind); - Smtp(crate::smtp::Error, crate::smtp::ErrorKind); - } - foreign_links { - Utf8(std::string::FromUtf8Error); - } -} - pub fn subcmds<'a>() -> Vec> { vec![ clap::SubCommand::with_name("list") @@ -384,7 +371,7 @@ fn msg_matches_attachments(ctx: &Ctx, matches: &clap::ArgMatches) -> Result Result { ("forward", Some(matches)) => tpl_matches_forward(ctx, matches), // TODO: find a way to show the help message for template subcommand - _ => Err("Subcommand not found".into()), + _ => Err(anyhow!("Subcommand not found")), } } @@ -887,7 +874,7 @@ fn msg_interaction(ctx: &Ctx, msg: &mut Msg, imap_conn: &mut ImapConnector) -> R ctx.output.print("Message successfully saved to Drafts"); } Err(err) => { - ctx.output.print("Couldn't save it to the server..."); + ctx.output.print("Cannot save draft to the server"); return Err(err.into()); } }; diff --git a/src/msg/headers.rs b/src/msg/headers.rs index 13456cf..d2205ce 100644 --- a/src/msg/headers.rs +++ b/src/msg/headers.rs @@ -1,32 +1,10 @@ -use std::borrow::Cow; -use std::collections::HashMap; -use std::convert::TryFrom; -use std::fmt; - -use log::{debug, warn}; - -use serde::Serialize; - -use rfc2047_decoder; - -use error_chain::error_chain; - +use anyhow::{anyhow, Error, Result}; use lettre::message::header::ContentTransferEncoding; +use log::{debug, warn}; +use rfc2047_decoder; +use serde::Serialize; +use std::{borrow::Cow, collections::HashMap, convert::TryFrom, fmt}; -error_chain! { - errors { - Convertion(field: &'static str) { - display("Couldn't get the data from the '{}:' field.", field), - } - } - - foreign_links { - StringFromUtf8(std::string::FromUtf8Error); - Rfc2047Decoder(rfc2047_decoder::Error); - } -} - -// == Structs == /// This struct is a wrapper for the [Envelope struct] of the [imap_proto] /// crate. It's should mainly help to interact with the mails by using more /// common data types like `Vec` or `String` since a `[u8]` array is a little @@ -245,7 +223,7 @@ impl TryFrom>> for Headers { let from = match convert_vec_address_to_string(envelope.from.as_ref())? { Some(from) => from, - None => return Err(ErrorKind::Convertion("From").into()), + None => return Err(anyhow!("cannot extract senders from envelope")), }; // only the first address is used, because how should multiple machines send the same @@ -266,7 +244,7 @@ impl TryFrom>> for Headers { let reply_to = convert_vec_address_to_string(envelope.reply_to.as_ref())?; let to = match convert_vec_address_to_string(envelope.to.as_ref())? { Some(to) => to, - None => return Err(ErrorKind::Convertion("To").into()), + None => return Err(anyhow!("cannot extract recipients from envelope")), }; let cc = convert_vec_address_to_string(envelope.cc.as_ref())?; let bcc = convert_vec_address_to_string(envelope.bcc.as_ref())?; diff --git a/src/msg/model.rs b/src/msg/model.rs index f1a9d42..71fe59b 100644 --- a/src/msg/model.rs +++ b/src/msg/model.rs @@ -1,13 +1,9 @@ -use super::attachment::Attachment; -use super::body::Body; -use super::headers::Headers; - -use log::debug; - +use anyhow::{anyhow, Context, Error, Result}; use imap::types::{Fetch, Flag, ZeroCopy}; - +use log::debug; use mailparse; +use super::{attachment::Attachment, body::Body, headers::Headers}; use crate::{ ctx::Ctx, flag::model::Flags, @@ -29,46 +25,6 @@ use std::{ fmt, }; -use colorful::Colorful; - -// == Macros == -error_chain::error_chain! { - errors { - ParseBody (err: String) { - description("An error appeared, when trying to parse the body of the msg!"), - display("Couldn't get the body of the parsed msg: {}", err), - } - - /// Is mainly used in the "to_sendable_msg" function - Header(error_msg: String, header_name: &'static str, header_input: String) { - - description("An error happened, when trying to parse a header-field."), - display(concat![ - "[{}] {}\n", - "Header-Field-Name: '{}'\n", - "The word which let this error occur: '{}'"], - "Error".red(), - error_msg.clone().light_red(), - header_name.light_blue(), - header_input.clone().light_cyan()), - } - } - - links { - Attachment(super::attachment::Error, super::attachment::ErrorKind); - Headers(super::headers::Error, super::headers::ErrorKind); - Input(crate::input::Error, crate::input::ErrorKind); - } - - foreign_links { - MailParse(mailparse::MailParseError); - Lettre(lettre::error::Error); - LettreAddress(lettre::address::AddressError); - FromUtf8Error(std::string::FromUtf8Error); - } -} - -// == Msg == /// Represents the msg in a serializeable form with additional values. /// This struct-type makes it also possible to print the msg in a serialized form or in a normal /// form. @@ -528,13 +484,13 @@ impl Msg { /// ``` pub fn parse_from_str(&mut self, content: &str) -> Result<()> { let parsed = mailparse::parse_mail(content.as_bytes()) - .chain_err(|| format!("How the message looks like currently:\n{}", self))?; + .with_context(|| format!("How the message looks like currently:\n{}", self))?; self.headers = Headers::from(&parsed); match parsed.get_body() { Ok(body) => self.body = Body::new_with_text(body), - Err(err) => return Err(ErrorKind::ParseBody(err.to_string()).into()), + Err(err) => return Err(anyhow!(err.to_string())), }; Ok(()) @@ -623,76 +579,72 @@ impl Msg { // -- Must-have-fields -- // add "from" for mailaddress in &self.headers.from { - msg = msg.from(match mailaddress.parse() { - Ok(from) => from, - Err(err) => { - return Err( - ErrorKind::Header(err.to_string(), "From", mailaddress.to_string()).into(), - ) - } - }); + msg = msg.from( + match mailaddress + .parse() + .with_context(|| "cannot parse `From` header") + { + Ok(from) => from, + Err(err) => return Err(anyhow!(err.to_string())), + }, + ); } // add "to" for mailaddress in &self.headers.to { - msg = msg.to(match mailaddress.parse() { - Ok(to) => to, - Err(err) => { - return Err( - ErrorKind::Header(err.to_string(), "To", mailaddress.to_string()).into(), - ) - } - }); + msg = msg.to( + match mailaddress + .parse() + .with_context(|| "cannot parse `To` header") + { + Ok(from) => from, + Err(err) => return Err(anyhow!(err.to_string())), + }, + ); } // -- Optional fields -- // add "bcc" if let Some(bcc) = &self.headers.bcc { for mailaddress in bcc { - msg = msg.bcc(match mailaddress.parse() { - Ok(bcc) => bcc, - Err(err) => { - return Err(ErrorKind::Header( - err.to_string(), - "Bcc", - mailaddress.to_string(), - ) - .into()) - } - }); + msg = msg.bcc( + match mailaddress + .parse() + .with_context(|| "cannot parse `Bcc` header") + { + Ok(from) => from, + Err(err) => return Err(anyhow!(err.to_string())), + }, + ); } } // add "cc" if let Some(cc) = &self.headers.cc { for mailaddress in cc { - msg = msg.cc(match mailaddress.parse() { - Ok(cc) => cc, - Err(err) => { - return Err(ErrorKind::Header( - err.to_string(), - "Cc", - mailaddress.to_string(), - ) - .into()) - } - }); + msg = msg.cc( + match mailaddress + .parse() + .with_context(|| "cannot parse `Cc` header") + { + Ok(from) => from, + Err(err) => return Err(anyhow!(err.to_string())), + }, + ); } } // add "in_reply_to" if let Some(in_reply_to) = &self.headers.in_reply_to { - msg = msg.in_reply_to(match in_reply_to.parse() { - Ok(in_reply_to) => in_reply_to, - Err(err) => { - return Err(ErrorKind::Header( - err.to_string(), - "In-Reply-To", - in_reply_to.to_string(), - ) - .into()) - } - }); + msg = msg.in_reply_to( + match in_reply_to + .parse() + .with_context(|| "cannot parse `In-Reply-To` header") + { + Ok(from) => from, + Err(err) => return Err(anyhow!(err.to_string())), + }, + ); } // add message-id if it exists @@ -713,30 +665,29 @@ impl Msg { // add "reply-to" if let Some(reply_to) = &self.headers.reply_to { for mailaddress in reply_to { - msg = msg.reply_to(match mailaddress.parse() { - Ok(reply_to) => reply_to, - Err(err) => { - return Err(ErrorKind::Header( - err.to_string(), - "Reply-to", - mailaddress.to_string(), - ) - .into()) - } - }); + msg = msg.reply_to( + match mailaddress + .parse() + .with_context(|| "cannot parse `Reply-To` header") + { + Ok(from) => from, + Err(err) => return Err(anyhow!(err.to_string())), + }, + ); } } // add "sender" if let Some(sender) = &self.headers.sender { - msg = msg.sender(match sender.parse() { - Ok(sender) => sender, - Err(err) => { - return Err( - ErrorKind::Header(err.to_string(), "Sender", sender.to_string()).into(), - ) - } - }); + msg = msg.sender( + match sender + .parse() + .with_context(|| "cannot parse `Sender` header") + { + Ok(from) => from, + Err(err) => return Err(anyhow!(err.to_string())), + }, + ); } // add subject @@ -779,7 +730,7 @@ impl Msg { .multipart(msg_parts) // whenever an error appears, print out the messge as well to see what might be the // error - .chain_err(|| format!("-- Current Message --\n{}", self))?) + .context(format!("-- Current Message --\n{}", self))?) } /// Returns the uid of the msg. @@ -802,12 +753,8 @@ impl Msg { /// Returns the raw mail as a string instead of a Vector of bytes. pub fn get_raw_as_string(&self) -> Result { - let raw_message = String::from_utf8(self.raw.clone()).chain_err(|| { - format!( - "[{}]: Couldn't parse the raw message as string.", - "Error".red() - ) - })?; + let raw_message = String::from_utf8(self.raw.clone()) + .context(format!("cannot parse raw message as string"))?; Ok(raw_message) } @@ -974,8 +921,7 @@ impl TryFrom<&Fetch> for Msg { // the body of the mail but something else. Log that! else { println!( - "[{}] Unknown attachment with the following mime-type: {}\n", - "Warning".yellow(), + "Unknown attachment with the following mime-type: {}", subpart.ctype.mimetype, ); } @@ -1219,7 +1165,7 @@ mod tests { // -- for general tests -- let ctx = Ctx { account: Account::new(Some("Name"), "some@address.asdf"), - config: config, + config, mbox: String::from("INBOX"), ..Ctx::default() }; diff --git a/src/output/utils.rs b/src/output/utils.rs index 7325e8f..feb25d1 100644 --- a/src/output/utils.rs +++ b/src/output/utils.rs @@ -1,14 +1,7 @@ -use error_chain::error_chain; +use anyhow::Result; use serde::ser::{self, SerializeStruct}; use std::{fmt, process::Command, result}; -error_chain! { - foreign_links { - Utf8(std::string::FromUtf8Error); - Io(std::io::Error); - } -} - pub struct Info(pub String); impl fmt::Display for Info { diff --git a/src/smtp.rs b/src/smtp.rs index fa2ed50..c0726d3 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -1,4 +1,4 @@ -use error_chain::error_chain; +use anyhow::Result; use lettre::{ self, transport::{smtp::client::Tls, smtp::client::TlsParameters, smtp::SmtpTransport}, @@ -7,15 +7,6 @@ use lettre::{ use crate::config::model::Account; -error_chain! { - links { - Config(crate::config::model::Error, crate::config::model::ErrorKind); - } - foreign_links { - Smtp(lettre::transport::smtp::Error); - } -} - pub fn send(account: &Account, msg: &lettre::Message) -> Result<()> { let smtp_relay = if account.smtp_starttls() { SmtpTransport::starttls_relay