uni_error/
error.rs

1use alloc::{borrow::Cow, boxed::Box, sync::Arc};
2use core::{
3    any::{Any, TypeId, type_name},
4    error::Error,
5    fmt::{Debug, Display},
6    ops::Deref,
7};
8
9use crate::cause::{Cause, CauseInner, Chain, UniDisplay};
10
11// *** Type aliases ***
12
13/// A result type that specifies a customkind.
14pub type UniResult<T, U> = Result<T, UniError<U>>;
15
16/// A result type that specifies no kind.
17pub type SimpleResult<T> = Result<T, SimpleError>;
18
19/// A result type that specifies a trait object kind.
20pub type DynResult<T> = Result<T, DynError>;
21
22/// An error type that is used when there is no kind.
23pub type SimpleError = UniError<()>;
24
25// *** DynError ***
26
27/// An error type with an erased kind type. It can be used when you
28/// don't know `T` or wish to propagate multiple `T` types. `UniError<T>`
29/// can be recovered via downcasting if `T` is known.
30#[derive(Debug)]
31pub struct DynError(Box<dyn UniErrorOps + Send + Sync>);
32
33impl DynError {
34    pub(crate) fn new<E: UniErrorOps>(err: E) -> Self {
35        Self(Box::new(err))
36    }
37
38    /// Attempts to downcast a `DynError` to a `UniError<T>`.
39    pub fn downcast<T: UniKind>(self) -> Option<UniError<T>> {
40        let err: Box<dyn Any> = self.0;
41        err.downcast().ok().map(|err| *err)
42    }
43}
44
45impl Deref for DynError {
46    type Target = dyn UniErrorOps + Send + Sync;
47
48    fn deref(&self) -> &Self::Target {
49        &*self.0
50    }
51}
52
53// *** UniKind trait ***
54
55/// A trait that specifies a custom error kind. Any specified to facilitate downcasting.
56pub trait UniKind: Debug + Any + Send + Sync {
57    /// The string value of the kind, if any. This is useful for programmatic evaluation
58    /// when the type is boxed in the error chain and the type is not known. Defaults to `""`.
59    fn value(&self, _cause: Option<Cause<'_>>) -> Cow<'static, str> {
60        Cow::Borrowed("")
61    }
62
63    /// Returns additional context for this specific kind, if any. Defaults to `None`.
64    fn context(&self, _cause: Option<Cause<'_>>) -> Option<Cow<'static, str>> {
65        None
66    }
67
68    /// Returns the code (typically for FFI) for this specific kind. Defaults to -1.
69    fn code(&self, _cause: Option<Cause<'_>>) -> i32 {
70        -1
71    }
72
73    /// Returns the concrete type name.
74    fn type_name(&self) -> &'static str {
75        type_name::<Self>()
76    }
77
78    /// Converts the `UniKind` into a `UniError` with the same kind.
79    fn into_error(self) -> UniError<Self>
80    where
81        Self: Sized,
82    {
83        UniError::from_kind(self)
84    }
85}
86
87impl dyn UniKind {
88    /// Attempts to downcast a `UniKind` to a specific concrete type.
89    pub fn downcast_ref<T: UniKind>(&self) -> Option<&T> {
90        let err: &dyn Any = self;
91        err.downcast_ref()
92    }
93}
94
95impl UniKind for () {}
96
97// *** UniErrorInner ***
98
99// NOTE: Each piece of the inner is separated into an independent cloneable so that
100// we have the option to create a new error from parts of an existing error.
101#[derive(Debug)]
102pub(crate) struct UniErrorInner<T> {
103    kind: Arc<T>,
104    context: Option<Cow<'static, str>>,
105    cause: Option<Arc<CauseInner>>,
106}
107
108impl<T: UniKind> UniErrorInner<T> {
109    pub fn prev_cause<'e>(&'e self) -> Option<Cause<'e>> {
110        self.cause.as_ref().map(|inner| Cause::from_inner(inner))
111    }
112}
113
114impl<T: UniKind> Display for UniErrorInner<T> {
115    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116        if let Some(context) = &self.context {
117            write!(f, "{}", context)?;
118        }
119
120        let cause = self.cause.as_ref().map(|inner| Cause::from_inner(inner));
121        let context = self.kind.context(cause);
122        if let Some(context) = context.as_ref() {
123            if self.context.is_some() {
124                write!(f, ": ")?;
125            }
126            write!(f, "{}", context)?;
127        }
128
129        if let Some(cause) = &self.prev_cause() {
130            if self.context.is_some() || context.is_some() {
131                write!(f, ": ")?;
132            }
133            write!(f, "{}", cause)?;
134        }
135
136        Ok(())
137    }
138}
139
140impl<T: UniKind> Error for UniErrorInner<T> {
141    fn source(&self) -> Option<&(dyn Error + 'static)> {
142        match self.prev_cause() {
143            Some(Cause::UniError(err)) => Some(&***err),
144            Some(Cause::UniStdError(err)) => Some(err),
145            Some(Cause::StdError(err)) => Some(err),
146            Some(Cause::UniDisplay(_)) | None => None,
147        }
148    }
149}
150
151// *** UniError ***
152
153/// A custom error type that can be used to return an error with a custom error kind.
154#[derive(Debug)]
155pub struct UniError<T> {
156    inner: Box<UniErrorInner<T>>,
157}
158
159impl<T: UniKind + Default> UniError<T> {
160    /// Creates a new `UniError` with a default kind, the provided context, and no cause.
161    pub fn from_context(context: impl Into<Cow<'static, str>>) -> Self {
162        Self::new(Default::default(), Some(context.into()), None)
163    }
164
165    /// Creates a new `UniError` with a default kind and the boxed error as the cause.
166    pub fn from_boxed(error: Box<dyn Error + Send + Sync>) -> Self {
167        Self::new(
168            Default::default(),
169            None,
170            Some(CauseInner::from_boxed_error(error)),
171        )
172    }
173
174    /// Creates a new `UniError` with a default kind, the provided context and the boxed error as the cause.
175    pub fn from_context_boxed(
176        context: impl Into<Cow<'static, str>>,
177        error: Box<dyn Error + Send + Sync>,
178    ) -> Self {
179        Self::new(
180            Default::default(),
181            Some(context.into()),
182            Some(CauseInner::from_boxed_error(error)),
183        )
184    }
185}
186
187impl<T: UniKind> UniError<T> {
188    pub(crate) fn new(
189        kind: T,
190        context: Option<Cow<'static, str>>,
191        cause: Option<CauseInner>,
192    ) -> Self {
193        Self {
194            inner: Box::new(UniErrorInner {
195                kind: Arc::new(kind),
196                context,
197                cause: cause.map(Arc::new),
198            }),
199        }
200    }
201
202    /// Creates a new `UniError` with the provided kind and the boxed error as the cause.
203    pub fn from_kind_boxed(kind: T, error: Box<dyn Error + Send + Sync>) -> Self {
204        Self::new(kind, None, Some(CauseInner::from_boxed_error(error)))
205    }
206
207    /// Creates a new `UniError` with the provided kind, the provided context and the boxed error as the cause.
208    pub fn from_kind_context_boxed(
209        kind: T,
210        context: impl Into<Cow<'static, str>>,
211        error: Box<dyn Error + Send + Sync>,
212    ) -> Self {
213        Self::new(
214            kind,
215            Some(context.into()),
216            Some(CauseInner::from_boxed_error(error)),
217        )
218    }
219
220    /// Creates a new `UniError` with the provided kind and no context or cause.
221    pub fn from_kind(kind: T) -> Self {
222        Self::new(kind, None, None)
223    }
224
225    /// Creates a new `UniError` with the provided kind, the provided context, and no cause.
226    pub fn from_kind_context(kind: T, context: impl Into<Cow<'static, str>>) -> Self {
227        Self::new(kind, Some(context.into()), None)
228    }
229
230    /// Returns a reference to the custom kind.
231    pub fn kind_ref(&self) -> &T {
232        &self.inner.kind
233    }
234}
235
236impl<T: Clone> UniError<T> {
237    /// Returns a clone of the custom kind.
238    pub fn kind_clone(&self) -> T {
239        (*self.inner.kind).clone()
240    }
241}
242
243/// A trait that specifies the operations that can be performed on a `UniError`.
244pub trait UniErrorOps: UniDisplay + Deref<Target = dyn Error + Send + Sync + 'static> {
245    /// Return a trait object reference to the custom kind.
246    fn kind_dyn_ref(&self) -> &dyn UniKind;
247
248    /// Returns the code (typically for FFI) for this specific kind
249    fn kind_code(&self) -> i32 {
250        self.kind_dyn_ref().code(self.prev_cause())
251    }
252
253    /// The string value of the kind, if any. This is useful for programmatic evaluation
254    /// when the type is boxed in the error chain and the type is not known
255    fn kind_value(&self) -> Cow<'static, str> {
256        self.kind_dyn_ref().value(self.prev_cause())
257    }
258
259    /// Returns additional context for this specific kind, if any
260    fn kind_context_str(&self) -> Option<Cow<'static, str>> {
261        self.kind_dyn_ref().context(self.prev_cause())
262    }
263
264    /// Returns true if the error is a `SimpleError`.
265    fn is_simple(&self) -> bool {
266        self.type_id() == TypeId::of::<SimpleError>()
267    }
268
269    /// Returns a reference to the first entry in the cause chain.
270    fn prev_cause<'e>(&'e self) -> Option<Cause<'e>>;
271
272    /// Returns an iterator over the cause chain.
273    fn chain(&self) -> Chain<'_>;
274
275    // TODO: Remove Option and make 'self' a possible candidate
276    /// Returns the root cause of this error. If `None` is returned then this error is the root cause.
277    fn root_cause(&self) -> Option<Cause<'_>>;
278}
279
280impl dyn UniErrorOps + Send + Sync {
281    /// Attempts to downcast a `DynError` to a reference to a `UniError<T>`.
282    pub fn downcast_ref<T: UniKind>(&self) -> Option<&UniError<T>> {
283        let err: &dyn Any = self;
284        err.downcast_ref()
285    }
286}
287
288impl<T: UniKind> UniErrorOps for UniError<T> {
289    fn kind_dyn_ref(&self) -> &dyn UniKind {
290        self.kind_ref()
291    }
292
293    fn prev_cause<'e>(&'e self) -> Option<Cause<'e>> {
294        self.inner.prev_cause()
295    }
296
297    fn chain(&self) -> Chain<'_> {
298        Chain::new(self.prev_cause())
299    }
300
301    fn root_cause(&self) -> Option<Cause<'_>> {
302        let mut chain = self.chain();
303        let mut root = chain.next();
304
305        for next in chain {
306            root = Some(next);
307        }
308        root
309    }
310}
311
312impl<T: UniKind> Display for UniError<T> {
313    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
314        <UniErrorInner<T> as Display>::fmt(&self.inner, f)
315    }
316}
317
318// Manually implement as derive requires T: Clone
319impl<T: UniKind> Clone for UniError<T> {
320    fn clone(&self) -> Self {
321        Self {
322            inner: Box::new(UniErrorInner {
323                kind: self.inner.kind.clone(),
324                context: self.inner.context.clone(),
325                cause: self.inner.cause.clone(),
326            }),
327        }
328    }
329}
330
331impl<T: UniKind + PartialEq + 'static> PartialEq for UniError<T> {
332    fn eq(&self, other: &Self) -> bool {
333        // Kind values must be equal at minimum
334        if self.inner.kind == other.inner.kind {
335            // If the kind is `()`, then the context must be equal, otherwise
336            // kind equality alone is sufficient
337            if self.is_simple() {
338                self.inner.context == other.inner.context
339            } else {
340                true
341            }
342        } else {
343            false
344        }
345    }
346}
347
348impl<T: UniKind> Deref for UniError<T> {
349    type Target = dyn Error + Sync + Send + 'static;
350
351    fn deref(&self) -> &Self::Target {
352        &self.inner
353    }
354}
355
356impl<T: UniKind> AsRef<dyn Error + Sync + Send> for UniError<T> {
357    fn as_ref(&self) -> &(dyn Error + Sync + Send + 'static) {
358        &**self
359    }
360}