use crate::{backtrace::Frame, Result, StdError};
use gory::*;
use std::{
convert::From,
fmt::{self, Debug, Display, Formatter},
};
static ERROR_TYPE: &str = "witcher::Error";
static STDERROR_TYPE: &str = "std::error::Error";
static LONG_ERROR_TYPE: &str = "witcher::error::Error";
pub struct Error {
pass: bool,
msg: String,
type_name: String,
backtrace: Vec<Frame>,
inner: Option<Box<dyn StdError+Send+Sync+'static>>,
}
impl Error {
pub fn raw(msg: &str) -> Self {
Self {
pass: false,
msg: msg.to_string(),
type_name: String::from(ERROR_TYPE),
backtrace: crate::backtrace::new(),
inner: None,
}
}
pub fn wrapr<E>(err: E, msg: &str) -> Self
where
E: StdError+Send+Sync+'static,
{
Self {
pass: false,
msg: msg.to_string(),
type_name: Error::name(&err),
backtrace: crate::backtrace::new(),
inner: Some(Box::new(err)),
}
}
pub fn new<T>(msg: &str) -> Result<T> {
Err(Error::raw(msg))
}
pub fn pass<T, E>(err: E) -> Result<T>
where
E: StdError+Send+Sync+'static,
{
Err(Self {
pass: true,
msg: "pass".to_string(),
type_name: Error::name(&err),
backtrace: crate::backtrace::new(),
inner: Some(Box::new(err)),
})
}
pub fn wrap<T, E>(err: E, msg: &str) -> Result<T>
where
E: StdError+Send+Sync+'static,
{
Err(Error::wrapr(err, msg))
}
pub fn ext(&self) -> &(dyn StdError+'static) {
let mut stderr: &(dyn StdError+'static) = self;
let mut source = self.source();
while let Some(err) = source {
stderr = err;
if !err.is::<Error>() {
break;
}
source = err.source();
}
stderr
}
pub fn last(&self) -> &(dyn StdError+'static) {
let mut err: &(dyn StdError+'static) = self;
let mut source = self.source();
while let Some(e) = source {
err = e;
source = e.source();
}
err
}
pub fn is<T: StdError+'static>(&self) -> bool {
if self.pass && self.inner.is_some() {
<dyn StdError+'static>::is::<T>(self.source().unwrap())
} else {
<dyn StdError+'static>::is::<T>(self)
}
}
pub fn downcast_ref<T: StdError+'static>(&self) -> Option<&T> {
if self.pass && self.inner.is_some() {
<dyn StdError+'static>::downcast_ref::<T>(self.source().unwrap())
} else {
<dyn StdError+'static>::downcast_ref::<T>(self)
}
}
pub fn source(&self) -> Option<&(dyn StdError+'static)> {
self.as_ref().source()
}
fn msg(&self) -> String {
if self.pass {
match self.source() {
Some(err) => format!("{}", err),
None => String::new(),
}
} else {
self.msg.clone()
}
}
fn name<T>(_: T) -> String {
let mut name = std::any::type_name::<T>().to_string();
if name.starts_with('&') {
name = String::from(name.trim_start_matches('&'));
}
if name.starts_with("dyn ") {
name = String::from(name.trim_start_matches("dyn "));
}
name = String::from(name.split('<').next().unwrap_or("<unknown>"));
if name == LONG_ERROR_TYPE {
name = String::from(ERROR_TYPE);
}
name
}
fn write_std(&self, f: &mut Formatter<'_>, stderr: &dyn StdError) -> fmt::Result {
let mut buf = format!(" cause: {}: {}", self.type_name.red(), stderr.to_string().red());
let mut source = stderr.source();
while let Some(inner) = source {
if !buf.ends_with('\n') {
buf += &"\n";
}
buf += &format!(" cause: {}: {}", STDERROR_TYPE.red(), inner.to_string().red());
source = inner.source();
}
if !buf.ends_with('\n') {
buf += &"\n";
}
write!(f, "{}", buf)
}
fn write_frames(&self, f: &mut Formatter<'_>, parent: Option<&Error>, fullstack: bool) -> fmt::Result {
let frames: Vec<&Frame> = if !fullstack {
let frames: Vec<&Frame> = self.backtrace.iter().filter(|x| !x.is_dependency()).collect();
match parent {
Some(parent) => {
let len = frames.len();
let plen = parent.backtrace.iter().filter(|x| !x.is_dependency()).count();
frames.into_iter().take(len - plen).collect::<Vec<&Frame>>()
},
_ => frames,
}
} else {
self.backtrace.iter().collect()
};
let len = frames.len();
for (i, frame) in frames.iter().enumerate() {
writeln!(f, "symbol: {}", frame.symbol.cyan())?;
write!(f, " at: {}", frame.filename)?;
if let Some(line) = frame.lineno {
write!(f, ":{}", line)?;
if let Some(column) = frame.column {
write!(f, ":{}", column)?;
}
}
if i + 1 < len {
writeln!(f)?;
}
}
Ok(())
}
}
impl AsRef<dyn StdError> for Error {
fn as_ref(&self) -> &(dyn StdError+'static) {
self
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError+'static)> {
match &self.inner {
Some(x) => Some(&**x),
None => None,
}
}
}
impl Debug for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let fullstack = f.alternate();
let mut errors: Vec<&Error> = Vec::new();
let mut source = self.source();
errors.push(self);
while let Some(stderr_ref) = source {
if let Some(err) = stderr_ref.downcast_ref::<Error>() {
errors.push(err);
source = stderr_ref.source();
} else {
break;
}
}
errors = errors.into_iter().rev().collect();
let len = errors.len();
for (i, err) in errors.iter().enumerate() {
let parent: Option<&Error> = if i + 1 < len {
Some(errors[i + 1])
} else {
None
};
writeln!(f, " error: {}: {}", ERROR_TYPE.red(), err.msg().red())?;
if i == 0 {
if let Some(stderr) = if self.pass {
(*err).source().and_then(|x| x.source())
} else {
(*err).source()
} {
err.write_std(f, stderr)?;
}
}
err.write_frames(f, parent, fullstack)?;
if i + 1 < len {
writeln!(f)?;
}
}
Ok(())
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if !f.alternate() {
return write!(f, "{}", self.msg());
}
let mut buf = String::new();
buf += &format!(" error: {}", self.msg().red());
let mut source = if self.pass {
self.source().and_then(|x| x.source())
} else {
self.source()
};
while let Some(stderr) = source {
if !buf.ends_with('\n') {
buf += &"\n";
}
buf += &" cause: ".to_string();
match stderr.downcast_ref::<Error>() {
Some(err) => buf += &format!("{}", err.msg().red()),
_ => buf += &format!("{}", stderr.to_string().red()),
}
source = stderr.source();
}
write!(f, "{}", buf)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use std::sync::Once;
static INIT: Once = Once::new();
pub fn initialize() {
INIT.call_once(|| {
env::set_var(gory::TERM_COLOR, "0");
env::set_var("RUST_BACKTRACE", "0");
});
}
struct TestError {
msg: String,
inner: Option<Box<TestError>>,
}
#[cfg(not(tarpaulin_include))]
impl Debug for TestError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.msg)
}
}
impl Display for TestError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.msg)
}
}
impl StdError for TestError {
fn source(&self) -> Option<&(dyn StdError+'static)> {
match &self.inner {
Some(x) => Some(x as &dyn StdError),
None => None,
}
}
}
#[test]
fn test_output_levels() {
initialize();
assert_eq!("wrapped", format!("{}", Error::wrapr(TestError { msg: "cause".to_string(), inner: None }, "wrapped")));
assert_eq!(" error: wrapped\n cause: cause", format!("{:#}", Error::wrapr(TestError { msg: "cause".to_string(), inner: None }, "wrapped")));
let err = Error::wrapr(TestError { msg: "cause".to_string(), inner: None }, "wrapped");
assert_eq!(" error: witcher::Error: wrapped\n cause: witcher::error::tests::TestError: cause\n", format!("{:?}", err).split("symbol").next().unwrap());
let err = Error::wrapr(
TestError {
msg: "cause".to_string(),
inner: Some(Box::new(TestError { msg: "cause2".to_string(), inner: None })),
},
"wrapped",
);
assert_eq!(" error: witcher::Error: wrapped\n cause: witcher::error::tests::TestError: cause\n cause: std::error::Error: cause2\n", format!("{:#?}", err).split("symbol").next().unwrap());
}
#[test]
fn test_chained_cause() {
initialize();
let err = TestError {
msg: "cause 1".to_string(),
inner: Some(Box::new(TestError {
msg: "cause 2".to_string(),
inner: Some(Box::new(TestError { msg: "cause 3".to_string(), inner: None })),
})),
};
assert_eq!(" error: wrapped\n cause: cause 1\n cause: cause 2\n cause: cause 3", format!("{:#}", Error::wrapr(err, "wrapped")));
}
#[test]
fn test_ext_and_last() {
initialize();
let err = TestError {
msg: "cause 1".to_string(),
inner: Some(Box::new(TestError {
msg: "cause 2".to_string(),
inner: Some(Box::new(TestError { msg: "cause 3".to_string(), inner: None })),
})),
};
assert_eq!("foo", Error::wrapr(TestError { msg: "cause 1".to_string(), inner: None }, "foo").to_string());
assert_eq!("cause 1", Error::wrapr(TestError { msg: "cause 1".to_string(), inner: None }, "foo").ext().to_string());
assert_eq!("cause 3", Error::wrapr(err, "foo").last().to_string());
}
#[test]
fn test_assist_methods() {
initialize();
assert!(Error::raw("").is::<Error>());
assert!(Error::raw("").downcast_ref::<Error>().is_some());
}
}