use std::{
borrow::Cow,
error::Error,
fmt::{Debug, Display, Write},
panic::Location,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum ErrorLoc {
CreatedAt(Location<'static>),
WrappedAt(Location<'static>),
}
impl AsRef<Location<'static>> for ErrorLoc {
#[inline]
fn as_ref(&self) -> &Location<'static> {
match self {
Self::CreatedAt(loc) => loc,
Self::WrappedAt(loc) => loc,
}
}
}
impl Display for ErrorLoc {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CreatedAt(loc) => write!(f, "Created@{}", loc),
Self::WrappedAt(loc) => write!(f, "Wrapped@{}", loc),
}
}
}
#[derive(Clone)]
pub struct LibErr {
loc: ErrorLoc,
msg: Cow<'static, str>,
src: Option<Box<LibErr>>,
}
impl LibErr {
#[inline]
pub fn loc(&self) -> &Location<'static> {
self.loc.as_ref()
}
#[inline]
pub fn has_source(&self) -> bool {
self.src.is_some()
}
}
impl Debug for LibErr {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "\n{}", self)?;
let sources = self.iter_sources();
if let (1, _) = sources.size_hint() {
for src in sources {
if src.has_source() {
writeln!(f, "-> {}", src)?;
} else {
writeln!(f, "\\-> {}", src)?;
}
}
f.write_char('\n')?;
}
Ok(())
}
}
impl Display for LibErr {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}\t-> {}", self.loc, self.msg)?;
Ok(())
}
}
impl Error for LibErr {
#[inline]
fn description(&self) -> &str {
&self.msg
}
#[inline]
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.src {
Some(src) => Some(src.as_ref()),
None => None,
}
}
}
unsafe impl Send for LibErr {}
impl LibErr {
#[inline]
#[track_caller]
pub fn new<T: ToString>(msg: T) -> Self {
Self {
loc: ErrorLoc::CreatedAt(*Location::caller()),
msg: Cow::Owned(msg.to_string()),
src: None,
}
}
#[inline]
#[track_caller]
pub const fn new_static(msg: &'static str) -> Self {
Self {
loc: ErrorLoc::CreatedAt(*Location::caller()),
msg: Cow::Borrowed(msg),
src: None,
}
}
#[inline]
pub fn get_source(&self) -> Option<&LibErr> {
self.src.as_ref().map(AsRef::as_ref)
}
#[inline]
#[track_caller]
pub fn from_err<E: Error>(err: E) -> Self {
Self {
msg: Cow::Owned(err.to_string()),
loc: ErrorLoc::WrappedAt(*Location::caller()),
src: err.source().map(|err| Box::new(Self::from_err(err))),
}
}
#[inline]
pub fn iter_sources(&self) -> TraceIter<'_> {
TraceIter { cur: self }
}
#[inline]
#[track_caller]
pub fn derive_err<T: ToString>(self, msg: T) -> Self {
Self {
msg: Cow::Owned(msg.to_string()),
loc: ErrorLoc::CreatedAt(*Location::caller()),
src: Some(Box::new(self)),
}
}
#[inline]
pub fn write_trace<F: std::fmt::Write>(&self, f: &mut F) -> std::fmt::Result {
for err in self.iter_sources() {
writeln!(f, "{}", err)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct TraceIter<'a> {
cur: &'a LibErr,
}
impl<'a> Iterator for TraceIter<'a> {
type Item = &'a LibErr;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let src = self.cur.get_source()?;
self.cur = src;
Some(src)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if self.cur.has_source() {
(1, None)
} else {
(0, Some(0))
}
}
}
#[macro_export]
macro_rules! create_err {
($msg: literal) => {
$crate::LibErr::new_static(concat!($msg))
};
($msg: expr) => {
$crate::LibErr::new($msg)
};
($err: expr, $msg: expr) => {
$crate::LibErr::derive_err($crate::LibErr::from($err), $msg)
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_error() {
let err = LibErr::new("Test error");
assert_eq!(err.msg, "Test error");
assert!(matches!(err.loc, ErrorLoc::CreatedAt(_)));
assert!(err.get_source().is_none());
}
#[test]
fn test_new_static_error() {
let err = LibErr::new_static("Static error");
assert_eq!(err.msg, "Static error");
assert!(matches!(err.loc, ErrorLoc::CreatedAt(_)));
assert!(err.get_source().is_none());
}
#[test]
fn test_from_err_wraps_error() {
let base_err = std::io::Error::new(std::io::ErrorKind::Other, "IO failure");
let wrapped = LibErr::from_err(base_err);
assert_eq!(wrapped.msg, "IO failure");
assert!(matches!(wrapped.loc, ErrorLoc::WrappedAt(_)));
assert!(wrapped.get_source().is_none());
}
#[test]
fn test_wraps_another_crateerr() {
let base_err = LibErr::new("Base error");
let wrapped = base_err.derive_err("Additional context");
assert_eq!(wrapped.msg, "Additional context");
assert!(wrapped.get_source().is_some());
assert_eq!(wrapped.get_source().unwrap().msg, "Base error");
}
#[test]
fn test_iter_sources() {
let base_err = LibErr::new("Base error");
let mid_err = base_err.derive_err("Mid error");
let top_err = mid_err.derive_err("Top error");
let mut iter = top_err.iter_sources();
assert_eq!(iter.next().unwrap().msg, "Mid error");
assert_eq!(iter.next().unwrap().msg, "Base error");
assert!(iter.next().is_none());
}
#[test]
fn test_macro_create_err() {
let err1 = create_err!("Static macro error");
let err2 = create_err!("Dynamic macro error".to_string());
assert_eq!(err1.msg, "Static macro error");
assert_eq!(err2.msg, "Dynamic macro error");
}
#[test]
fn test_macro_create_err_with_chaining() {
let base_err = create_err!("Initial error");
let derived_err = create_err!(base_err, "Derived error");
assert_eq!(derived_err.msg, "Derived error");
assert!(derived_err.get_source().is_some());
assert_eq!(derived_err.get_source().unwrap().msg, "Initial error");
}
#[test]
fn test_error_loc_tracking() {
let err1 = LibErr::new("Error location test");
let err2 = LibErr::from_err(err1.clone());
assert!(matches!(err1.loc, ErrorLoc::CreatedAt(_)));
assert!(matches!(err2.loc, ErrorLoc::WrappedAt(_)));
}
}