use std::ops::Deref;
use std::panic::Location;
#[cfg(any(feature = "backtrace", feature = "force_backtrace"))]
use std::sync::Arc;
pub struct LocatedError<E: std::error::Error> {
inner: E,
location: &'static Location<'static>,
#[cfg(any(feature = "backtrace", feature = "force_backtrace"))]
backtrace: Arc<std::backtrace::Backtrace>,
}
impl<E: std::error::Error> std::error::Error for LocatedError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.inner.source()
}
}
impl<E: std::error::Error> std::fmt::Display for LocatedError<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
const PAT: &str = "; Caused by ";
let inner_msg = format!("{}", self.inner);
if let Some(pos) = inner_msg.find(PAT) {
write!(
f,
"{}; Caused by {} ({}){}",
&inner_msg[..pos],
std::any::type_name::<E>(),
self.location,
&inner_msg[pos..]
)
} else {
write!(
f,
"{}; Caused by {}({});",
self.inner,
std::any::type_name::<E>(),
self.location,
)
}
}
}
impl<E: std::error::Error> std::fmt::Debug for LocatedError<E> {
#[cfg(not(any(feature = "backtrace", feature = "force_backtrace")))]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:?}\n\tat ({}) by {}",
self.inner,
self.location,
std::any::type_name::<E>(), )
}
#[cfg(any(feature = "backtrace", feature = "force_backtrace"))]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(stacktrace) = super::stacktrace::StackTrace::parse(&self.backtrace) {
self.fmt_stacktrace(stacktrace, f)
} else {
write!(
f,
"{:?}\n\tat ({}) by {}",
self.inner,
self.location,
std::any::type_name::<E>() )
}
}
}
#[cfg(any(feature = "backtrace", feature = "force_backtrace"))]
impl<E: std::error::Error> LocatedError<E> {
fn fmt_stacktrace(
&self,
stacktrace: super::stacktrace::StackTrace,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
const CAUSED_BY_PAT: &str = "\nCaused by: ";
let inner_msg = format!("{:?} ({})", self.inner, self.location);
let index = if let Some(cause_index) = inner_msg.find(CAUSED_BY_PAT) {
let msg_head = &inner_msg[0..cause_index + 1];
write!(f, "{}", msg_head)?;
cause_index
} else {
writeln!(f, "{}", inner_msg)?;
0
};
writeln!(
f,
"Caused by: {}: {}",
std::any::type_name::<E>(),
self.pure_desc()
)?;
for frame in stacktrace.frames {
let line = if frame.file.is_empty() {
format!("\tat {}", frame.func)
} else {
format!("\tat {} ({}:{})", frame.func, frame.file, frame.line)
};
if inner_msg.find(&line).is_some() {
continue;
}
writeln!(f, "{}", line)?;
}
if index > 0 {
let msg_remain = &inner_msg[index + 1..];
write!(f, "{}", msg_remain)?;
}
write!(f, "")
}
fn pure_desc(&self) -> String {
const PAT: &str = "; Caused by ";
let desc = self.inner.to_string();
let desc = if let Some(pos) = desc.find(PAT) {
desc[..pos].to_string()
} else {
desc
};
desc
}
}
impl<E: std::error::Error> From<E> for LocatedError<E> {
#[track_caller]
fn from(err: E) -> Self {
LocatedError {
inner: err,
location: std::panic::Location::caller(),
#[cfg(all(feature = "backtrace", not(feature = "force_backtrace")))]
backtrace: Arc::new(std::backtrace::Backtrace::capture()),
#[cfg(feature = "force_backtrace")]
backtrace: Arc::new(std::backtrace::Backtrace::force_capture()), }
}
}
impl<T: std::error::Error> AsRef<T> for LocatedError<T> {
fn as_ref(&self) -> &T {
&self.inner
}
}
impl<T: std::error::Error> Deref for LocatedError<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T: std::error::Error> std::borrow::Borrow<T> for LocatedError<T> {
fn borrow(&self) -> &T {
&self.inner
}
}
unsafe impl<T: std::error::Error + Send> Send for LocatedError<T> {}
unsafe impl<T: std::error::Error + Sync> Sync for LocatedError<T> {}
impl<T: std::error::Error + Clone> Clone for LocatedError<T> {
fn clone(&self) -> Self {
LocatedError {
inner: self.inner.clone(),
location: self.location,
#[cfg(any(feature = "backtrace", feature = "force_backtrace"))]
backtrace: self.backtrace.clone(),
}
}
}
#[cfg(test)]
mod tests {
use thiserror::Error;
use super::*;
#[test]
fn test_located_error() {
#[derive(Debug, Error)]
pub enum MyError {
#[error("{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(())
}
if let Err(e) = located_error1() {
println!("error {}", e);
println!("error {:?}", e);
}
if let Err(e) = located_error2() {
println!("error {}", e);
println!("error {:?}", e);
}
}
}