use std::error::Error;
use std::fmt::{self, Debug, Display, Formatter};
pub trait ErrorKind {
fn short(&self) -> &str;
fn detailed(&self) -> String {
String::new()
}
fn full(&self) -> String {
let detailed = self.detailed();
if detailed.is_empty() {
self.short().to_string()
} else {
format!("{} {{ {} }}", self.short(), self.detailed())
}
}
fn into_err(self) -> ChainedError<Self>
where
Self: Sized,
{
ChainedError::new(self, vec![])
}
fn into_with<C: ErrorContext, F>(self, op: F) -> ChainedError<Self>
where
F: FnOnce() -> C,
Self: Sized,
{
ChainedError::new(self, vec![Box::new(op())])
}
}
pub trait ErrorContext: 'static {
fn context(&self) -> &str;
}
impl<S: 'static + AsRef<str>> ErrorContext for S {
fn context(&self) -> &str {
self.as_ref()
}
}
pub struct ChainedError<T: ErrorKind> {
inner: Box<ErrorImpl<T>>,
}
impl<T: ErrorKind> Debug for ChainedError<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self, f)
}
}
impl<T: ErrorKind> Error for ChainedError<T> {
fn description(&self) -> &str {
self.inner.kind.short()
}
}
impl<T: ErrorKind> Display for ChainedError<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "\nkind: {}", self.inner.kind.short())?;
let detailed = self.inner.kind.detailed();
if !detailed.is_empty() {
write!(f, " {{ {} }}", detailed)?;
}
writeln!(f)?;
for (i, s) in self.inner.context.iter().enumerate() {
if i != 0 {
writeln!(f)?;
}
write!(f, "context{:3}: {}", i, s.context())?;
}
writeln!(f)
}
}
impl<T: ErrorKind> ChainedError<T> {
pub fn new(kind: T, context: Vec<Box<ErrorContext>>) -> Self {
ChainedError {
inner: Box::new(ErrorImpl::new(kind, context)),
}
}
pub fn kind(&self) -> &T {
&self.inner.kind
}
pub fn contexts(&self) -> impl Iterator<Item = &str> {
self.inner.context.iter().map(|cxt| cxt.context())
}
pub fn chain<C, F>(mut self, op: F) -> Self
where
C: ErrorContext,
F: FnOnce() -> C,
{
self.inner.context.push(Box::new(op()));
self
}
pub fn convert<U>(self) -> ChainedError<U>
where
U: ErrorKind + From<T>,
{
ChainedError {
inner: Box::new(self.inner.convert(U::from)),
}
}
pub fn convert_with<U, F>(self, converter: F) -> ChainedError<U>
where
U: ErrorKind,
F: FnOnce(T) -> U,
{
ChainedError {
inner: Box::new(self.inner.convert(converter)),
}
}
}
struct ErrorImpl<T> {
kind: T,
context: Vec<Box<ErrorContext>>,
}
impl<T> ErrorImpl<T> {
fn new(kind: T, context: Vec<Box<ErrorContext>>) -> Self {
ErrorImpl { kind, context }
}
fn convert<F, U>(self, f: F) -> ErrorImpl<U>
where
F: FnOnce(T) -> U,
{
ErrorImpl::new(f(self.kind), self.context)
}
}
unsafe impl<T: ErrorKind + Sync> Sync for ErrorImpl<T> {}
unsafe impl<T: ErrorKind + Send> Send for ErrorImpl<T> {}
pub trait ResultExt {
type OkType;
type ErrType;
fn chain_err<C, K, F>(self, op: F) -> Result<Self::OkType, ChainedError<K>>
where
F: FnOnce() -> C,
K: ErrorKind,
C: ErrorContext,
Self::ErrType: Into<ChainedError<K>>;
fn into_chained<C, K, F>(self, op: F) -> Result<Self::OkType, ChainedError<K>>
where
F: FnOnce() -> C,
K: ErrorKind + From<Self::ErrType>,
C: ErrorContext;
fn convert<K, U>(self) -> Result<Self::OkType, ChainedError<U>>
where
K: ErrorKind,
Self::ErrType: Into<ChainedError<K>>,
U: From<K> + ErrorKind;
fn convert_with<K, U, F>(self, converter: F) -> Result<Self::OkType, ChainedError<U>>
where
K: ErrorKind,
Self::ErrType: Into<ChainedError<K>>,
U: ErrorKind,
F: FnOnce(K) -> U;
}
impl<T, E> ResultExt for Result<T, E> {
type OkType = T;
type ErrType = E;
fn chain_err<C, K, F>(self, op: F) -> Result<Self::OkType, ChainedError<K>>
where
F: FnOnce() -> C,
K: ErrorKind,
C: ErrorContext,
Self::ErrType: Into<ChainedError<K>>,
{
self.map_err(|e| e.into().chain(op))
}
fn into_chained<C, K, F>(self, op: F) -> Result<Self::OkType, ChainedError<K>>
where
F: FnOnce() -> C,
K: ErrorKind + From<Self::ErrType>,
C: ErrorContext,
{
self.map_err(|e| K::from(e).into_with(op))
}
fn convert<K, U>(self) -> Result<Self::OkType, ChainedError<U>>
where
K: ErrorKind,
Self::ErrType: Into<ChainedError<K>>,
U: From<K> + ErrorKind,
{
self.map_err(|e| e.into().convert())
}
fn convert_with<K, U, F>(self, converter: F) -> Result<Self::OkType, ChainedError<U>>
where
K: ErrorKind,
Self::ErrType: Into<ChainedError<K>>,
U: ErrorKind,
F: FnOnce(K) -> U,
{
self.map_err(|e| e.into().convert_with(converter))
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::Error as IoError;
enum MyErrorKind {
Io(IoError),
Index(usize),
}
impl ErrorKind for MyErrorKind {
fn short(&self) -> &str {
match self {
MyErrorKind::Io(_) => "io error",
MyErrorKind::Index(_) => "index error",
}
}
fn detailed(&self) -> String {
match *self {
MyErrorKind::Io(ref io) => format!("{:?}", io),
MyErrorKind::Index(id) => format!("{}", id),
}
}
}
type MyError = ChainedError<MyErrorKind>;
impl From<IoError> for MyErrorKind {
fn from(e: IoError) -> Self {
MyErrorKind::Io(e)
}
}
fn index_err(u: usize) -> Result<u32, MyError> {
let array = vec![3, 7, 9, 20];
if let Some(u) = array.get(u) {
Ok(*u)
} else {
Err(MyErrorKind::Index(u).into_with(|| "Invalid access in index_err()"))
}
}
#[test]
fn io() {
use std::fs::File;
let file = File::open("not_existing_file").into_chained(|| "In io()");
assert!(file.is_err());
if let Err(e) = file {
if let MyErrorKind::Index(_) = e.kind() {
panic!("error kind is incorrect");
}
assert_eq!(e.contexts().collect::<Vec<_>>(), vec!["In io()"])
}
}
#[test]
fn index() {
let id = 8;
let res = index_err(id).chain_err(|| "In index()");
assert!(res.is_err());
if let Err(e) = res {
if let MyErrorKind::Index(u) = e.kind() {
assert_eq!(*u, id);
} else {
panic!("error kind is incorrect");
}
assert_eq!(
e.contexts().collect::<Vec<_>>(),
vec![
"Invalid access in index_err()".to_owned(),
"In index()".to_owned(),
]
);
}
}
#[test]
#[should_panic]
fn display() {
let id = 8;
let res = index_err(id).chain_err(|| "In index()");
res.unwrap();
}
}