use crate::{Err, ReasonAndSource, SendSyncNonNull};
#[cfg(any(feature = "notify", feature = "notify-tokio"))]
use crate::notify;
use std::{any, error, fmt, marker, panic, ptr};
#[cfg(any(feature = "notify", feature = "notify-tokio"))]
use std::sync::atomic;
unsafe impl<T: Send + Sync> Send for SendSyncNonNull<T> {}
unsafe impl<T: Send + Sync> Sync for SendSyncNonNull<T> {}
impl<T: Send + Sync> SendSyncNonNull<T> {
fn new(non_null_ptr: ptr::NonNull<T>) -> Self {
Self {
non_null_ptr,
_phantom: marker::PhantomData,
}
}
}
impl Err {
#[track_caller]
pub fn new<R>(reason: R) -> Self
where
R: fmt::Debug + Send + Sync + 'static,
{
let loc = panic::Location::caller();
let boxed = Box::new(ReasonAndSource::<R>::new(reason));
let ptr = ptr::NonNull::from(Box::leak(boxed)).cast::<ReasonAndSource>();
#[cfg(any(feature = "notify", feature = "notify-tokio"))]
{
let err_notified = Self {
file: loc.file(),
line: loc.line(),
reason_and_source: SendSyncNonNull::new(ptr),
};
if let Err(e) = notify::notify_err(err_notified) {
eprintln!("ERROR(errs): {e:?}");
}
Self {
file: loc.file(),
line: loc.line(),
reason_and_source: SendSyncNonNull::new(ptr),
}
}
#[cfg(not(any(feature = "notify", feature = "notify-tokio")))]
{
Self {
file: loc.file(),
line: loc.line(),
reason_and_source: SendSyncNonNull::new(ptr),
}
}
}
#[track_caller]
pub fn with_source<R, E>(reason: R, source: E) -> Self
where
R: fmt::Debug + Send + Sync + 'static,
E: error::Error + Send + Sync + 'static,
{
let loc = panic::Location::caller();
let boxed = Box::new(ReasonAndSource::<R, E>::with_source(reason, source));
let ptr = ptr::NonNull::from(Box::leak(boxed)).cast::<ReasonAndSource>();
#[cfg(any(feature = "notify", feature = "notify-tokio"))]
{
let err_notified = Self {
file: loc.file(),
line: loc.line(),
reason_and_source: SendSyncNonNull::new(ptr),
};
if let Err(e) = notify::notify_err(err_notified) {
eprintln!("ERROR(errs): {e:?}");
}
Self {
file: loc.file(),
line: loc.line(),
reason_and_source: SendSyncNonNull::new(ptr),
}
}
#[cfg(not(any(feature = "notify", feature = "notify-tokio")))]
{
Self {
file: loc.file(),
line: loc.line(),
reason_and_source: SendSyncNonNull::new(ptr),
}
}
}
#[inline]
pub fn file(&self) -> &'static str {
self.file
}
#[inline]
pub fn line(&self) -> u32 {
self.line
}
pub fn source(&self) -> Option<&(dyn error::Error + 'static)> {
let source_fn = unsafe { (*self.reason_and_source.non_null_ptr.as_ptr()).source_fn };
source_fn(self.reason_and_source.non_null_ptr)
}
pub fn reason<R>(&self) -> Result<&R, &Self>
where
R: fmt::Debug + Send + Sync + 'static,
{
let type_id = any::TypeId::of::<R>();
let ptr = self.reason_and_source.non_null_ptr.as_ptr();
let is_fn = unsafe { (*ptr).is_fn };
if is_fn(type_id) {
let typed_ptr = ptr as *const ReasonAndSource<R>;
Ok(unsafe { &((*typed_ptr).reason_and_source.0) })
} else {
Err(self)
}
}
pub fn match_reason<R>(&self, func: fn(&R)) -> &Self
where
R: fmt::Debug + Send + Sync + 'static,
{
let type_id = any::TypeId::of::<R>();
let ptr = self.reason_and_source.non_null_ptr.as_ptr();
let is_fn = unsafe { (*ptr).is_fn };
if is_fn(type_id) {
let typed_ptr = ptr as *const ReasonAndSource<R>;
func(unsafe { &((*typed_ptr).reason_and_source.0) });
}
self
}
}
impl Drop for Err {
fn drop(&mut self) {
let drop_fn = unsafe { (*self.reason_and_source.non_null_ptr.as_ptr()).drop_fn };
drop_fn(self.reason_and_source.non_null_ptr);
}
}
impl fmt::Debug for Err {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let debug_fn = unsafe { (*self.reason_and_source.non_null_ptr.as_ptr()).debug_fn };
write!(f, "{} {{ ", any::type_name::<Err>())?;
debug_fn(self.reason_and_source.non_null_ptr, f)?;
write!(f, ", file = {}, line = {}", self.file, self.line)?;
write!(f, " }}")
}
}
impl fmt::Display for Err {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let display_fn = unsafe { (*self.reason_and_source.non_null_ptr.as_ptr()).display_fn };
display_fn(self.reason_and_source.non_null_ptr, f)
}
}
impl error::Error for Err {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
let source_fn = unsafe { (*self.reason_and_source.non_null_ptr.as_ptr()).source_fn };
source_fn(self.reason_and_source.non_null_ptr)
}
}
impl<R, E> ReasonAndSource<R, E>
where
R: fmt::Debug + Send + Sync + 'static,
E: error::Error + Send + Sync + 'static,
{
fn new(reason: R) -> Self {
Self {
is_fn: is_reason::<R>,
drop_fn: drop_reason_and_source::<R, E>,
debug_fn: debug_reason_and_source::<R, E>,
display_fn: display_reason_and_source::<R, E>,
source_fn: get_source::<R, E>,
#[cfg(any(feature = "notify", feature = "notify-tokio"))]
is_referenced_by_another: atomic::AtomicBool::new(true),
reason_and_source: (reason, None),
}
}
fn with_source(reason: R, source: E) -> Self {
Self {
is_fn: is_reason::<R>,
drop_fn: drop_reason_and_source::<R, E>,
debug_fn: debug_reason_and_source::<R, E>,
display_fn: display_reason_and_source::<R, E>,
source_fn: get_source::<R, E>,
#[cfg(any(feature = "notify", feature = "notify-tokio"))]
is_referenced_by_another: atomic::AtomicBool::new(true),
reason_and_source: (reason, Some(*Box::new(source))),
}
}
}
fn is_reason<R>(type_id: any::TypeId) -> bool
where
R: fmt::Debug + Send + Sync + 'static,
{
any::TypeId::of::<R>() == type_id
}
fn drop_reason_and_source<R, E>(ptr: ptr::NonNull<ReasonAndSource>)
where
R: fmt::Debug + Send + Sync + 'static,
E: error::Error + Send + Sync + 'static,
{
let typed_ptr = ptr.cast::<ReasonAndSource<R, E>>().as_ptr();
#[cfg(any(feature = "notify", feature = "notify-tokio"))]
{
let is_ref = unsafe { &(*typed_ptr).is_referenced_by_another };
if !is_ref.fetch_and(false, atomic::Ordering::AcqRel) {
unsafe { drop(Box::from_raw(typed_ptr)) };
}
}
#[cfg(not(any(feature = "notify", feature = "notify-tokio")))]
{
unsafe { drop(Box::from_raw(typed_ptr)) };
}
}
fn debug_reason_and_source<R, E>(
ptr: ptr::NonNull<ReasonAndSource>,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result
where
R: fmt::Debug + Send + Sync + 'static,
E: error::Error + Send + Sync + 'static,
{
let typed_ptr = ptr.cast::<ReasonAndSource<R, E>>().as_ptr();
let reason_and_source = unsafe { &(*typed_ptr).reason_and_source };
write!(
f,
"reason = {} {:?}",
any::type_name::<R>(),
reason_and_source.0
)?;
match &reason_and_source.1 {
Some(src) => write!(f, ", source = {:?}", src),
None => Ok(()),
}
}
fn display_reason_and_source<R, E>(
ptr: ptr::NonNull<ReasonAndSource>,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result
where
R: fmt::Debug + Send + Sync + 'static,
E: error::Error + Send + Sync + 'static,
{
let typed_ptr = ptr.cast::<ReasonAndSource<R, E>>().as_ptr();
write!(f, "{:?}", unsafe { &(*typed_ptr).reason_and_source.0 })
}
fn get_source<R, E>(
ptr: ptr::NonNull<ReasonAndSource>,
) -> Option<&'static (dyn error::Error + 'static)>
where
R: fmt::Debug + Send + Sync + 'static,
E: error::Error + Send + Sync + 'static,
{
let typed_ptr = ptr.cast::<ReasonAndSource<R, E>>().as_ptr();
match unsafe { &(*typed_ptr).reason_and_source.1 } {
Some(src) => Some(src),
None => None,
}
}
#[cfg(test)]
mod tests_of_err {
use super::*;
use std::sync::{LazyLock, Mutex};
struct Logger {
log_vec: Vec<String>,
}
impl Logger {
fn new() -> Self {
Self {
log_vec: Vec::<String>::new(),
}
}
fn log(&mut self, s: &str) {
self.log_vec.push(s.to_string());
}
fn assert_logs(&self, logs: &[&str]) {
if self.log_vec.len() != logs.len() {
assert_eq!(self.log_vec, logs);
return;
}
for i in 0..self.log_vec.len() {
assert_eq!(self.log_vec[i], logs[i]);
}
}
}
const BASE_LINE: u32 = line!();
mod test_of_drop {
use super::*;
static LOGGER: LazyLock<Mutex<Logger>> = LazyLock::new(|| Mutex::new(Logger::new()));
#[allow(dead_code)]
#[derive(Debug)]
enum Enum0 {
InvalidValue { name: String, value: String },
}
impl Drop for Enum0 {
fn drop(&mut self) {
LOGGER.lock().unwrap().log("drop Enum0");
}
}
fn create_err() -> Result<(), Err> {
let err = Err::new(Enum0::InvalidValue {
name: "foo".to_string(),
value: "abc".to_string(),
});
LOGGER.lock().unwrap().log("created Enum0");
Err(err)
}
fn consume_err() {
let err = create_err().unwrap_err();
assert_eq!(
format!("{err}"),
"InvalidValue { name: \"foo\", value: \"abc\" }",
);
#[cfg(unix)]
assert_eq!(
format!("{err:?}"),
format!("errs::Err {{ reason = errs::err::tests_of_err::test_of_drop::Enum0 InvalidValue {{ name: \"foo\", value: \"abc\" }}, file = src/err.rs, line = {} }}", BASE_LINE + 19),
);
#[cfg(windows)]
assert_eq!(
format!("{err:?}"),
format!("errs::Err {{ reason = errs::err::tests_of_err::test_of_drop::Enum0 InvalidValue {{ name: \"foo\", value: \"abc\" }}, file = src\\err.rs, line = {} }}", BASE_LINE + 19),
);
LOGGER.lock().unwrap().log("consumed Enum0");
}
#[test]
fn test() {
consume_err();
std::thread::sleep(std::time::Duration::from_secs(1));
LOGGER.lock().unwrap().log("end");
LOGGER.lock().unwrap().assert_logs(&[
"created Enum0",
"consumed Enum0",
"drop Enum0",
"end",
]);
}
}
mod test_of_new {
use super::*;
#[derive(Debug)]
enum Enum0 {
InvalidValue { name: String, value: String },
}
#[test]
fn new_err() {
let err = Err::new(Enum0::InvalidValue {
name: "foo".to_string(),
value: "abc".to_string(),
});
#[cfg(unix)]
assert_eq!(err.file(), "src/err.rs");
#[cfg(windows)]
assert_eq!(err.file(), "src\\err.rs");
assert_eq!(err.line(), BASE_LINE + 74);
assert_eq!(
format!("{err}"),
"InvalidValue { name: \"foo\", value: \"abc\" }",
);
#[cfg(unix)]
assert_eq!(
format!("{err:?}"),
format!("errs::Err {{ reason = errs::err::tests_of_err::test_of_new::Enum0 InvalidValue {{ name: \"foo\", value: \"abc\" }}, file = src/err.rs, line = {} }}", BASE_LINE + 74),
);
#[cfg(windows)]
assert_eq!(
format!("{err:?}"),
format!("errs::Err {{ reason = errs::err::tests_of_err::test_of_new::Enum0 InvalidValue {{ name: \"foo\", value: \"abc\" }}, file = src\\err.rs, line = {} }}", BASE_LINE + 74),
);
match err.reason::<Enum0>().unwrap() {
Enum0::InvalidValue { name, value } => {
assert_eq!(name, "foo");
assert_eq!(value, "abc");
}
}
assert!(err.source().is_none());
}
}
mod test_of_with_source {
use super::*;
#[derive(Debug)]
enum Enum0 {
InvalidValue { name: String, value: String },
}
#[test]
fn source_is_a_standard_error() {
let source = std::io::Error::new(std::io::ErrorKind::NotFound, "oh no!");
let err = Err::with_source(
Enum0::InvalidValue {
name: "foo".to_string(),
value: "abc".to_string(),
},
source,
);
#[cfg(unix)]
assert_eq!(err.file, "src/err.rs");
#[cfg(windows)]
assert_eq!(err.file, "src\\err.rs");
assert_eq!(err.line, BASE_LINE + 120);
assert_eq!(
format!("{err}"),
"InvalidValue { name: \"foo\", value: \"abc\" }",
);
#[cfg(unix)]
assert_eq!(
format!("{err:?}"),
format!("errs::Err {{ reason = errs::err::tests_of_err::test_of_with_source::Enum0 InvalidValue {{ name: \"foo\", value: \"abc\" }}, source = Custom {{ kind: NotFound, error: \"oh no!\" }}, file = src/err.rs, line = {} }}", BASE_LINE + 120),
);
#[cfg(windows)]
assert_eq!(
format!("{err:?}"),
format!("errs::Err {{ reason = errs::err::tests_of_err::test_of_with_source::Enum0 InvalidValue {{ name: \"foo\", value: \"abc\" }}, source = Custom {{ kind: NotFound, error: \"oh no!\" }}, file = src\\err.rs, line = {} }}", BASE_LINE + 120),
);
match err.reason::<Enum0>().unwrap() {
Enum0::InvalidValue { name, value } => {
assert_eq!(name, "foo");
assert_eq!(value, "abc");
}
}
assert!(err.source().is_some());
match err.source() {
Some(e) => match e.downcast_ref::<std::io::Error>() {
Some(io_err) => {
assert_eq!((*io_err).kind(), std::io::ErrorKind::NotFound);
}
_ => unreachable!(),
},
None => unreachable!(),
}
}
#[derive(Debug)]
struct MyError {
message: String,
}
impl MyError {
fn new(msg: &str) -> Self {
Self {
message: msg.to_string(),
}
}
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "MyError{{message:\"{}\"}}", self.message)
}
}
impl error::Error for MyError {}
#[test]
fn source_is_a_user_defined_error() {
let source = MyError::new("hello");
let err = Err::with_source(
Enum0::InvalidValue {
name: "foo".to_string(),
value: "abc".to_string(),
},
source,
);
#[cfg(unix)]
assert_eq!(err.file, "src/err.rs");
#[cfg(windows)]
assert_eq!(err.file, "src\\err.rs");
assert_eq!(err.line, BASE_LINE + 190);
assert_eq!(
format!("{err}"),
"InvalidValue { name: \"foo\", value: \"abc\" }",
);
#[cfg(unix)]
assert_eq!(
format!("{err:?}"),
format!("errs::Err {{ reason = errs::err::tests_of_err::test_of_with_source::Enum0 InvalidValue {{ name: \"foo\", value: \"abc\" }}, source = MyError {{ message: \"hello\" }}, file = src/err.rs, line = {} }}", BASE_LINE + 190),
);
#[cfg(windows)]
assert_eq!(
format!("{err:?}"),
format!("errs::Err {{ reason = errs::err::tests_of_err::test_of_with_source::Enum0 InvalidValue {{ name: \"foo\", value: \"abc\" }}, source = MyError {{ message: \"hello\" }}, file = src\\err.rs, line = {} }}", BASE_LINE + 190),
);
assert!(err.source().is_some());
match err.source() {
Some(e) => match e.downcast_ref::<MyError>() {
Some(my_err) => {
assert_eq!((*my_err).message, "hello".to_string());
}
_ => unreachable!(),
},
None => unreachable!(),
}
}
}
mod test_of_reason {
use super::*;
#[test]
fn reason_is_a_boolean() {
let err = Err::new(true);
assert_eq!(format!("{err}"), "true");
#[cfg(unix)]
assert_eq!(
format!("{err:?}"),
format!(
"errs::Err {{ reason = bool true, file = src/err.rs, line = {} }}",
BASE_LINE + 236,
),
);
#[cfg(windows)]
assert_eq!(
format!("{err:?}"),
format!(
"errs::Err {{ reason = bool true, file = src\\err.rs, line = {} }}",
BASE_LINE + 236,
),
);
match err.reason() {
Ok(true) => {}
Ok(false) => unreachable!(),
Err(_) => unreachable!(),
}
assert!(err.source().is_none());
}
#[test]
fn reason_is_a_number() {
let err = Err::new(123i64);
assert_eq!(format!("{err}"), "123");
#[cfg(unix)]
assert_eq!(
format!("{err:?}"),
format!(
"errs::Err {{ reason = i64 123, file = src/err.rs, line = {} }}",
BASE_LINE + 266,
),
);
#[cfg(windows)]
assert_eq!(
format!("{err:?}"),
format!(
"errs::Err {{ reason = i64 123, file = src\\err.rs, line = {} }}",
BASE_LINE + 266,
),
);
match err.reason::<i64>() {
Ok(n) => assert_eq!(*n, 123i64),
Err(_) => unreachable!(),
}
assert!(err.source().is_none());
}
#[test]
fn reason_is_a_string() {
let err = Err::new("abc".to_string());
assert_eq!(format!("{err}"), "\"abc\"");
#[cfg(unix)]
assert_eq!(
format!("{err:?}"),
format!(
"errs::Err {{ reason = alloc::string::String \"abc\", file = src/err.rs, line = {} }}",
BASE_LINE + 295,
),
);
#[cfg(windows)]
assert_eq!(
format!("{err:?}"),
format!(
"errs::Err {{ reason = alloc::string::String \"abc\", file = src\\err.rs, line = {} }}",
BASE_LINE + 295,
),
);
match err.reason::<String>() {
Ok(s) => assert_eq!(s, "abc"),
Err(_) => unreachable!(),
}
assert!(err.source().is_none());
}
#[derive(Debug)]
struct StructA {
name: String,
value: i64,
}
#[test]
fn reason_is_a_struct() {
let err = Err::new(StructA {
name: "abc".to_string(),
value: 123,
});
assert_eq!(format!("{err}"), "StructA { name: \"abc\", value: 123 }");
#[cfg(unix)]
assert_eq!(
format!("{err:?}"),
format!("errs::Err {{ reason = errs::err::tests_of_err::test_of_reason::StructA StructA {{ name: \"abc\", value: 123 }}, file = src/err.rs, line = {} }}", BASE_LINE + 330),
);
#[cfg(windows)]
assert_eq!(
format!("{err:?}"),
format!("errs::Err {{ reason = errs::err::tests_of_err::test_of_reason::StructA StructA {{ name: \"abc\", value: 123 }}, file = src\\err.rs, line = {} }}", BASE_LINE + 330),
);
match err.reason::<StructA>() {
Ok(st) => {
assert_eq!(st.name, "abc".to_string());
assert_eq!(st.value, 123);
}
Err(_) => unreachable!(),
}
assert!(err.source().is_none());
}
#[test]
fn reaason_is_an_unit() {
let err = Err::new(());
assert_eq!(format!("{err}"), "()");
#[cfg(unix)]
assert_eq!(
format!("{err:?}"),
format!(
"errs::Err {{ reason = () (), file = src/err.rs, line = {} }}",
BASE_LINE + 359,
),
);
#[cfg(windows)]
assert_eq!(
format!("{err:?}"),
format!(
"errs::Err {{ reason = () (), file = src\\err.rs, line = {} }}",
BASE_LINE + 359,
),
);
match err.reason::<()>() {
Ok(()) => {}
Err(_) => unreachable!(),
}
assert!(err.source().is_none());
}
}
mod test_of_match_reason {
use super::*;
#[allow(dead_code)]
#[derive(Debug)]
enum Enum0 {
InvalidValue { name: String, value: String },
FailToGetValue { name: String },
}
#[test]
fn reason_is_enum() {
let err = Err::new(Enum0::InvalidValue {
name: "foo".to_string(),
value: "abc".to_string(),
});
match err.reason::<String>() {
Ok(_) => panic!(),
Err(err) => match err.reason::<Enum0>() {
Ok(r) => match r {
Enum0::InvalidValue { name, value } => {
assert_eq!(name, "foo");
assert_eq!(value, "abc");
}
_ => panic!(),
},
Err(_) => panic!(),
},
}
err.match_reason::<String>(|_s| {
panic!();
})
.match_reason::<Enum0>(|r| match r {
Enum0::InvalidValue { name, value } => {
assert_eq!(name, "foo");
assert_eq!(value, "abc");
}
_ => panic!(),
});
}
}
}