use crate::backtrace::Frame;
use crate::term::{self, Colorized};
use crate::WITCHER_FULLSTACK;
use crate::{Result, StdError};
use std::convert::From;
use std::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 {
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 { 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 { 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 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 {
<dyn StdError + 'static>::is::<T>(self)
}
pub fn downcast_ref<T: StdError + 'static>(&self) -> Option<&T> {
<dyn StdError + 'static>::downcast_ref::<T>(self)
}
pub fn downcast_mut<T: StdError + 'static>(&mut self) -> Option<&mut T> {
<dyn StdError + 'static>::downcast_mut::<T>(self)
}
pub fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.as_ref().source()
}
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<'_>, c: &Colorized, stderr: &dyn StdError) -> fmt::Result {
let mut buf = format!(" cause: {}: {}", c.red(&self.type_name), c.red(stderr));
let mut source = stderr.source();
while let Some(inner) = source {
if !buf.ends_with('\n') {
buf += &"\n";
}
buf += &format!(" cause: {}: {}", c.red(STDERROR_TYPE), c.red(inner));
source = inner.source();
}
if !buf.ends_with('\n') {
buf += &"\n";
}
write!(f, "{}", buf)
}
fn write_frames(&self, f: &mut Formatter<'_>, c: &Colorized, 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: {}", c.cyan(&frame.symbol))?;
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 c = Colorized::new();
let fullstack = term::var_enabled(WITCHER_FULLSTACK);
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: {}: {}", c.red(ERROR_TYPE), c.red(&err.msg))?;
if i == 0 {
if let Some(stderr) = (*err).source() {
err.write_std(f, &c, stderr)?;
}
}
err.write_frames(f, &c, 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 c = Colorized::new();
let mut buf = String::new();
buf += &format!(" error: {}", c.red(&self.msg));
let mut source = 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!("{}", c.red(&err.msg)),
_ => buf += &format!("{}", c.red(stderr)),
}
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(crate::WITCHER_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());
assert!(Error::raw("").downcast_mut::<Error>().is_some());
}
}