#![warn(
missing_debug_implementations,
missing_docs,
rust_2018_idioms,
rust_2018_compatibility
)]
#![warn(clippy::all)]
#![allow(clippy::new_without_default)]
pub use backtrace::Backtrace;
use std::{
borrow::Cow,
env, error, fmt, result,
sync::atomic::{self, AtomicUsize},
};
const RUST_BACKTRACE: &str = "RUST_BACKTRACE";
#[derive(Debug)]
struct Inner<T: ?Sized> {
source: Option<Box<Error>>,
backtrace: Option<Backtrace>,
error: T,
}
pub struct Error {
inner: Box<Inner<dyn error::Error + Send + Sync>>,
}
impl Error {
pub fn new<E>(error: E) -> Self
where
E: 'static + error::Error + Send + Sync,
{
Self {
inner: Box::new(Inner {
source: None,
backtrace: new_backtrace(),
error: Box::new(error),
}),
}
}
pub fn with_source<S>(mut self, source: S) -> Self
where
S: 'static + Into<Error>,
{
self.inner.source = Some(Box::new(source.into()));
self
}
pub fn from_string<M>(message: M) -> Self
where
M: Into<Cow<'static, str>>,
{
#[derive(Debug)]
struct StringError(Cow<'static, str>);
impl fmt::Display for StringError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(fmt)
}
}
impl error::Error for StringError {}
Self {
inner: Box::new(Inner {
source: None,
backtrace: new_backtrace(),
error: Box::new(StringError(message.into())),
}),
}
}
pub fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace.as_ref()
}
pub fn source(&self) -> Option<&Error> {
self.inner.source.as_ref().map(|e| &**e)
}
pub fn causes(&self) -> Causes<'_> {
Causes {
current: Some(self),
}
}
pub fn as_error(&self) -> &(dyn error::Error + 'static) {
&self.inner.error
}
}
impl<T> From<T> for Error
where
T: 'static + error::Error + Send + Sync,
{
fn from(value: T) -> Error {
Error::new(value)
}
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.inner.error, fmt)
}
}
impl fmt::Debug for Error {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("Error")
.field("inner", &self.inner)
.finish()
}
}
pub trait ResultExt<T>
where
Self: Sized,
{
fn with_context<C, D>(self, chain: C) -> Result<T, Error>
where
C: FnOnce(&Error) -> D,
D: Into<Error>;
}
impl<T, E> ResultExt<T> for result::Result<T, E>
where
E: Into<Error>,
{
fn with_context<C, D>(self, chain: C) -> Result<T, Error>
where
C: FnOnce(&Error) -> D,
D: Into<Error>,
{
match self {
Err(e) => {
let e = e.into();
Err(chain(&e).into().with_source(e))
}
Ok(value) => Ok(value),
}
}
}
#[derive(Debug, Clone)]
pub struct Causes<'a> {
current: Option<&'a Error>,
}
impl<'a> Iterator for Causes<'a> {
type Item = &'a Error;
fn next(&mut self) -> Option<Self::Item> {
if let Some(e) = self.current {
self.current = e.source();
return Some(e);
}
None
}
}
#[macro_export]
macro_rules! format_err {
($($arg:tt)*) => { $crate::Error::from_string(format!($($arg)*)) }
}
pub fn err_msg<M>(message: M) -> Error
where
M: 'static + Send + Sync + fmt::Debug + fmt::Display,
{
Error::new(ErrMsgError { message })
}
struct ErrMsgError<M> {
message: M,
}
impl<M> error::Error for ErrMsgError<M> where M: fmt::Debug + fmt::Display {}
impl<M> fmt::Display for ErrMsgError<M>
where
M: fmt::Display,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.message.fmt(fmt)
}
}
impl<M> fmt::Debug for ErrMsgError<M>
where
M: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
self.message.fmt(fmt)
}
}
fn is_backtrace_enabled() -> bool {
match env::var_os(RUST_BACKTRACE) {
Some(ref val) if val != "0" => true,
_ => false,
}
}
static BACKTRACE_STATUS: AtomicUsize = AtomicUsize::new(0);
fn new_backtrace() -> Option<Backtrace> {
match BACKTRACE_STATUS.load(atomic::Ordering::Relaxed) {
0 => {
let enabled = is_backtrace_enabled();
BACKTRACE_STATUS.store(enabled as usize + 1, atomic::Ordering::Relaxed);
if !enabled {
return None;
}
}
1 => return None,
_ => {}
}
Some(Backtrace::new())
}
#[cfg(test)]
mod tests {
use super::{Error, ResultExt};
#[test]
fn test_error_from_string() {
assert_eq!("foo", Error::from_string("foo").to_string());
}
#[test]
fn test_error_from_error() {
use std::io;
let e = io::Error::new(io::ErrorKind::Other, "i/o other");
assert_eq!("i/o other", Error::new(e).to_string());
}
#[test]
fn test_result_ext_source() {
use std::io;
let e = io::Error::new(io::ErrorKind::Other, "wrapped");
let a = Error::new(e);
let res = Result::Err::<(), Error>(a).with_context(|_| Error::from_string("top"));
let e = res.expect_err("no error");
assert_eq!("top", e.to_string());
assert_eq!("wrapped", e.source().expect("no source").to_string());
}
#[test]
fn test_sources() {
use std::io;
let e = io::Error::new(io::ErrorKind::Other, "wrapped");
let a = Error::new(e);
let res = Result::Err::<(), Error>(a).with_context(|_| Error::from_string("top"));
let e = res.expect_err("no error");
let messages = e.causes().map(|e| e.to_string()).collect::<Vec<_>>();
assert_eq!(messages, vec!["top", "wrapped"]);
}
#[test]
fn test_try_compat() {
use std::io;
fn foo() -> Result<u32, io::Error> {
Err(io::Error::new(io::ErrorKind::Other, "foo"))
}
fn bar() -> Result<u32, Error> {
let v = foo().with_context(|_| Error::from_string("bar"))?;
Ok(v + 1)
}
let e = bar().expect_err("no error");
let messages = e.causes().map(|e| e.to_string()).collect::<Vec<_>>();
assert_eq!(messages, vec!["bar", "foo"]);
}
#[test]
fn test_with_source() {
let e = Error::from_string("foo");
assert_eq!(e.to_string(), "foo");
assert!(e.source().is_none());
let e = e.with_source(Error::from_string("bar"));
assert_eq!(e.to_string(), "foo");
assert_eq!(e.source().map(|e| e.to_string()), Some(String::from("bar")));
}
#[test]
fn test_backtrace() {
use super::BACKTRACE_STATUS;
use std::sync::atomic;
BACKTRACE_STATUS.store(2, atomic::Ordering::Relaxed);
#[allow(warnings)]
#[inline(never)]
#[no_mangle]
fn a_really_unique_name_42() -> Error {
Error::from_string("an error")
}
let e = a_really_unique_name_42();
let bt = e.backtrace().expect("a backtrace");
let frame_names = bt
.frames()
.iter()
.flat_map(|f| f.symbols().iter().flat_map(|s| s.name()))
.map(|n| n.to_string())
.collect::<Vec<_>>();
assert!(frame_names
.iter()
.any(|n| n.ends_with("a_really_unique_name_42")));
BACKTRACE_STATUS.store(1, atomic::Ordering::Relaxed);
assert!(Error::from_string("an error").backtrace().is_none());
}
}