replace error-chain by anyhow (#152)

This commit is contained in:
Clément DOUIN 2021-09-13 11:52:20 +02:00
parent fa622ba1db
commit c619f06206
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
18 changed files with 180 additions and 454 deletions

View file

@ -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

83
Cargo.lock generated
View file

@ -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"

View file

@ -6,12 +6,11 @@ authors = ["soywod <clement.douin@posteo.net>"]
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

View file

@ -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<App<'s, 's>> {
vec![SubCommand::with_name("completion")
.about("Generates the completion script for the given shell")

View file

@ -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<String> {
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<SmtpCredentials> {
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<PathBuf> {
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(())
}

View file

@ -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\"")

View file

@ -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<clap::App<'a, 'a>> {
vec![
clap::SubCommand::with_name("notify")

View file

@ -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<u32> = 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<u32> =
@ -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<u32> = 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<Option<imap::types::ZeroCopy<Vec<imap::types::Fetch>>>> {
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<String> = 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<Msg> {
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(())
}

View file

@ -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<String> {
@ -42,7 +36,7 @@ pub fn open_editor_with_tpl(tpl: &[u8]) -> Result<String> {
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<String> {
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<String> {
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<PreEditChoice> {
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<PreEditChoice> {
}
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<PostEditChoice> {
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<PostEditChoice> {
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")),
}
}

View file

@ -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);
}
}

View file

@ -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<clap::App<'a, 'a>> {
vec![clap::SubCommand::with_name("mailboxes")
.aliases(&["mailbox", "mboxes", "mbox", "m"])

View file

@ -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")]

View file

@ -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

View file

@ -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<clap::App<'a, 'a>> {
vec![
clap::SubCommand::with_name("list")
@ -384,7 +371,7 @@ fn msg_matches_attachments(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool
debug!("downloading {}…", &attachment.filename);
fs::write(&filepath, &attachment.body_raw)
.chain_err(|| format!("Could not save attachment {:?}", filepath))?;
.with_context(|| format!("cannot save attachment {:?}", filepath))?;
}
debug!(
@ -681,7 +668,7 @@ pub fn msg_matches_tpl(ctx: &Ctx, matches: &clap::ArgMatches) -> Result<bool> {
("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());
}
};

View file

@ -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<Option<&imap_proto::types::Envelope<'_>>> 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<Option<&imap_proto::types::Envelope<'_>>> 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())?;

View file

@ -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<String> {
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()
};

View file

@ -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 {

View file

@ -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