From 01de392977ea6683212aa89537490dc438dfd6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Thu, 7 Jan 2021 12:32:29 +0100 Subject: [PATCH] improve config error management --- src/config.rs | 148 +++++++++++++++++++++++++++++++------------------- src/editor.rs | 12 +--- src/imap.rs | 25 +++------ src/main.rs | 42 +++++++------- src/smtp.rs | 1 + 5 files changed, 124 insertions(+), 104 deletions(-) diff --git a/src/config.rs b/src/config.rs index fc7f1b6..6c7abf4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,10 +1,59 @@ use serde::Deserialize; -use std::env; -use std::fs::File; -use std::io::{self, Read}; -use std::path::PathBuf; +use std::{ + env, fmt, + fs::File, + io::{self, Read}, + path::PathBuf, + result, +}; use toml; +// Error wrapper + +#[derive(Debug)] +pub enum Error { + IoError(io::Error), + ParseTomlError(toml::de::Error), + GetEnvVarError(env::VarError), + GetPathNotFoundError, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "(config): ")?; + match self { + Error::IoError(err) => err.fmt(f), + Error::ParseTomlError(err) => err.fmt(f), + Error::GetEnvVarError(err) => err.fmt(f), + Error::GetPathNotFoundError => write!(f, "path not found"), + } + } +} + +impl From for Error { + fn from(err: io::Error) -> Error { + Error::IoError(err) + } +} + +impl From for Error { + fn from(err: toml::de::Error) -> Error { + Error::ParseTomlError(err) + } +} + +impl From for Error { + fn from(err: env::VarError) -> Error { + Error::GetEnvVarError(err) + } +} + +// Result wrapper + +type Result = result::Result; + +// Config + #[derive(Debug, Deserialize)] pub struct ServerInfo { pub host: String, @@ -28,61 +77,48 @@ pub struct Config { } impl Config { - pub fn new_from_file() -> Self { - match read_file_content() { - Err(err) => panic!(err), - Ok(content) => toml::from_str(&content).unwrap(), - } + fn path_from_xdg() -> Result { + let path = env::var("XDG_CONFIG_HOME")?; + let mut path = PathBuf::from(path); + path.push("himalaya"); + path.push("config.toml"); + + Ok(path) + } + + fn path_from_home(_err: Error) -> Result { + let path = env::var("HOME")?; + let mut path = PathBuf::from(path); + path.push(".config"); + path.push("himalaya"); + path.push("config.toml"); + + Ok(path) + } + + fn path_from_tmp(_err: Error) -> Result { + let mut path = env::temp_dir(); + path.push("himalaya"); + path.push("config.toml"); + + Ok(path) + } + + pub fn new_from_file() -> Result { + let mut file = File::open( + Self::path_from_xdg() + .or_else(Self::path_from_home) + .or_else(Self::path_from_tmp) + .or_else(|_| Err(Error::GetPathNotFoundError))?, + )?; + + let mut content = String::new(); + file.read_to_string(&mut content)?; + + Ok(toml::from_str(&content)?) } pub fn email_full(&self) -> String { format!("{} <{}>", self.name, self.email) } } - -pub fn from_xdg() -> Option { - match env::var("XDG_CONFIG_HOME") { - Err(_) => None, - Ok(path_str) => { - let mut path = PathBuf::from(path_str); - path.push("himalaya"); - path.push("config.toml"); - Some(path) - } - } -} - -pub fn from_home() -> Option { - match env::var("HOME") { - Err(_) => None, - Ok(path_str) => { - let mut path = PathBuf::from(path_str); - path.push(".config"); - path.push("himalaya"); - path.push("config.toml"); - Some(path) - } - } -} - -pub fn from_tmp() -> Option { - let mut path = env::temp_dir(); - path.push("himalaya"); - path.push("config.toml"); - Some(path) -} - -pub fn file_path() -> PathBuf { - match from_xdg().or_else(from_home).or_else(from_tmp) { - None => panic!("Config file path not found."), - Some(path) => path, - } -} - -pub fn read_file_content() -> Result { - let path = file_path(); - let mut file = File::open(path)?; - let mut content = String::new(); - file.read_to_string(&mut content)?; - Ok(content) -} diff --git a/src/editor.rs b/src/editor.rs index e91fdaf..1106485 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -2,7 +2,7 @@ use std::env::temp_dir; use std::fs::{remove_file, File}; use std::io::{self, Read, Write}; use std::process::Command; -use std::{error, fmt, result}; +use std::{fmt, result}; // Error wrapper @@ -19,14 +19,6 @@ impl fmt::Display for Error { } } -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match *self { - Error::IoError(ref err) => Some(err), - } - } -} - impl From for Error { fn from(err: io::Error) -> Error { Error::IoError(err) @@ -37,7 +29,7 @@ impl From for Error { type Result = result::Result; -// Utils +// Editor utils fn open_with_template(template: &[u8]) -> Result { // Create temporary draft diff --git a/src/imap.rs b/src/imap.rs index 35f55b0..d2758b4 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -1,7 +1,7 @@ use imap; use mailparse::{self, MailHeaderMap}; use native_tls::{self, TlsConnector, TlsStream}; -use std::{error, fmt, net::TcpStream, result}; +use std::{fmt, net::TcpStream, result}; use crate::config; use crate::email::Email; @@ -19,30 +19,20 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "(imap): ")?; match self { Error::CreateTlsConnectorError(err) => err.fmt(f), Error::CreateImapSession(err) => err.fmt(f), Error::ReadEmailNotFoundError(uid) => { - write!(f, "No email found for UID {}", uid) + write!(f, "no email found for uid {}", uid) } Error::ReadEmailEmptyPartError(uid, mime) => { - write!(f, "No {} content found for UID {}", mime, uid) + write!(f, "no {} content found for uid {}", mime, uid) } } } } -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match *self { - Error::CreateTlsConnectorError(ref err) => Some(err), - Error::CreateImapSession(ref err) => Some(err), - Error::ReadEmailNotFoundError(_) => None, - Error::ReadEmailEmptyPartError(_, _) => None, - } - } -} - impl From for Error { fn from(err: native_tls::Error) -> Error { Error::CreateTlsConnectorError(err) @@ -77,6 +67,7 @@ impl ImapConnector { .and_then(|v| if v.starts_with(mime) { Some(()) } else { None }) .is_some() { + // TODO: push part instead of body str parts.push(part.get_body().unwrap_or(String::new())) } } @@ -136,9 +127,9 @@ impl ImapConnector { self.sess.select(mbox)?; match self.sess.uid_fetch(uid, "BODY[]")?.first() { - None => return Err(Error::ReadEmailNotFoundError(uid.to_string())), - Some(email_raw) => { - let email = mailparse::parse_mail(email_raw.body().unwrap_or(&[])).unwrap(); + None => Err(Error::ReadEmailNotFoundError(uid.to_string())), + Some(fetch) => { + let email = mailparse::parse_mail(fetch.body().unwrap_or(&[])).unwrap(); let mut parts = vec![]; Self::extract_subparts_by_mime(mime, &email, &mut parts); diff --git a/src/main.rs b/src/main.rs index d3200f6..b91f7f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,8 @@ mod mailbox; mod smtp; mod table; -use clap::{App, Arg, SubCommand}; -use std::{error, fmt, process::exit, result}; +use clap::{App, AppSettings, Arg, SubCommand}; +use std::{fmt, process::exit, result}; use crate::config::Config; use crate::imap::ImapConnector; @@ -15,31 +15,24 @@ use crate::table::DisplayTable; #[derive(Debug)] pub enum Error { - EditorError(editor::Error), + ConfigError(config::Error), ImapError(imap::Error), + EditorError(editor::Error), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Error::EditorError(err) => err.fmt(f), + Error::ConfigError(err) => err.fmt(f), Error::ImapError(err) => err.fmt(f), + Error::EditorError(err) => err.fmt(f), } } } -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match *self { - Error::EditorError(ref err) => Some(err), - Error::ImapError(ref err) => Some(err), - } - } -} - -impl From for Error { - fn from(err: editor::Error) -> Error { - Error::EditorError(err) +impl From for Error { + fn from(err: config::Error) -> Error { + Error::ConfigError(err) } } @@ -49,6 +42,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: editor::Error) -> Error { + Error::EditorError(err) + } +} + // Result wrapper type Result = result::Result; @@ -76,6 +75,7 @@ fn run() -> Result<()> { .version("0.1.0") .about("📫 Minimalist CLI email client") .author("soywod ") + .setting(AppSettings::ArgRequiredElseHelp) .subcommand(SubCommand::with_name("list").about("Lists all available mailboxes")) .subcommand( SubCommand::with_name("search") @@ -126,7 +126,7 @@ fn run() -> Result<()> { .get_matches(); if let Some(_) = matches.subcommand_matches("list") { - let config = Config::new_from_file(); + let config = Config::new_from_file()?; let mboxes = ImapConnector::new(config.imap)? .list_mailboxes()? .to_table(); @@ -135,7 +135,7 @@ fn run() -> Result<()> { } if let Some(matches) = matches.subcommand_matches("search") { - let config = Config::new_from_file(); + let config = Config::new_from_file()?; let mbox = matches.value_of("mailbox").unwrap(); if let Some(matches) = matches.values_of("query") { @@ -171,7 +171,7 @@ fn run() -> Result<()> { } if let Some(matches) = matches.subcommand_matches("read") { - let config = Config::new_from_file(); + let config = Config::new_from_file()?; let mbox = matches.value_of("mailbox").unwrap(); let uid = matches.value_of("uid").unwrap(); let mime = matches.value_of("mime-type").unwrap(); @@ -181,7 +181,7 @@ fn run() -> Result<()> { } if let Some(_) = matches.subcommand_matches("write") { - let config = Config::new_from_file(); + let config = Config::new_from_file()?; let draft = editor::open_with_new_template()?; smtp::send(&config, draft.as_bytes()); @@ -194,7 +194,7 @@ fn run() -> Result<()> { fn main() { if let Err(err) = run() { - eprintln!("Error: {}", err); + eprintln!("Error {}", err); exit(1); } } diff --git a/src/smtp.rs b/src/smtp.rs index 5751301..7016ff2 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -7,6 +7,7 @@ use mailparse; use crate::config; +// TODO: improve error management pub fn send(config: &config::Config, bytes: &[u8]) { let email_origin = mailparse::parse_mail(bytes).unwrap(); let email = email_origin