use alloc::boxed::Box;
#[cfg(feature = "context")]
use alloc::vec::Vec;
use core::any::Any;
#[cfg(not(feature = "std"))]
use core::any::TypeId;
use core::fmt;
use core::marker::PhantomData;
use core::mem;
use core::ops::Deref;
use core::ptr;
#[cfg(feature = "std")]
use std::any::TypeId;
#[cfg(feature = "context")]
use crate::context::ContextFrame;
use crate::context::ContextValue;
use crate::formatting::Report;
use crate::type_set::{
Contains, NarrowTarget, RecoveryHandler, RecoveryTarget, SupersetOf, TupleForm, TypeSet,
};
use crate::AnyError;
pub trait SendSyncError: core::any::Any + core::error::Error + Send + Sync + 'static {
fn as_any(&self) -> &dyn Any;
}
impl<T> SendSyncError for T
where
T: core::error::Error + core::any::Any + Send + Sync + 'static,
{
fn as_any(&self) -> &dyn Any {
self
}
}
impl core::error::Error for Box<dyn SendSyncError> {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&**self)
}
}
#[cfg_attr(
not(any(feature = "backtrace", feature = "context", feature = "location")),
repr(transparent)
)]
pub(crate) struct ErrorUnionInner<T: ?Sized> {
#[cfg(feature = "backtrace")]
pub(crate) backtrace: std::backtrace::Backtrace,
#[cfg(feature = "context")]
pub(crate) context: Vec<ContextFrame>,
#[cfg(feature = "location")]
pub(crate) location: &'static core::panic::Location<'static>,
#[cfg(any(feature = "backtrace", feature = "context", feature = "location"))]
pub(crate) into_box_fn: fn(*mut dyn SendSyncError) -> Box<dyn SendSyncError>,
pub(crate) error: T,
}
#[cfg(any(feature = "backtrace", feature = "context", feature = "location"))]
fn make_box<T: SendSyncError>(ptr: *mut dyn SendSyncError) -> Box<dyn SendSyncError> {
let value: T = unsafe { ptr::read(ptr as *const dyn SendSyncError as *const T) };
Box::new(value)
}
impl ErrorUnionInner<dyn SendSyncError> {
#[cfg_attr(feature = "location", track_caller)]
pub(crate) fn new<T>(t: T) -> Box<ErrorUnionInner<dyn SendSyncError>>
where
T: SendSyncError,
{
Box::new(ErrorUnionInner {
#[cfg(feature = "backtrace")]
backtrace: std::backtrace::Backtrace::capture(),
#[cfg(feature = "context")]
context: Vec::new(),
#[cfg(feature = "location")]
location: core::panic::Location::caller(),
#[cfg(any(feature = "backtrace", feature = "context", feature = "location"))]
into_box_fn: make_box::<T>,
error: t,
})
}
pub(crate) fn new_from_parts<T>(
t: T,
#[cfg(feature = "backtrace")] backtrace: std::backtrace::Backtrace,
#[cfg(feature = "context")] context: Vec<ContextFrame>,
#[cfg(feature = "location")] location: &'static core::panic::Location<'static>,
) -> Box<ErrorUnionInner<dyn SendSyncError>>
where
T: SendSyncError,
{
Box::new(ErrorUnionInner {
#[cfg(feature = "backtrace")]
backtrace,
#[cfg(feature = "context")]
context,
#[cfg(feature = "location")]
location,
#[cfg(any(feature = "backtrace", feature = "context", feature = "location"))]
into_box_fn: make_box::<T>,
error: t,
})
}
#[allow(unstable_name_collisions)]
pub(crate) fn is_error<T: 'static>(&self) -> bool {
self.error.type_id() == TypeId::of::<T>()
}
pub(crate) unsafe fn downcast_error_unchecked<T: 'static>(self: Box<Self>) -> T {
debug_assert!(self.is_error::<T>());
let raw_container: *mut Self = Box::into_raw(self);
unsafe {
let thin_ptr = ptr::addr_of!((*raw_container).error) as *const T;
let downcasted_value: T = ptr::read(thin_ptr);
#[cfg(feature = "backtrace")]
ptr::drop_in_place(ptr::addr_of_mut!((*raw_container).backtrace));
#[cfg(feature = "context")]
ptr::drop_in_place(ptr::addr_of_mut!((*raw_container).context));
#[cfg(feature = "location")]
ptr::drop_in_place(ptr::addr_of_mut!((*raw_container).location));
let _dead_box: Box<mem::ManuallyDrop<Self>> =
Box::from_raw(raw_container as *mut mem::ManuallyDrop<Self>);
downcasted_value
}
}
pub(crate) unsafe fn downcast_error_unchecked_with_parts<T: 'static>(
self: Box<Self>,
) -> ErrorUnionInner<T> {
debug_assert!(self.is_error::<T>());
let raw_container: *mut Self = Box::into_raw(self);
unsafe {
let thin_ptr = ptr::addr_of!((*raw_container).error) as *const T;
let downcasted_value: T = ptr::read(thin_ptr);
#[cfg(feature = "backtrace")]
let backtrace = ptr::read(ptr::addr_of!((*raw_container).backtrace));
#[cfg(feature = "context")]
let context = ptr::read(ptr::addr_of!((*raw_container).context));
#[cfg(feature = "location")]
let location = ptr::read(ptr::addr_of!((*raw_container).location));
#[cfg(any(feature = "backtrace", feature = "context", feature = "location"))]
let into_box_fn = ptr::read(ptr::addr_of!((*raw_container).into_box_fn));
let _dead_box: Box<mem::ManuallyDrop<Self>> =
Box::from_raw(raw_container as *mut mem::ManuallyDrop<Self>);
ErrorUnionInner {
#[cfg(feature = "backtrace")]
backtrace,
#[cfg(feature = "context")]
context,
#[cfg(feature = "location")]
location,
#[cfg(any(feature = "backtrace", feature = "context", feature = "location"))]
into_box_fn,
error: downcasted_value,
}
}
}
pub(crate) fn downcast_error_ref<T: 'static>(&self) -> Option<&T> {
(&self.error as &dyn Any).downcast_ref::<T>()
}
pub(crate) fn downcast_error_mut<T: 'static>(&mut self) -> Option<&mut T> {
(&mut self.error as &mut dyn Any).downcast_mut::<T>()
}
}
pub struct ErrorUnion<E: TypeSet = AnyError> {
pub(crate) inner: Box<ErrorUnionInner<dyn SendSyncError>>,
pub(crate) _pd: PhantomData<fn(E) -> E>,
}
impl<T> Deref for ErrorUnion<(T,)>
where
T: SendSyncError,
{
type Target = T;
fn deref(&self) -> &T {
(&self.inner.error as &dyn Any).downcast_ref::<T>().unwrap()
}
}
impl<E: TypeSet> fmt::Debug for ErrorUnion<E> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
Report::new(self).debug(formatter)
}
}
impl<E: TypeSet> fmt::Display for ErrorUnion<E> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
Report::new(self).display(formatter)
}
}
#[cfg(feature = "std")]
fn _send_sync_error_assert() {
use std::io;
fn is_send<T: Send>(_: &T) {}
fn is_sync<T: Sync>(_: &T) {}
fn is_error<T: core::error::Error>(_: &T) {}
let error_union: ErrorUnion<(io::Error, fmt::Error)> =
ErrorUnion::new(io::Error::other("yooo"));
is_send(&error_union);
is_sync(&error_union);
}
unsafe impl<T> Send for ErrorUnion<T> where T: TypeSet + Send {}
unsafe impl<T> Sync for ErrorUnion<T> where T: TypeSet + Sync {}
impl ErrorUnion {
#[cfg_attr(feature = "location", track_caller)]
pub fn new<T, OutSet, Index>(t: T) -> ErrorUnion<OutSet>
where
T: SendSyncError,
OutSet: TypeSet,
OutSet::Variants: Contains<T, Index>,
{
ErrorUnion {
inner: ErrorUnionInner::new(t),
_pd: PhantomData,
}
}
#[allow(unused)] pub(crate) fn new_from_parts<T, OutSet, Index>(
t: T,
#[cfg(feature = "backtrace")] backtrace: std::backtrace::Backtrace,
#[cfg(feature = "context")] context: Vec<ContextFrame>,
#[cfg(feature = "location")] location: &'static core::panic::Location<'static>,
) -> ErrorUnion<OutSet>
where
T: SendSyncError,
OutSet: TypeSet,
OutSet::Variants: Contains<T, Index>,
{
ErrorUnion {
inner: ErrorUnionInner::new_from_parts(
t,
#[cfg(feature = "backtrace")]
backtrace,
#[cfg(feature = "context")]
context,
#[cfg(feature = "location")]
location,
),
_pd: PhantomData,
}
}
pub(crate) fn erase<E>(t: ErrorUnion<E>) -> ErrorUnion
where
E: TypeSet,
{
ErrorUnion {
inner: t.inner,
_pd: PhantomData,
}
}
}
pub struct StdError<E = AnyError>(ErrorUnion<E>)
where
E: TypeSet;
impl<E: TypeSet> StdError<E> {
pub fn as_union(&self) -> &ErrorUnion<E> {
&self.0
}
pub fn into_union(self) -> ErrorUnion<E> {
self.0
}
}
impl<E> core::error::Error for StdError<E>
where
E: TypeSet,
{
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
self.0.source()
}
}
impl<E> fmt::Display for StdError<E>
where
E: TypeSet,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}", self.0.inner())
}
}
impl<E> fmt::Debug for StdError<E>
where
E: TypeSet,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, formatter)
}
}
impl<E> ErrorUnion<E>
where
E: TypeSet,
{
pub fn into_std_error(self) -> StdError<E> {
StdError(self)
}
pub fn try_from_dyn_error(
error: Box<dyn SendSyncError>,
) -> Result<Self, Box<dyn SendSyncError>> {
let error_ref = &*error as &dyn Any;
if !error_ref.is::<StdError<E>>() {
return Err(error);
}
let error = error as Box<dyn Any>;
let adapter = error.downcast::<StdError<E>>().unwrap();
Ok(adapter.into_union())
}
}
impl<E> ErrorUnion<E>
where
E: TypeSet,
{
#[allow(clippy::type_complexity)]
pub fn narrow<Target, Index>(
self,
) -> Result<Target::Output, ErrorUnion<<Target::Remainder as TupleForm>::Tuple>>
where
Target: NarrowTarget<E, Index>,
{
Target::split(self)
}
pub fn widen<Other, Index>(self) -> ErrorUnion<Other>
where
Other: TypeSet,
Other::Variants: SupersetOf<E::Variants, Index>,
{
ErrorUnion {
inner: self.inner,
_pd: PhantomData,
}
}
pub fn downcast_inner<T: 'static>(self) -> Result<T, Self> {
if self.inner.is_error::<T>() {
Ok(unsafe { self.inner.downcast_error_unchecked::<T>() })
} else {
Err(self)
}
}
pub fn downcast_inner_ref<T: 'static>(&self) -> Option<&T> {
self.inner.downcast_error_ref()
}
pub fn downcast_inner_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.inner.downcast_error_mut()
}
pub fn is_inner<T: 'static>(&self) -> bool {
self.inner.is_error::<T>()
}
#[cfg(feature = "backtrace")]
pub fn backtrace(&self) -> &std::backtrace::Backtrace {
&self.inner.backtrace
}
#[cfg(feature = "location")]
pub fn location(&self) -> &'static core::panic::Location<'static> {
self.inner.location
}
#[cfg(feature = "context")]
pub fn contexts(
&self,
) -> impl DoubleEndedIterator<Item = &crate::ContextFrame> + ExactSizeIterator {
self.inner.context.iter()
}
pub fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
self.inner.error.source()
}
pub fn inner(&self) -> &dyn SendSyncError {
&self.inner.error
}
pub fn inner_mut(&mut self) -> &mut dyn SendSyncError {
&mut self.inner.error
}
pub fn into_inner(self) -> Box<dyn SendSyncError> {
let raw = Box::into_raw(self.inner);
#[cfg(not(any(feature = "backtrace", feature = "context", feature = "location")))]
unsafe {
Box::from_raw(ptr::addr_of_mut!((*raw).error))
}
#[cfg(any(feature = "backtrace", feature = "context", feature = "location"))]
unsafe {
let into_box_fn = (*raw).into_box_fn;
let error_ptr = ptr::addr_of_mut!((*raw).error);
let boxed = (into_box_fn)(error_ptr);
#[cfg(feature = "backtrace")]
ptr::drop_in_place(ptr::addr_of_mut!((*raw).backtrace));
#[cfg(feature = "context")]
ptr::drop_in_place(ptr::addr_of_mut!((*raw).context));
#[cfg(feature = "location")]
ptr::drop_in_place(ptr::addr_of_mut!((*raw).location));
let _dead: Box<mem::ManuallyDrop<ErrorUnionInner<dyn SendSyncError>>> =
Box::from_raw(raw as *mut _);
boxed
}
}
pub fn latest_context_error(&self) -> Option<&dyn SendSyncError> {
#[cfg(feature = "context")]
for context in self.inner.context.iter().rev() {
if let Some(err) = context.context.as_error() {
return Some(err);
}
}
None
}
pub fn into_enum(self) -> E::Enum
where
E::Enum: From<Self>,
{
E::Enum::from(self)
}
pub fn as_enum<'a>(&'a self) -> E::RefEnum<'a>
where
E::RefEnum<'a>: From<&'a Self>,
{
E::RefEnum::from(self)
}
pub fn as_mut_enum<'a>(&'a mut self) -> E::MutEnum<'a>
where
E::MutEnum<'a>: From<&'a mut Self>,
{
E::MutEnum::from(self)
}
#[allow(unused_mut)]
#[allow(unused_variables)]
#[cfg_attr(feature = "location", track_caller)]
pub fn context<C: Into<ContextValue>>(mut self, context: C) -> Self {
#[cfg(feature = "context")]
self.inner
.context
.push(crate::context::ContextFrame::new(context.into()));
self
}
#[cfg(feature = "user_context")]
#[allow(unused_mut)]
#[allow(unused_variables)]
#[cfg_attr(feature = "location", track_caller)]
pub fn user_context<C: Into<ContextValue>>(mut self, context: C) -> Self {
#[cfg(feature = "context")]
self.inner
.context
.push(crate::context::ContextFrame::new_user_facing(context.into()));
self
}
#[allow(unused_mut)]
#[allow(unused_variables)]
#[cfg_attr(feature = "location", track_caller)]
pub fn with_context<F, C: Into<ContextValue>>(mut self, f: F) -> Self
where
F: FnOnce() -> C,
{
#[cfg(feature = "context")]
self.inner
.context
.push(crate::context::ContextFrame::new(f().into()));
self
}
#[cfg(feature = "user_context")]
#[allow(unused_mut)]
#[allow(unused_variables)]
#[cfg_attr(feature = "location", track_caller)]
pub fn with_user_context<F, C: Into<ContextValue>>(mut self, f: F) -> Self
where
F: FnOnce() -> C,
{
#[cfg(feature = "context")]
self.inner
.context
.push(crate::context::ContextFrame::new_user_facing(f().into()));
self
}
}
impl<A: SendSyncError> AsRef<A> for ErrorUnion<(A,)> {
fn as_ref(&self) -> &A {
self.inner.downcast_error_ref().unwrap()
}
}
impl<A: SendSyncError> AsMut<A> for ErrorUnion<(A,)> {
fn as_mut(&mut self) -> &mut A {
self.inner.downcast_error_mut().unwrap()
}
}
impl<A: SendSyncError> ErrorUnion<(A,)> {
pub fn into_single(self) -> A {
unsafe { self.inner.downcast_error_unchecked() }
}
pub fn map_single<U, F>(self, f: F) -> ErrorUnion<(U,)>
where
U: SendSyncError,
F: FnOnce(A) -> U,
{
let inner = unsafe { self.inner.downcast_error_unchecked_with_parts::<A>() };
ErrorUnion {
inner: ErrorUnionInner::new_from_parts(
f(inner.error),
#[cfg(feature = "backtrace")]
inner.backtrace,
#[cfg(feature = "context")]
inner.context,
#[cfg(feature = "location")]
inner.location,
),
_pd: PhantomData,
}
}
}
pub trait ReshapeUnion<S, E>
where
E: TypeSet,
{
fn widen<Other, Index>(self) -> Result<S, ErrorUnion<Other>>
where
Other: TypeSet,
Other::Variants: SupersetOf<E::Variants, Index>;
#[allow(clippy::type_complexity)]
fn narrow<Target, Index>(
self,
) -> Result<
Target::Output,
Result<S, ErrorUnion<<Target::Remainder as TupleForm>::Tuple>>,
>
where
Target: NarrowTarget<E, Index>;
#[allow(clippy::type_complexity)]
fn recover<Target, Index>(
self,
f: impl RecoveryHandler<Target, E, Index, S>,
) -> Result<S, ErrorUnion<<Target::Remainder as TupleForm>::Tuple>>
where
Target: RecoveryTarget<E, Index>;
fn try_recover<Target, Other, Index, OtherIndex>(
self,
f: impl RecoveryHandler<Target, E, Index, Result<S, ErrorUnion<Other>>>,
) -> Result<S, ErrorUnion<Other>>
where
Target: RecoveryTarget<E, Index>,
Other: TypeSet,
Other::Variants: SupersetOf<Target::Remainder, OtherIndex>;
fn into_value(self) -> S
where
E: TypeSet<Enum = core::convert::Infallible>;
}
impl<S, E> ReshapeUnion<S, E> for Result<S, ErrorUnion<E>>
where
E: TypeSet,
{
fn widen<Other, Index>(self) -> Result<S, ErrorUnion<Other>>
where
Other: TypeSet,
Other::Variants: SupersetOf<E::Variants, Index>,
{
self.map_err(|e| e.widen())
}
fn narrow<Target, Index>(
self,
) -> Result<
Target::Output,
Result<S, ErrorUnion<<Target::Remainder as TupleForm>::Tuple>>,
>
where
Target: NarrowTarget<E, Index>,
{
match self {
Ok(value) => Err(Ok(value)),
Err(err) => match err.narrow::<Target, Index>() {
Ok(value) => Ok(value),
Err(err) => Err(Err(err)),
},
}
}
fn recover<Target, Index>(
self,
f: impl RecoveryHandler<Target, E, Index, S>,
) -> Result<S, ErrorUnion<<Target::Remainder as TupleForm>::Tuple>>
where
Target: RecoveryTarget<E, Index>,
{
match self {
Ok(value) => Ok(value),
Err(error) => match Target::split(error) {
Ok(selected) => Ok(f(selected)),
Err(remainder) => Err(remainder),
},
}
}
fn try_recover<Target, Other, Index, OtherIndex>(
self,
f: impl RecoveryHandler<Target, E, Index, Result<S, ErrorUnion<Other>>>,
) -> Result<S, ErrorUnion<Other>>
where
Target: RecoveryTarget<E, Index>,
Other: TypeSet,
Other::Variants: SupersetOf<Target::Remainder, OtherIndex>,
{
match self {
Ok(value) => Ok(value),
Err(error) => match Target::split(error) {
Ok(selected) => f(selected),
Err(remainder) => Err(ErrorUnion {
inner: remainder.inner,
_pd: PhantomData,
}),
},
}
}
fn into_value(self) -> S
where
E: TypeSet<Enum = core::convert::Infallible>,
{
match self {
Ok(value) => value,
Err(_) => unreachable!("an empty error set cannot contain an error"),
}
}
}
pub trait IntoUnion<S, F> {
fn union<Other, Index>(self) -> Result<S, ErrorUnion<Other>>
where
Other: TypeSet,
Other::Variants: Contains<F, Index>;
}
impl<S, F: SendSyncError> IntoUnion<S, F> for Result<S, F> {
#[cfg_attr(feature = "location", track_caller)]
fn union<Other, Index>(self) -> Result<S, ErrorUnion<Other>>
where
Other: TypeSet,
Other::Variants: Contains<F, Index>,
{
match self {
Ok(value) => Ok(value),
Err(err) => Err(ErrorUnion::new(err)),
}
}
}
pub trait IntoAnyUnion<S> {
fn any_union(self) -> Result<S, ErrorUnion>;
}
impl<S, F: SendSyncError> IntoAnyUnion<S> for Result<S, F> {
#[cfg_attr(feature = "location", track_caller)]
fn any_union(self) -> Result<S, ErrorUnion> {
match self {
Ok(value) => Ok(value),
Err(err) => Err(ErrorUnion::new(err)),
}
}
}
impl<S, E: TypeSet> IntoAnyUnion<S> for Result<S, ErrorUnion<E>> {
fn any_union(self) -> Result<S, ErrorUnion> {
self.map_err(|e| ErrorUnion::erase(e))
}
}
#[cfg(feature = "anyhow")]
#[derive(Debug)]
pub(crate) struct AnyhowError(pub(crate) anyhow::Error);
#[cfg(feature = "anyhow")]
impl fmt::Display for AnyhowError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}", self.0)
}
}
#[cfg(feature = "anyhow")]
impl core::error::Error for AnyhowError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
self.0.source()
}
}
#[cfg(feature = "anyhow")]
#[derive(Debug)]
pub(crate) struct AnyhowErrorArc(pub(crate) alloc::sync::Arc<anyhow::Error>);
#[cfg(feature = "anyhow")]
impl fmt::Display for AnyhowErrorArc {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}", self.0)
}
}
#[cfg(feature = "anyhow")]
impl core::error::Error for AnyhowErrorArc {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
self.0.source()
}
}
#[cfg(feature = "anyhow")]
impl ErrorUnion {
#[cfg_attr(feature = "location", track_caller)]
pub fn from_anyhow(error: anyhow::Error) -> ErrorUnion {
ErrorUnion::new_from_parts(
AnyhowError(error),
#[cfg(feature = "backtrace")]
std::backtrace::Backtrace::disabled(),
#[cfg(feature = "context")]
Vec::new(),
#[cfg(feature = "location")]
core::panic::Location::caller(),
)
}
#[cfg_attr(feature = "location", track_caller)]
pub fn from_anyhow_arc(error: alloc::sync::Arc<anyhow::Error>) -> ErrorUnion {
ErrorUnion::new_from_parts(
AnyhowErrorArc(error),
#[cfg(feature = "backtrace")]
std::backtrace::Backtrace::disabled(),
#[cfg(feature = "context")]
Vec::new(),
#[cfg(feature = "location")]
core::panic::Location::caller(),
)
}
}
#[cfg(feature = "anyhow")]
impl From<ErrorUnion> for anyhow::Error {
#[allow(unused_mut)]
fn from(mut value: ErrorUnion) -> Self {
#[cfg(feature = "context")]
let context = core::mem::take(&mut value.inner.context);
let inner_error = value.into_inner();
let mut error = anyhow::Error::new(inner_error);
#[cfg(feature = "context")]
for c in context {
error = error.context(c.context);
}
error
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt;
#[cfg(not(feature = "std"))]
use std::{prelude::v1::*, vec};
#[derive(Debug, PartialEq)]
struct FooError(String);
impl fmt::Display for FooError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FooError({})", self.0)
}
}
impl std::error::Error for FooError {}
#[derive(Debug, PartialEq)]
struct BarError(u32);
impl fmt::Display for BarError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BarError({})", self.0)
}
}
impl std::error::Error for BarError {}
#[cfg(not(any(feature = "backtrace", feature = "context", feature = "location")))]
#[test]
fn inner_without_metadata_has_the_error_layout() {
fn check<T: SendSyncError>(error: T) {
assert_eq!(
core::alloc::Layout::new::<ErrorUnionInner<T>>(),
core::alloc::Layout::new::<T>()
);
let inner = ErrorUnionInner::new(error);
assert_eq!(
core::alloc::Layout::for_value(&*inner),
core::alloc::Layout::for_value(&inner.error)
);
}
check(FooError("owned".into()));
check(BarError(42));
check(fmt::Error);
}
#[test]
fn downcast_error_unchecked_correct_type_recovers_value() {
let inner = ErrorUnionInner::new(FooError("hello".into()));
assert!(inner.is_error::<FooError>());
let recovered: FooError = unsafe { inner.downcast_error_unchecked() };
assert_eq!(recovered, FooError("hello".into()));
}
#[test]
fn downcast_error_unchecked_does_not_leak_or_double_drop() {
let payload = vec![1u8, 2, 3, 4, 5];
#[derive(Debug, PartialEq)]
struct VecError(Vec<u8>);
impl fmt::Display for VecError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl std::error::Error for VecError {}
let inner = ErrorUnionInner::new(VecError(payload.clone()));
let recovered: VecError = unsafe { inner.downcast_error_unchecked() };
assert_eq!(recovered.0, payload);
}
#[test]
fn downcast_error_unchecked_with_parts_preserves_context() {
let inner = ErrorUnionInner::new(FooError("ctx".into()));
let mut union: ErrorUnion<(FooError,)> = ErrorUnion {
inner,
_pd: PhantomData,
};
#[cfg(feature = "context")]
{
union
.inner
.context
.push(ContextFrame::new("step one".into()));
union
.inner
.context
.push(ContextFrame::new("step two".into()));
}
let parts: ErrorUnionInner<FooError> =
unsafe { union.inner.downcast_error_unchecked_with_parts() };
assert_eq!(parts.error, FooError("ctx".into()));
#[cfg(feature = "context")]
assert_eq!(parts.context.len(), 2);
}
#[test]
fn downcast_error_unchecked_with_parts_correct_error_value() {
let inner = ErrorUnionInner::new(BarError(42));
let parts: ErrorUnionInner<BarError> =
unsafe { inner.downcast_error_unchecked_with_parts() };
assert_eq!(parts.error, BarError(42));
}
#[test]
fn boxed_std_error_and_back_roundtrips() {
let union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("roundtrip".into()));
let dyn_err: Box<dyn SendSyncError> = Box::new(union.into_std_error());
assert!((&*dyn_err as &dyn Any).is::<StdError<(FooError,)>>());
let recovered: ErrorUnion<(FooError,)> =
ErrorUnion::try_from_dyn_error(dyn_err).expect("round-trip should succeed");
assert_eq!(recovered.as_ref(), &FooError("roundtrip".into()));
}
#[test]
fn try_from_dyn_error_wrong_type_returns_err() {
let union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("mismatch".into()));
let dyn_err: Box<dyn SendSyncError> = Box::new(union.into_std_error());
let result: Result<ErrorUnion<(BarError,)>, _> = ErrorUnion::try_from_dyn_error(dyn_err);
assert!(result.is_err(), "mismatched type should be returned as Err");
}
#[test]
fn std_error_display_delegates_to_inner() {
let union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("display".into()));
let adapter = union.into_std_error();
assert!(adapter.to_string().contains("FooError(display)"));
}
#[test]
fn std_error_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>(_: T) {}
let union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("traits".into()));
assert_send_sync(union.into_std_error());
}
#[test]
fn try_from_dyn_error_preserves_context() {
let mut union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("ctx".into()));
#[cfg(feature = "context")]
{
union = union.context("some context");
}
let dyn_err = Box::new(union.into_std_error());
let recovered: ErrorUnion<(FooError,)> = ErrorUnion::try_from_dyn_error(dyn_err).unwrap();
#[cfg(feature = "context")]
assert_eq!(recovered.inner.context.len(), 1);
assert_eq!(recovered.as_ref(), &FooError("ctx".into()));
}
#[test]
fn multi_variant_union_boxed_std_error_roundtrips() {
let union: ErrorUnion<(FooError, BarError)> = ErrorUnion::new(BarError(99));
let dyn_err = Box::new(union.into_std_error());
let recovered: ErrorUnion<(FooError, BarError)> =
ErrorUnion::try_from_dyn_error(dyn_err).unwrap();
let bar: BarError = recovered.narrow::<BarError, _>().unwrap();
assert_eq!(bar, BarError(99));
}
#[test]
fn into_inner_dyn_error_returns_concrete_type_not_wrapper() {
let union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("concrete".into()));
let dyn_err = union.into_inner();
assert!(
(&*dyn_err as &dyn Any).is::<FooError>(),
"expected FooError, got a wrapper or wrong type"
);
}
#[test]
fn into_inner_dyn_error_value_is_preserved() {
let union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("preserved".into()));
let dyn_err = union.into_inner();
let foo = (&*dyn_err as &dyn Any).downcast_ref::<FooError>().unwrap();
assert_eq!(foo, &FooError("preserved".into()));
}
#[test]
fn into_inner_dyn_error_display_is_concrete_type() {
let union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("display".into()));
let dyn_err = union.into_inner();
assert_eq!(dyn_err.to_string(), "FooError(display)");
}
#[test]
fn into_inner_dyn_error_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>(_: T) {}
let union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("traits".into()));
assert_send_sync(union.into_inner());
}
#[test]
fn into_inner_differs_from_boxed_std_error() {
let union_a: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("a".into()));
let union_b: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("b".into()));
let inner_dyn = union_a.into_inner();
let wrapper_dyn = Box::new(union_b.into_std_error());
assert!((&*inner_dyn as &dyn Any).is::<FooError>());
assert!(!(&*wrapper_dyn as &dyn Any).is::<FooError>());
assert!((&*wrapper_dyn as &dyn Any).is::<StdError<(FooError,)>>());
}
#[test]
fn into_inner_dyn_error_multi_variant_foo() {
let union: ErrorUnion<(FooError, BarError)> = ErrorUnion::new(FooError("multi".into()));
let dyn_err = union.into_inner();
assert!((&*dyn_err as &dyn Any).is::<FooError>());
assert!(!(&*dyn_err as &dyn Any).is::<BarError>());
let foo = (&*dyn_err as &dyn Any).downcast_ref::<FooError>().unwrap();
assert_eq!(foo, &FooError("multi".into()));
}
#[test]
fn into_inner_dyn_error_multi_variant_bar() {
let union: ErrorUnion<(FooError, BarError)> = ErrorUnion::new(BarError(77));
let dyn_err = union.into_inner();
assert!((&*dyn_err as &dyn Any).is::<BarError>());
assert!(!(&*dyn_err as &dyn Any).is::<FooError>());
let bar = (&*dyn_err as &dyn Any).downcast_ref::<BarError>().unwrap();
assert_eq!(bar, &BarError(77));
}
#[test]
fn into_inner_dyn_error_does_not_leak_heap_allocation() {
#[derive(Debug, PartialEq)]
struct VecError(Vec<u8>);
impl fmt::Display for VecError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl std::error::Error for VecError {}
let payload = vec![1u8, 2, 3, 4, 5];
let union: ErrorUnion<(VecError,)> = ErrorUnion::new(VecError(payload.clone()));
let dyn_err = union.into_inner();
let recovered = (&*dyn_err as &dyn Any).downcast_ref::<VecError>().unwrap();
assert_eq!(recovered.0, payload);
}
#[test]
fn into_inner_dyn_error_not_roundtrippable_via_try_from_dyn_error() {
let union_a: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("bare".into()));
let bare_dyn = union_a.into_inner();
let result: Result<ErrorUnion<(FooError,)>, _> = ErrorUnion::try_from_dyn_error(bare_dyn);
assert!(
result.is_err(),
"try_from_dyn_error should reject a bare inner error, not a StdError"
);
}
}
#[cfg(test)]
mod latest_context_error_tests {
use super::*;
use std::fmt;
#[cfg(not(feature = "std"))]
use std::prelude::v1::*;
#[derive(Debug, PartialEq)]
struct PrimaryError(String);
impl fmt::Display for PrimaryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PrimaryError({})", self.0)
}
}
impl std::error::Error for PrimaryError {}
#[derive(Debug, PartialEq)]
struct ContextError(String);
impl fmt::Display for ContextError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ContextError({})", self.0)
}
}
impl std::error::Error for ContextError {}
fn box_err(e: impl SendSyncError) -> Box<dyn SendSyncError> {
Box::new(e)
}
#[test]
fn latest_context_error_with_no_context_returns_none() {
let union: ErrorUnion<(PrimaryError,)> = ErrorUnion::new(PrimaryError("base".into()));
assert!(union.latest_context_error().is_none());
}
#[test]
fn latest_context_error_fallback_returns_inner() {
let union: ErrorUnion<(PrimaryError,)> = ErrorUnion::new(PrimaryError("base".into()));
let error = union
.latest_context_error()
.unwrap_or_else(|| union.inner());
assert_eq!(error.to_string(), "PrimaryError(base)");
assert!(error.as_any().is::<PrimaryError>());
}
#[cfg(feature = "context")]
#[test]
fn latest_context_error_with_string_context_only_returns_none() {
let union: ErrorUnion<(PrimaryError,)> = ErrorUnion::new(PrimaryError("base".into()));
let union = union.context("just a string message");
assert!(union.latest_context_error().is_none());
}
#[cfg(feature = "context")]
#[test]
fn latest_context_error_with_error_context_returns_context_error() {
let union: ErrorUnion<(PrimaryError,)> = ErrorUnion::new(PrimaryError("base".into()));
let union = union.context(box_err(ContextError("ctx-1".into())));
assert_eq!(
union.latest_context_error().unwrap().to_string(),
"ContextError(ctx-1)"
);
}
#[cfg(feature = "context")]
#[test]
fn latest_context_error_returns_most_recently_added_error_context() {
let union: ErrorUnion<(PrimaryError,)> = ErrorUnion::new(PrimaryError("base".into()));
let union = union
.context(box_err(ContextError("ctx-1".into())))
.context(box_err(ContextError("ctx-2".into())))
.context(box_err(ContextError("ctx-3".into())));
assert_eq!(
union.latest_context_error().unwrap().to_string(),
"ContextError(ctx-3)"
);
}
#[cfg(feature = "context")]
#[test]
fn latest_context_error_skips_trailing_string_contexts_to_find_error_context() {
let union: ErrorUnion<(PrimaryError,)> = ErrorUnion::new(PrimaryError("base".into()));
let union = union
.context(box_err(ContextError("ctx-1".into())))
.context("a string note added after");
assert_eq!(
union.latest_context_error().unwrap().to_string(),
"ContextError(ctx-1)"
);
}
#[cfg(feature = "context")]
#[test]
fn latest_context_error_with_only_string_contexts_can_fall_back_to_inner() {
let union: ErrorUnion<(PrimaryError,)> = ErrorUnion::new(PrimaryError("base".into()));
let union = union.context("note one").context("note two");
assert!(union.latest_context_error().is_none());
let error = union
.latest_context_error()
.unwrap_or_else(|| union.inner());
assert_eq!(error.to_string(), "PrimaryError(base)");
}
#[cfg(feature = "context")]
#[test]
fn latest_context_error_error_context_correct_concrete_type() {
let union: ErrorUnion<(PrimaryError,)> = ErrorUnion::new(PrimaryError("base".into()));
let union = union.context(box_err(ContextError("typed".into())));
assert!(
union
.latest_context_error()
.unwrap()
.as_any()
.is::<ContextError>()
);
}
#[cfg(feature = "context")]
#[test]
fn latest_context_error_multi_variant_union_with_error_context() {
let union: ErrorUnion<(PrimaryError, ContextError)> =
ErrorUnion::new(PrimaryError("base".into()));
let union = union.context(box_err(ContextError("ctx-1".into())));
assert_eq!(
union.latest_context_error().unwrap().to_string(),
"ContextError(ctx-1)"
);
}
}
#[cfg(test)]
mod downcast_inner_tests {
use super::*;
use std::fmt;
#[cfg(not(feature = "std"))]
use std::{prelude::v1::*, vec};
#[derive(Debug, PartialEq)]
struct FooError(String);
impl fmt::Display for FooError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FooError({})", self.0)
}
}
impl std::error::Error for FooError {}
#[derive(Debug, PartialEq)]
struct BarError(u32);
impl fmt::Display for BarError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BarError({})", self.0)
}
}
impl std::error::Error for BarError {}
#[test]
fn downcast_inner_correct_type_returns_ok() {
let union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("hello".into()));
let result = union.downcast_inner::<FooError>();
assert_eq!(result.unwrap(), FooError("hello".into()));
}
#[test]
fn downcast_inner_wrong_type_returns_original_union() {
let union: ErrorUnion<(FooError, BarError)> = ErrorUnion::new(FooError("hello".into()));
let result = union.downcast_inner::<BarError>();
let union: ErrorUnion<(FooError, BarError)> = result.unwrap_err();
assert_eq!(
union.downcast_inner::<FooError>().unwrap(),
FooError("hello".into())
);
}
#[test]
fn downcast_inner_multi_variant_correct_type() {
let union: ErrorUnion<(FooError, BarError)> = ErrorUnion::new(BarError(7));
let result = union.downcast_inner::<BarError>();
assert_eq!(result.unwrap(), BarError(7));
}
#[test]
fn downcast_inner_does_not_leak_or_double_drop() {
#[derive(Debug, PartialEq)]
struct VecError(Vec<u8>);
impl fmt::Display for VecError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl std::error::Error for VecError {}
let payload = vec![1u8, 2, 3, 4, 5];
let union: ErrorUnion<(VecError,)> = ErrorUnion::new(VecError(payload.clone()));
let result = union.downcast_inner::<VecError>();
assert_eq!(result.unwrap(), VecError(payload));
}
#[test]
fn downcast_inner_wrong_type_retains_value_without_leaking() {
#[derive(Debug, PartialEq)]
struct VecError(Vec<u8>);
impl fmt::Display for VecError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl std::error::Error for VecError {}
let union: ErrorUnion<(VecError, FooError)> = ErrorUnion::new(VecError(vec![9, 9, 9]));
let result = union.downcast_inner::<FooError>();
let union = result.unwrap_err();
assert_eq!(
union.downcast_inner::<VecError>().unwrap(),
VecError(vec![9, 9, 9])
);
}
#[test]
fn downcast_inner_ref_correct_type_returns_some() {
let union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("ref".into()));
let result = union.downcast_inner_ref::<FooError>();
assert_eq!(result, Some(&FooError("ref".into())));
}
#[test]
fn downcast_inner_ref_wrong_type_returns_none() {
let union: ErrorUnion<(FooError, BarError)> = ErrorUnion::new(FooError("ref".into()));
let result = union.downcast_inner_ref::<BarError>();
assert_eq!(result, None);
}
#[test]
fn downcast_inner_ref_does_not_consume_union() {
let union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("still here".into()));
let first = union.downcast_inner_ref::<FooError>();
assert_eq!(first, Some(&FooError("still here".into())));
let second = union.downcast_inner_ref::<FooError>();
assert_eq!(second, Some(&FooError("still here".into())));
}
#[test]
fn downcast_inner_ref_multi_variant_bar() {
let union: ErrorUnion<(FooError, BarError)> = ErrorUnion::new(BarError(123));
assert_eq!(union.downcast_inner_ref::<BarError>(), Some(&BarError(123)));
assert_eq!(union.downcast_inner_ref::<FooError>(), None);
}
#[test]
fn downcast_inner_mut_correct_type_returns_some() {
let mut union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("mut".into()));
let result = union.downcast_inner_mut::<FooError>();
assert_eq!(result, Some(&mut FooError("mut".into())));
}
#[test]
fn downcast_inner_mut_wrong_type_returns_none() {
let mut union: ErrorUnion<(FooError, BarError)> = ErrorUnion::new(FooError("mut".into()));
let result = union.downcast_inner_mut::<BarError>();
assert_eq!(result, None);
}
#[test]
fn downcast_inner_mut_allows_mutation() {
let mut union: ErrorUnion<(FooError,)> = ErrorUnion::new(FooError("before".into()));
{
let inner = union.downcast_inner_mut::<FooError>().unwrap();
inner.0 = "after".into();
}
assert_eq!(
union.downcast_inner_ref::<FooError>(),
Some(&FooError("after".into()))
);
}
#[test]
fn downcast_inner_mut_multi_variant_bar() {
let mut union: ErrorUnion<(FooError, BarError)> = ErrorUnion::new(BarError(1));
{
let bar = union.downcast_inner_mut::<BarError>().unwrap();
bar.0 = 99;
}
assert_eq!(union.downcast_inner_ref::<BarError>(), Some(&BarError(99)));
assert_eq!(union.downcast_inner_mut::<FooError>(), None);
}
}