use std::fmt::{self, Debug, Display};
use std::panic;
use std::sync::Arc;
use super::*;
#[cfg(all(feature = "backtrace", not(miri)))]
mod ie_backtrace {
use super::*;
use std::backtrace::Backtrace;
#[derive(Debug, Clone)]
pub(crate) struct Captured(Arc<Backtrace>);
pub(crate) fn capture() -> Captured {
Captured(Arc::new(Backtrace::force_capture()))
}
impl Display for Captured {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
}
#[cfg(any(not(feature = "backtrace"), miri))]
mod ie_backtrace {
use super::*;
#[derive(Debug, Clone)]
pub(crate) struct Captured;
pub(crate) fn capture() -> Captured {
Captured
}
impl Display for Captured {
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
Ok(())
}
}
}
#[derive(Debug, Clone)]
pub struct Bug(Box<BugRepr>);
type SourceError = Arc<dyn std::error::Error + Send + Sync + 'static>;
#[derive(Debug, Clone)]
struct BugRepr {
message: String,
location: &'static panic::Location<'static>,
backtrace: ie_backtrace::Captured,
source: Option<SourceError>,
kind: ErrorKind,
}
impl Bug {
#[track_caller]
pub fn new<S: Into<String>>(kind: ErrorKind, message: S) -> Self {
Bug::new_inner(kind, message.into(), None)
}
#[track_caller]
fn new_inner(kind: ErrorKind, message: String, source: Option<SourceError>) -> Self {
Bug(BugRepr {
kind,
message,
source,
location: panic::Location::caller(),
backtrace: ie_backtrace::capture(),
}
.into())
}
#[track_caller]
pub fn from_error<E, S>(kind: ErrorKind, source: E, message: S) -> Self
where
S: Into<String>,
E: std::error::Error + Send + Sync + 'static,
{
Bug::new_inner(kind, message.into(), Some(Arc::new(source)))
}
pub fn bug_context(mut self, prepend: impl Display) -> Self {
self.0.message = format!("{prepend}: {}", self.0.message);
self
}
}
impl std::error::Error for Bug {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.0
.source
.as_deref()
.map(|traitobj| traitobj as _ )
}
}
impl Display for Bug {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
f,
"{} at {}: {}",
self.0.kind, &self.0.location, &self.0.message
)?;
Display::fmt(&self.0.backtrace, f)?;
Ok(())
}
}
pub trait BugContext: Sealed {
fn bug_context<D: Display>(self, prefix: D) -> Self;
}
impl<T> BugContext for Result<T, Bug> {
fn bug_context<D: Display>(self, prefix: D) -> Self {
self.map_err(move |e| e.bug_context(prefix))
}
}
impl<T> Sealed for Result<T, Bug> {}
mod sealed {
pub trait Sealed {}
}
use sealed::Sealed;
#[macro_export]
macro_rules! internal {
{ $( $arg:tt )* } => {
$crate::Bug::new($crate::ErrorKind::Internal, format!($($arg)*))
}
}
#[macro_export]
macro_rules! bad_api_usage {
{ $( $arg:tt )* } => {
$crate::Bug::new($crate::ErrorKind::BadApiUsage, format!($($arg)*))
}
}
#[macro_export]
macro_rules! into_internal {
{ $( $arg:tt )* } => {
std::convert::identity( |source| $crate::Bug::from_error($crate::ErrorKind::Internal, source, format!($($arg)*))
)
}
}
#[macro_export]
macro_rules! into_bad_api_usage {
{ $( $arg:tt )* } => {
std::convert::identity( |source| $crate::Bug::from_error($crate::ErrorKind::BadApiUsage, source, format!($($arg)*))
)
}
}
impl HasKind for Bug {
fn kind(&self) -> ErrorKind {
self.0.kind
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
use super::*;
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
#[test]
#[inline(never)]
fn internal_macro_test() {
let start_of_func = line!();
let e = internal!("Couldn't {} the {}.", "wobble", "wobbling device");
assert_eq!(e.0.message, "Couldn't wobble the wobbling device.");
assert!(e.0.location.file().ends_with("internal.rs"));
assert!(e.0.location.line() > start_of_func);
assert!(e.0.source.is_none());
let s = e.to_string();
dbg!(&s);
assert!(s.starts_with("internal error (bug) at "));
assert!(s.contains("Couldn't wobble the wobbling device."));
#[cfg(feature = "backtrace")]
assert!(s.contains("internal_macro_test"));
#[derive(thiserror::Error, Debug)]
enum Wrap {
#[error("Internal error")]
Internal(#[from] Bug),
}
let w: Wrap = e.into();
let s = format!("Got: {}", w.report());
dbg!(&s);
assert!(s.contains("Couldn't wobble the wobbling device."));
}
#[test]
fn source() {
use std::error::Error;
use std::str::FromStr;
let start_of_func = line!();
let s = "penguin";
let inner = u32::from_str(s).unwrap_err();
let outer = u32::from_str(s)
.map_err(into_internal!("{} is not a number", s))
.unwrap_err();
let afterwards = line!();
assert_eq!(outer.source().unwrap().to_string(), inner.to_string());
assert!(outer.0.location.line() > start_of_func);
assert!(outer.0.location.line() < afterwards);
}
}