improve config error management

This commit is contained in:
Clément DOUIN 2021-01-07 12:32:29 +01:00
parent d746a780ba
commit 01de392977
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
5 changed files with 124 additions and 104 deletions

View file

@ -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<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::IoError(err)
}
}
impl From<toml::de::Error> for Error {
fn from(err: toml::de::Error) -> Error {
Error::ParseTomlError(err)
}
}
impl From<env::VarError> for Error {
fn from(err: env::VarError) -> Error {
Error::GetEnvVarError(err)
}
}
// Result wrapper
type Result<T> = result::Result<T, Error>;
// 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<PathBuf> {
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<PathBuf> {
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<PathBuf> {
let mut path = env::temp_dir();
path.push("himalaya");
path.push("config.toml");
Ok(path)
}
pub fn new_from_file() -> Result<Self> {
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<PathBuf> {
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<PathBuf> {
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<PathBuf> {
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<String, io::Error> {
let path = file_path();
let mut file = File::open(path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}

View file

@ -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<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::IoError(err)
@ -37,7 +29,7 @@ impl From<io::Error> for Error {
type Result<T> = result::Result<T, Error>;
// Utils
// Editor utils
fn open_with_template(template: &[u8]) -> Result<String> {
// Create temporary draft

View file

@ -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<native_tls::Error> 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);

View file

@ -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<editor::Error> for Error {
fn from(err: editor::Error) -> Error {
Error::EditorError(err)
impl From<config::Error> for Error {
fn from(err: config::Error) -> Error {
Error::ConfigError(err)
}
}
@ -49,6 +42,12 @@ impl From<imap::Error> for Error {
}
}
impl From<editor::Error> for Error {
fn from(err: editor::Error) -> Error {
Error::EditorError(err)
}
}
// Result wrapper
type Result<T> = result::Result<T, Error>;
@ -76,6 +75,7 @@ fn run() -> Result<()> {
.version("0.1.0")
.about("📫 Minimalist CLI email client")
.author("soywod <clement.douin@posteo.net>")
.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);
}
}

View file

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