use std::fmt::{self, Debug, Display};
use std::panic;
use std::sync::Arc;
use super::*;
#[cfg(feature = "backtrace")]
mod ie_backtrace {
use super::*;
use backtrace::Backtrace;
#[derive(Debug, Clone)]
pub(crate) struct Captured(Backtrace);
pub(crate) fn capture() -> Captured {
Captured(Backtrace::new())
}
impl Display for Captured {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Debug::fmt(self, f)
}
}
}
#[cfg(not(feature = "backtrace"))]
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)))
}
}
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(())
}
}
#[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 )* } => {
|source| $crate::Bug::from_error($crate::ErrorKind::Internal, source, format!($($arg)*))
}
}
#[macro_export]
macro_rules! into_bad_api_usage {
{ $( $arg:tt )* } => {
|source| $crate::Bug::from_error($crate::ErrorKind::BadApiUsage, source, format!($($arg)*))
}
}
impl HasKind for Bug {
fn kind(&self) -> ErrorKind {
self.0.kind
}
}
#[allow(clippy::unwrap_used)]
#[cfg(test)]
mod test {
use super::*;
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
#[test]
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();
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"));
}
#[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);
}
}