use core::any::type_name;
use core::error::Error;
use core::ops::Deref;
use core::panic::Location;
use core::{borrow, fmt, panic};
#[cfg(feature = "backtrace")]
use std::backtrace::Backtrace;
#[cfg(feature = "backtrace")]
use std::borrow::Cow;
#[cfg(feature = "backtrace")]
use std::sync::Arc;
pub struct LocatedError<E: Error> {
inner: E,
location: &'static Location<'static>,
#[cfg(feature = "backtrace")]
backtrace: Arc<Backtrace>,
}
impl<E: Error> Error for LocatedError<E> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.inner.source()
}
}
#[cfg(feature = "backtrace")]
const DEBUG_CAUSED_BY_PAT: &str = "Caused by: ";
const DISPLAY_CAUSED_BY_PAT: &str = "; Caused by ";
impl<E: Error> fmt::Display for LocatedError<E> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let inner_msg = format!("{}", self.inner);
if let Some(pos) = inner_msg.find(DISPLAY_CAUSED_BY_PAT) {
write!(
f,
"{}{DISPLAY_CAUSED_BY_PAT}{} ({}){}",
&inner_msg[..pos],
type_name::<E>(),
self.location,
&inner_msg[pos..]
)
} else {
write!(
f,
"{}{DISPLAY_CAUSED_BY_PAT}{}({});",
self.inner,
type_name::<E>(),
self.location,
)
}
}
#[cfg(not(feature = "std"))]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{DISPLAY_CAUSED_BY_PAT}{}({});",
self.inner,
type_name::<E>(),
self.location,
)
}
}
impl<E: Error> fmt::Debug for LocatedError<E> {
#[cfg(not(feature = "backtrace"))]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:?} at ({}) by {}",
self.inner,
self.location,
type_name::<E>(), )
}
#[cfg(feature = "backtrace")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(stacktrace) = super::stacktrace::StackTrace::parse(&self.backtrace) {
self.fmt_stacktrace(stacktrace, f)
} else {
write!(
f,
"{:?} at ({}) by {}",
self.inner,
self.location,
type_name::<E>() )
}
}
}
#[cfg(feature = "backtrace")]
impl<E: Error> LocatedError<E> {
fn fmt_stacktrace(
&self,
stacktrace: super::stacktrace::StackTrace,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let mut output = Vec::new();
let inner_debug = format!("{:?}", self.inner);
let mut lines = inner_debug.lines();
let mut first_caused_by = true;
while let Some(line) = lines.next() {
if first_caused_by {
if line.starts_with(DEBUG_CAUSED_BY_PAT) {
first_caused_by = false;
self.inject_stacktrace(&stacktrace, &mut output);
}
output.push(Cow::Borrowed(line));
} else {
let line = Cow::Borrowed(line);
if !output.contains(&line) {
output.push(line);
}
}
}
if first_caused_by {
self.inject_stacktrace(&stacktrace, &mut output);
}
for line in output {
writeln!(f, "{}", line)?;
}
write!(f, "")
}
#[cfg(feature = "backtrace")]
fn inject_stacktrace(
&self,
stacktrace: &crate::stacktrace::StackTrace,
output: &mut Vec<Cow<'_, str>>,
) {
let cause = format!(
"{DEBUG_CAUSED_BY_PAT}{}: {} ({})",
type_name::<E>(),
self.pure_desc(),
self.location
);
output.push(Cow::Owned(cause));
for frame in &stacktrace.frames {
let trace = if frame.file.is_empty() {
format!("\tat {}", frame.func)
} else {
format!("\tat {} ({}:{})", frame.func, frame.file, frame.line)
};
output.push(Cow::Owned(trace));
}
}
fn pure_desc(&self) -> String {
let desc = self.inner.to_string();
let desc = if let Some(pos) = desc.find(DISPLAY_CAUSED_BY_PAT) {
desc[..pos].to_string()
} else {
desc
};
desc
}
}
impl<E: Error> From<E> for LocatedError<E> {
#[track_caller]
fn from(err: E) -> Self {
LocatedError {
inner: err,
location: panic::Location::caller(),
#[cfg(all(feature = "backtrace", not(feature = "force_backtrace")))]
backtrace: Arc::new(Backtrace::capture()),
#[cfg(feature = "force_backtrace")]
backtrace: Arc::new(Backtrace::force_capture()), }
}
}
impl<T: Error> AsRef<T> for LocatedError<T> {
fn as_ref(&self) -> &T {
&self.inner
}
}
impl<T: Error> Deref for LocatedError<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T: Error> borrow::Borrow<T> for LocatedError<T> {
fn borrow(&self) -> &T {
&self.inner
}
}
unsafe impl<T: Error + Send> Send for LocatedError<T> {}
unsafe impl<T: Error + Sync> Sync for LocatedError<T> {}
impl<T: Error + Clone> Clone for LocatedError<T> {
fn clone(&self) -> Self {
LocatedError {
inner: self.inner.clone(),
location: self.location,
#[cfg(feature = "backtrace")]
backtrace: self.backtrace.clone(),
}
}
}
#[cfg(test)]
mod tests {
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MyError {
#[error("MyError {0}")]
LocatedIoError(#[from] LocatedError<std::io::Error>),
}
impl From<std::io::Error> for MyError {
#[track_caller]
fn from(err: std::io::Error) -> Self {
MyError::from(LocatedError::from(err))
}
}
fn located_error1() -> Result<(), MyError> {
std::fs::File::open("blurb.txt").map_err(|e| LocatedError::<std::io::Error>::from(e))?;
Ok(())
}
fn located_error2() -> Result<(), MyError> {
std::fs::File::open("blurb.txt")?;
Ok(())
}
use super::*;
#[test]
fn test_located_error() {
if let Err(e) = located_error1() {
println!("==========================================================");
println!("{}", e);
println!("==========================================================");
println!("{:?}", e);
}
if let Err(e) = located_error2() {
println!("==========================================================");
println!("{}", e);
println!("==========================================================");
println!("{:?}", e);
}
}
}