use std::{
fmt,
path::{Path, PathBuf},
result,
};
use failure::{Context, Error, Fail};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Operation {
Create,
Read,
Write,
Delete,
Other,
}
impl fmt::Display for Operation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let text = match *self {
Operation::Create => "creating",
Operation::Read => "reading",
Operation::Write => "writing",
Operation::Delete => "deleting",
Operation::Other => "operating on",
};
write!(f, "{}", text)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Target {
Directory(PathBuf),
File(PathBuf),
Stdin,
Stdout,
Stderr,
Other(String),
}
impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Target::Directory(ref path) => {
write!(f, "the directory {}", path.display())
}
Target::File(ref path) => {
write!(f, "the file {}", path.display())
}
Target::Stdin => write!(f, "standard input"),
Target::Stdout => write!(f, "standard output"),
Target::Stderr => write!(f, "standard error"),
Target::Other(ref name) => write!(f, "{}", name),
}
}
}
impl From<PathBuf> for Target {
fn from(path: PathBuf) -> Self {
Target::File(path)
}
}
impl<'a> From<&'a PathBuf> for Target {
fn from(path: &'a PathBuf) -> Self {
Target::File(path.to_owned())
}
}
impl<'a> From<&'a Path> for Target {
fn from(path: &'a Path) -> Self {
Target::File(path.to_path_buf())
}
}
#[derive(Debug, Fail)]
pub struct IoError {
pub operation: Operation,
pub target: Target,
}
impl fmt::Display for IoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "error {} {}", self.operation, self.target)
}
}
pub trait IoContextExt<T, E>: Sized {
fn io_context<Tgt>(
self,
operation: Operation,
target: Tgt,
) -> result::Result<T, Context<IoError>>
where
Tgt: Into<Target>;
fn io_read_context<Tgt>(self, target: Tgt) -> result::Result<T, Context<IoError>>
where
Tgt: Into<Target>,
{
self.io_context(Operation::Read, target)
}
fn io_write_context<Tgt>(self, target: Tgt) -> result::Result<T, Context<IoError>>
where
Tgt: Into<Target>,
{
self.io_context(Operation::Write, target)
}
}
impl<T, E> IoContextExt<T, E> for result::Result<T, E>
where
E: Fail,
{
fn io_context<Tgt>(
self,
operation: Operation,
target: Tgt,
) -> result::Result<T, Context<IoError>>
where
Tgt: Into<Target>,
{
self.map_err(|failure| {
let ioerr = IoError {
operation,
target: target.into(),
};
failure.context(ioerr)
})
}
}
pub trait IoContextErrorExt<T, E>: Sized {
fn io_context<Tgt>(
self,
operation: Operation,
target: Tgt,
) -> result::Result<T, Context<IoError>>
where
Tgt: Into<Target>;
fn io_read_context<Tgt>(self, target: Tgt) -> result::Result<T, Context<IoError>>
where
Tgt: Into<Target>,
{
self.io_context(Operation::Read, target)
}
fn io_write_context<Tgt>(self, target: Tgt) -> result::Result<T, Context<IoError>>
where
Tgt: Into<Target>,
{
self.io_context(Operation::Write, target)
}
}
impl<T> IoContextErrorExt<T, Error> for result::Result<T, Error> {
fn io_context<Tgt>(
self,
operation: Operation,
target: Tgt,
) -> result::Result<T, Context<IoError>>
where
Tgt: Into<Target>,
{
self.map_err(|err| {
let ioerr = IoError {
operation,
target: target.into(),
};
err.context(ioerr)
})
}
}