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() {
format!("{}", self.short())
} else {
format!("{}, {}", self.short(), self.detailed())
}
}
fn into_err(self) -> ChainedError<Self>
where
Self: Sized,
{
ChainedError {
kind: self,
context: vec![],
}
}
fn into_with<C: ErrorContext>(self, cxt: C) -> ChainedError<Self>
where
Self: Sized,
{
let s = cxt.context().to_owned();
ChainedError {
kind: self,
context: vec![s],
}
}
}
pub trait ErrorContext: Sized {
fn context(&self) -> &str;
}
impl<S: AsRef<str>> ErrorContext for S {
fn context(&self) -> &str {
self.as_ref()
}
}
pub struct ChainedError<T: ErrorKind> {
pub kind: T,
pub context: Vec<String>,
}
impl<T: ErrorKind + Clone> Clone for ChainedError<T> {
fn clone(&self) -> Self {
ChainedError {
kind: self.kind.clone(),
context: self.context.clone(),
}
}
}
unsafe impl<T: ErrorKind + Sync> Sync for ChainedError<T> {}
unsafe impl<T: ErrorKind + Send> Send for ChainedError<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.kind.short()
}
}
impl<T: ErrorKind> Display for ChainedError<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "--- ChainedError:")?;
write!(f, "kind: {}", self.kind.short())?;
let detailed = self.kind.detailed();
if !detailed.is_empty() {
write!(f, ", {}", detailed)?;
}
writeln!(f, "")?;
for (i, s) in self.context.iter().enumerate() {
if i != 0 {
writeln!(f, "")?;
}
write!(f, "context{:3}: {}", i, s)?;
}
writeln!(f, " ---")
}
}
impl<T: ErrorKind> ChainedError<T> {
pub fn chain<C: ErrorContext>(mut self, c: C) -> Self {
let s = c.context().to_owned();
self.context.push(s);
self
}
pub fn convert<U, C>(mut self, c: C) -> ChainedError<U>
where
U: ErrorKind + From<T>,
C: ErrorContext,
{
let s = c.context().to_owned();
self.context.push(s);
ChainedError {
kind: U::from(self.kind),
context: self.context,
}
}
}
pub trait ResultExt {
type OkType;
type ErrType;
fn chain_err<C, K>(self, context: C) -> Result<Self::OkType, ChainedError<K>>
where
K: ErrorKind,
C: ErrorContext,
Self::ErrType: Into<ChainedError<K>>;
fn into_chained<C, K>(self, context: C) -> Result<Self::OkType, ChainedError<K>>
where
K: ErrorKind + From<Self::ErrType>,
C: ErrorContext;
}
impl<T, E> ResultExt for Result<T, E> {
type OkType = T;
type ErrType = E;
fn chain_err<C, K>(self, context: C) -> Result<Self::OkType, ChainedError<K>>
where
K: ErrorKind,
C: ErrorContext,
Self::ErrType: Into<ChainedError<K>>,
{
self.map_err(|e| e.into().chain(context))
}
fn into_chained<C, K>(self, context: C) -> Result<Self::OkType, ChainedError<K>>
where
K: ErrorKind + From<Self::ErrType>,
C: ErrorContext,
{
self.map_err(|e| K::from(e).into_with(context))
}
}
#[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.context, vec!["In io()".to_owned()])
}
}
#[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.context,
vec![
"Invalid access in index_err()".to_owned(),
"In index()".to_owned(),
]
);
}
}
}