#![cfg_attr(nightly, feature(error_generic_member_access))]
use std::{
any::Any,
borrow::Cow,
fmt::{self, Debug, Display},
str::FromStr,
};
use mdsn::DsnError;
use source::Inner;
use thiserror::Error;
mod code;
mod source;
pub use code::Code;
#[derive(Error)]
#[must_use]
pub struct Error {
code: Code,
context: Option<String>,
#[cfg_attr(nightly, backtrace)]
source: Inner,
}
unsafe impl Send for Error {}
unsafe impl Sync for Error {}
impl Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_struct("Error")
.field("code", &self.code)
.field("context", &self.context)
.field("source", &self.source)
.finish()
} else {
if self.code != Code::FAILED {
write!(f, "[{:#06X}] ", self.code)?;
}
if let Some(context) = &self.context {
f.write_fmt(format_args!("{}", context))?;
writeln!(f)?;
writeln!(f)?;
writeln!(f, "Caused by:")?;
let chain = self.source.chain();
for (idx, source) in chain.enumerate() {
writeln!(f, "{:4}: {}", idx, source)?;
}
} else {
let mut chain = self.source.chain();
if let Some(context) = chain.next() {
f.write_fmt(format_args!("{}", context))?;
}
if self.source.deep() {
writeln!(f)?;
writeln!(f)?;
writeln!(f, "Caused by:")?;
for (idx, source) in chain.enumerate() {
writeln!(f, "{:4}: {}", idx, source)?;
}
}
}
#[cfg(nightly)]
{
writeln!(f)?;
writeln!(f, "Backtrace:")?;
writeln!(f, "{}", self.source.backtrace())?;
}
Ok(())
}
}
}
impl Display for Error {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.code != Code::FAILED {
write!(f, "[{:#06X}] ", self.code)?;
}
if let Some(context) = self.context.as_deref() {
write!(f, "{}", context)?;
if self.source.is_empty() {
return Ok(());
}
f.write_str(": ")?;
} else if self.source.is_empty() {
return f.write_str("Unknown error");
}
if f.alternate() {
write!(f, "{:#}", self.source)?;
} else {
write!(f, "{}", self.source)?;
}
Ok(())
}
}
impl From<DsnError> for Error {
fn from(dsn: DsnError) -> Self {
Self::new(Code::FAILED, dsn.to_string())
}
}
impl From<anyhow::Error> for Error {
fn from(error: anyhow::Error) -> Self {
Self {
code: Code::FAILED,
context: None,
source: Inner::any(error),
}
}
}
impl<C: Into<Code>> From<C> for Error {
fn from(value: C) -> Self {
Self::from_code(value.into())
}
}
impl<'a> From<&'a str> for Error {
fn from(value: &'a str) -> Self {
Self::from_string(value.to_string())
}
}
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
#[inline(always)]
pub fn new_with_context(
code: impl Into<Code>,
err: impl Into<String>,
context: impl Into<String>,
) -> Self {
Self {
code: code.into(),
context: Some(context.into()),
source: err.into().into(),
}
}
#[inline]
pub fn new(code: impl Into<Code>, err: impl Into<String>) -> Self {
Self {
code: code.into(),
context: None,
source: err.into().into(),
}
}
#[inline]
pub fn context(mut self, context: impl Into<String>) -> Self {
self.context = Some(match self.context {
Some(pre) => format!("{}: {}", context.into(), pre),
None => context.into(),
});
self
}
#[inline]
#[deprecated = "Use self.code() instead"]
pub fn errno(&self) -> Code {
self.code
}
#[inline]
pub const fn code(&self) -> Code {
self.code
}
#[inline]
pub fn message(&self) -> String {
self.source.to_string()
}
#[inline(always)]
pub fn from_code(code: impl Into<Code>) -> Self {
let code = code.into();
if let Some(str) = code._priv_err_str() {
Self::new(code, str)
} else {
Self {
code,
context: None,
source: Inner::empty(),
}
}
}
#[inline]
pub fn from_string(err: impl Into<Cow<'static, str>>) -> Self {
anyhow::format_err!("{}", err.into()).into()
}
#[inline]
pub fn from_any(err: impl Into<anyhow::Error>) -> Self {
err.into().into()
}
#[inline]
pub fn any(err: impl Into<anyhow::Error> + 'static) -> Self {
if err.type_id() == std::any::TypeId::of::<Self>() {
let err = &err as &dyn Any;
let err = err.downcast_ref::<Self>().unwrap();
dbg!(err);
return Self {
code: err.code,
context: err.context.clone(),
source: err.source.clone(),
};
}
err.into().into()
}
#[inline]
pub fn success(&self) -> bool {
self.code == 0
}
}
#[macro_export]
macro_rules! format_err {
(code = $c:expr, raw = $arg:expr, context = $arg2:expr) => {
$crate::Error::new_with_context($c, $arg, $arg2)
};
(code = $c:expr, raw = $arg:expr) => {
$crate::Error::new($c, $arg)
};
(code = $c:expr, raw = ($($arg:tt)*), context = ($($arg2:tt)*) $(,)?) => {
$crate::Error::new_with_context($c, __priv_format!($($arg)*), __priv_format!($($arg2)*))
};
(code = $c:expr, context = $($arg2:tt)*) => {
$crate::Error::from($c).context(format!($($arg2)*))
};
(code = $c:expr, raw = $arg:literal, context = $($arg2:tt)*) => {
$crate::Error::new_with_context($c, format!($arg), $crate::__priv_format!($($arg2)*))
};
(code = $c:expr, raw = $arg:ident, context = $($arg2:tt)*) => {
$crate::Error::new_with_context($c, $arg, $crate::__priv_format!($($arg2)*))
};
(code = $c:expr) => {
$crate::Error::from_code($c)
};
(code = $c:expr, raw = $($arg:tt)*) => {
$crate::Error::new($c, format!($($arg)*))
};
(code = $c:expr, $($arg:tt)*) => {
$crate::Error::new($c, format!($($arg)*))
};
(any = $($arg:tt)*) => {
$crate::Error::from_string(format!($($arg)*))
};
(raw = $($arg:tt)*) => {
compile_error!("`raw` error message must be used along with an error code!")
};
($c:expr, raw = $arg:expr) => {
$crate::Error::new($c, $arg)
};
($c:expr, raw = $arg:expr, context = $arg2:expr) => {
$crate::Error::new_with_context($c, $arg, $arg2)
};
($c:expr, raw = ($($arg:tt)*), context = ($($arg2:tt)*) $(,)?) => {
$crate::Error::new_with_context($c, format!($($arg)*), format!($($arg2)*))
};
($c:expr, context = $arg:expr) => {
$crate::Error::from($c).context($arg)
};
($c:expr, context = $($arg2:tt)*) => {
$crate::Error::from($c).context(format!($($arg2)*))
};
($c:expr, raw = $($arg:tt)*) => {
$crate::Error::new($c, format!($($arg)*))
};
($c:expr) => {
$crate::Error::from($c)
};
($($arg:tt)*) => {
$crate::Error::from_string(format!($($arg)*))
};
}
#[macro_export]
macro_rules! __priv_format {
($msg:literal $(,)?) => {
literal.to_string()
};
($err:expr $(,)?) => {
$err
};
($fmt:expr, $($arg:tt)*) => {
format!($fmt, $($arg)*)
};
}
#[macro_export]
macro_rules! bail {
($($arg:tt)*) => {
return std::result::Result::Err($crate::format_err!($($arg)*))
};
}
impl FromStr for Error {
type Err = ();
#[inline]
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(Self::from_string(s.to_string()))
}
}
#[cfg(feature = "serde")]
impl serde::de::Error for Error {
#[inline]
fn custom<T: fmt::Display>(msg: T) -> Error {
Error::from_string(format!("{}", msg))
}
}
#[test]
fn test_format_err() {
let code = 0xF000;
let raw = "Nothing";
let context = "Error";
let err = dbg!(format_err!(code, raw = raw, context = context));
assert_eq!(err.to_string(), "[0xF000] Error: Internal error: `Nothing`");
let err = dbg!(format_err!(code, raw = raw));
assert_eq!(err.to_string(), "[0xF000] Internal error: `Nothing`");
let err = dbg!(format_err!(code, context = context));
assert_eq!(err.to_string(), "[0xF000] Error");
let err = dbg!(format_err!(code = 0xF000, context = "Error here"));
assert_eq!(err.to_string(), "[0xF000] Error here");
let err = dbg!(format_err!(0x6789, context = "Error here: {}", 1));
assert_eq!(err.to_string(), "[0x6789] Error here: 1");
let err = dbg!(format_err!(code = 0x6789, context = "Error here: {}", 1));
assert_eq!(err.to_string(), "[0x6789] Error here: 1");
let err = dbg!(format_err!(code = 0x6789, raw = "Error here: {}", 1));
assert_eq!(err.to_string(), "[0x6789] Internal error: `Error here: 1`");
let err = dbg!(format_err!(0x6789, raw = "Error here: {}", 1));
assert_eq!(err.to_string(), "[0x6789] Internal error: `Error here: 1`");
let err = dbg!(format_err!(
code = 0x6789,
raw = ("Error here: {}", 1),
context = ("Query error with {:?}", "sql"),
));
assert_eq!(
err.to_string(),
"[0x6789] Query error with \"sql\": Internal error: `Error here: 1`"
);
let err = dbg!(format_err!("Error here"));
assert_eq!(err.to_string(), "Error here");
let err = dbg!(format_err!(0x2603));
assert_eq!(
err.to_string(),
"[0x2603] Internal error: `Table does not exist`"
);
let err = dbg!(format_err!(0x6789));
assert_eq!(err.to_string(), "[0x6789] Unknown error");
let err = dbg!(format_err!(0x6789, context = "Error here"));
assert_eq!(err.to_string(), "[0x6789] Error here");
}
#[test]
fn test_bail() {
fn use_bail() -> Result<()> {
bail!(code = 0x2603, context = "Failed to insert into table `abc`");
}
let err = use_bail();
dbg!(&err);
assert!(err.is_err());
println!("{:?}", err.unwrap_err());
println!("{:?}", Error::any(use_bail().unwrap_err()));
}
#[test]
fn test_display() {
let err = Error::new(Code::SUCCESS, "Success").context("nothing");
assert!(dbg!(format!("{}", err)).contains("[0x0000] nothing"));
let result = std::panic::catch_unwind(|| {
let err = Error::new(Code::SUCCESS, "Success").context("nothing");
panic!("{:?}", err);
});
assert!(result.is_err());
}
#[test]
fn test_error() {
let err = Error::new(Code::SUCCESS, "success");
assert_eq!(err.code(), Code::SUCCESS);
assert_eq!(err.message(), "Internal error: `success`");
let _ = Error::from_code(1);
assert_eq!(Error::from_string("any").to_string(), "any");
assert_eq!(Error::from_string("any").to_string(), "any");
fn raise_error() -> Result<()> {
Err(Error::from_any(DsnError::InvalidDriver("mq".to_string())))
}
assert_eq!(raise_error().unwrap_err().to_string(), "invalid driver mq");
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_error() {
use serde::de::Error as DeError;
let _ = Error::custom("");
}