explicit_error/
error.rs

1use crate::domain::Domain;
2use crate::fault::*;
3use crate::unwrap_failed;
4use std::{error::Error as StdError, fmt::Display};
5
6/// Use `Result<T, explicit_error::Error>` as the return type of any binary crate
7/// faillible function returning errors.
8/// The [Error::Fault] variant is for errors that should not happen but cannot panic.
9/// The [Error::Domain] variant is for domain errors that provide feedbacks to the user.
10/// For library or functions that require the caller to pattern match on the returned error, a dedicated type is prefered.
11#[derive(Debug)]
12pub enum Error<D: Domain> {
13    Domain(Box<D>), // Box for size: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
14    Fault(Fault),
15}
16
17impl<D> StdError for Error<D>
18where
19    D: Domain,
20{
21    fn source(&self) -> Option<&(dyn StdError + 'static)> {
22        match self {
23            Error::Domain(explicit_error) => {
24                explicit_error.source().or(Some(explicit_error.as_ref()))
25            }
26            Error::Fault(fault) => fault.source().or(Some(fault)),
27        }
28    }
29}
30
31impl<D> Display for Error<D>
32where
33    D: Domain,
34{
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        match self {
37            Error::Domain(explicit_error) => Display::fmt(&explicit_error, f),
38            Error::Fault(fault) => fault.fmt(f),
39        }
40    }
41}
42
43impl<D> Error<D>
44where
45    D: Domain,
46{
47    /// Return true if it's a [Error::Domain] variant
48    pub fn is_domain(&self) -> bool {
49        matches!(*self, Error::Domain(_))
50    }
51
52    /// Return true if it's a [Error::Fault] variant
53    pub fn is_fault(&self) -> bool {
54        !self.is_domain()
55    }
56
57    /// Unwrap the [Error::Domain] variant, panic otherwise
58    pub fn unwrap(self) -> D {
59        match self {
60            Self::Domain(e) => *e,
61            Self::Fault(f) => unwrap_failed("called `Error::unwrap()` on an `Fault` value", &f),
62        }
63    }
64
65    /// Unwrap the [Error::Fault] variant, panic otherwise
66    pub fn unwrap_fault(self) -> Fault {
67        match self {
68            Self::Fault(b) => b,
69            Self::Domain(e) => {
70                unwrap_failed("called `Error::unwrap_err()` on an `Domain` value", &e)
71            }
72        }
73    }
74
75    /// Try to downcast the source of the type wrapped in either [Error::Domain] or [Error::Fault] variant.
76    /// If it is not set try to downcast the type wrapped.
77    /// Usefull to assert_eq! in tests
78    /// # Examples
79    /// ```rust
80    /// use explicit_error_exit::{ExitError, derive::ExitError, Error};
81    /// # use std::process::ExitCode;
82    /// #[test]
83    /// fn test() {
84    ///     assert_eq!(to_test().unwrap_err().downcast_source::<MyError>().unwrap(), MyError::Foo);
85    /// }
86    ///
87    /// #[derive(ExitError, Debug)]
88    /// enum MyError {
89    ///     Foo,
90    /// }
91    ///
92    /// # impl From<&MyError> for ExitError {
93    /// #     fn from(value: &MyError) -> Self {
94    /// #         match value {
95    /// #             MyError::Foo => ExitError::new(
96    /// #                     "Something went wrong because ..",
97    /// #                     ExitCode::from(42)
98    /// #                 ),
99    /// #         }
100    /// #     }
101    /// # }
102    ///
103    /// fn to_test() -> Result<(), Error> {
104    ///     Err(MyError::Foo)?;
105    ///     Ok(())
106    /// }
107    /// ```
108    pub fn downcast_source<E>(self) -> Result<E, Box<dyn std::error::Error + 'static>>
109    where
110        E: StdError + 'static,
111    {
112        match self {
113            Error::Domain(domain) => match domain.source() {
114                Some(_) => domain.into_source().unwrap(),
115                None => domain,
116            },
117            Error::Fault(fault) => match fault.source() {
118                Some(_) => fault.source.unwrap(),
119                None => Box::new(fault),
120            },
121        }
122        .downcast::<E>()
123        .map(|o| *o)
124    }
125
126    /// Try to downcast the source of the type wrapped in either [Error::Domain] or [Error::Fault] variant.
127    /// If it is not set try to downcast the type wrapped.
128    /// Usefull to assert_eq! in tests
129    /// # Examples
130    /// ```rust
131    /// use explicit_error_exit::{ExitError, derive::ExitError, Error};
132    /// # use std::process::ExitCode;
133    /// #[test]
134    /// fn test() {
135    ///     assert_eq!(to_test().unwrap_err().downcast_source_ref()::<MyError>().unwrap(), &MyError::Foo);
136    /// }
137    ///
138    /// #[derive(ExitError, Debug)]
139    /// enum MyError {
140    ///     Foo,
141    /// }
142    ///
143    /// # impl From<&MyError> for ExitError {
144    /// #     fn from(value: &MyError) -> Self {
145    /// #         match value {
146    /// #             MyError::Foo => ExitError::new(
147    /// #                     "Something went wrong because ..",
148    /// #                     ExitCode::from(42)
149    /// #                 ),
150    /// #         }
151    /// #     }
152    /// # }
153    ///
154    /// fn to_test() -> Result<(), Error> {
155    ///     Err(MyError::Foo)?;
156    ///     Ok(())
157    /// }
158    /// ```
159    pub fn downcast_source_ref<E>(&self) -> Option<&E>
160    where
161        E: StdError + 'static,
162    {
163        match self {
164            Error::Domain(domain) => match domain.source() {
165                Some(_) => domain.source().unwrap(),
166                None => domain as &dyn StdError,
167            },
168            Error::Fault(fault) => match fault.source() {
169                Some(_) => fault.source().unwrap(),
170                None => fault as &dyn StdError,
171            },
172        }
173        .downcast_ref::<E>()
174    }
175
176    /// Add context of either [Error::Domain] or [Error::Fault] variant.
177    /// Override existing context
178    pub fn with_context(self, context: impl Display) -> Self {
179        match self {
180            Error::Domain(d) => Error::Domain(Box::new(d.with_context(context))),
181            Error::Fault(fault) => Error::Fault(fault.with_context(context)),
182        }
183    }
184
185    /// Return the context of either [Error::Domain] or [Error::Fault] variant.
186    pub fn context(&self) -> Option<&str> {
187        match self {
188            Error::Domain(d) => d.context(),
189            Error::Fault(fault) => fault.context(),
190        }
191    }
192}
193
194pub fn errors_chain_debug(source: &dyn StdError) -> String {
195    use std::fmt::Write;
196    let mut source = source;
197    let mut str = format!("{:?}", source);
198
199    while source.source().is_some() {
200        source = source.source().unwrap();
201        let _ = write!(&mut str, "->{:?}", source);
202    }
203
204    str
205}
206
207/// To use this trait on [Result] import the prelude `use explicit_error::prelude::*`
208pub trait ResultFault<T, S> {
209    /// Convert with a closure any error wrapped in a [Result] to an [Error]. Returning an [Ok] convert the wrapped type to
210    /// [Error::Domain].
211    /// Returning an [Err] generates a [Fault] with the orginal error has its source.
212    /// # Examples
213    /// Pattern match to convert to an [Error::Domain]
214    /// ```rust
215    /// # use actix_web::http::StatusCode;
216    /// # use problem_details::ProblemDetails;
217    /// # use http::Uri;
218    /// # use explicit_error_http::{Error, prelude::*, HttpError, derive::HttpError};
219    /// fn authz_middleware(public_identifier: &str) -> Result<(), Error> {
220    ///     let entity = fetch_bar(&public_identifier).map_err_or_fault(|e|
221    ///         match e {
222    ///             sqlx::Error::RowNotFound => Ok(
223    ///                 NotFoundError::Bar(
224    ///                     public_identifier.to_string())),
225    ///             _ => Err(e), // Convert to Error::Fault
226    ///         }
227    ///     )?;
228    ///
229    ///     Ok(entity)
230    /// }
231    /// # fn fetch_bar(public_identifier: &str) -> Result<(), sqlx::Error> {
232    /// #    Err(sqlx::Error::RowNotFound)
233    /// # }
234    /// # #[derive(HttpError, Debug)]
235    /// # enum NotFoundError {
236    /// #     Bar(String)
237    /// # }
238    /// # impl From<&NotFoundError> for HttpError {
239    /// #   fn from(value: &NotFoundError) -> Self {
240    /// #       let (label, id) = match value {
241    /// #           NotFoundError::Bar(public_identifier) => ("Bar", public_identifier)
242    /// #       };
243    /// #       HttpError::new(
244    /// #           StatusCode::NOT_FOUND,
245    /// #           ProblemDetails::new()
246    /// #               .with_type(Uri::from_static("/errors/not-found"))
247    /// #               .with_title("Not found")
248    /// #               .with_detail(format!("Unknown {label} with identifier {id}."))
249    /// #       )
250    /// #   }
251    /// # }
252    /// ```
253    fn map_err_or_fault<F, E, D>(self, op: F) -> Result<T, Error<D>>
254    where
255        F: FnOnce(S) -> Result<E, S>,
256        E: Into<Error<D>>,
257        S: StdError + 'static,
258        D: Domain;
259
260    /// Convert any [Result::Err] into a [Result::Err] wrapping a [Fault]
261    /// Use [fault](ResultFault::or_fault) instead if the error implements [std::error::Error]
262    ///  ```rust
263    /// # use std::fs::File;
264    /// # use explicit_error_exit::{Error, prelude::*};
265    /// fn foo() -> Result<(), Error> {
266    ///     let file: Result<File, std::io::Error> = File::open("foo.conf");
267    ///     file.or_fault_no_source().with_context("Configuration file foo.conf is missing.")?;
268    ///
269    ///     Err("error message").or_fault_no_source()?;
270    ///     # Ok(())
271    /// }
272    /// ```
273    fn or_fault_no_source(self) -> Result<T, Fault>;
274
275    /// Convert any [Result::Err] wrapping an error that implements
276    /// [std::error::Error] into a [Result::Err] wrapping a [Fault]
277    ///  ```rust
278    /// # use std::fs::File;
279    /// # use explicit_error_exit::{Error, prelude::*};
280    /// fn foo() -> Result<(), Error> {
281    ///     Err(sqlx::Error::RowNotFound)
282    ///         .or_fault()
283    ///         .with_context("Configuration file foo.conf is missing.")?;
284    ///     # Ok(())
285    /// }
286    /// ```
287    fn or_fault(self) -> Result<T, Fault>
288    where
289        S: StdError + 'static;
290
291    /// Convert any [Result::Err] into a [Result::Err] wrapping a [Fault] forcing backtrace capture
292    /// Use [or_fault_force](ResultFault::or_fault_force) instead if the error implements [std::error::Error]
293    ///  ```rust
294    /// # use std::fs::File;
295    /// # use explicit_error_exit::{Error, prelude::*};
296    /// fn foo() -> Result<(), Error> {
297    ///     let file: Result<File, std::io::Error> = File::open("foo.conf");
298    ///     file.or_fault_force().with_context("Configuration file foo.conf is missing.")?;
299    ///     # Ok(())
300    /// }
301    /// ```
302    fn or_fault_no_source_force(self) -> Result<T, Fault>;
303
304    /// Convert any [Result::Err] wrapping an error that implements
305    /// [std::error::Error] into a [Result::Err] wrapping a [Fault] forcing backtrace capture
306    ///  ```rust
307    /// # use std::fs::File;
308    /// # use explicit_error_exit::{Error, prelude::*};
309    /// fn foo() -> Result<(), Error> {
310    ///     Err(sqlx::Error::RowNotFound)
311    ///         .or_fault_force()
312    ///         .with_context("Configuration file foo.conf is missing.")?;
313    ///     # Ok(())
314    /// }
315    /// ```
316    fn or_fault_force(self) -> Result<T, Fault>
317    where
318        S: StdError + 'static;
319}
320
321impl<T, S> ResultFault<T, S> for Result<T, S> {
322    fn map_err_or_fault<F, E, D>(self, op: F) -> Result<T, Error<D>>
323    where
324        F: FnOnce(S) -> Result<E, S>,
325        E: Into<Error<D>>,
326        S: StdError + 'static,
327        D: Domain,
328    {
329        match self {
330            Ok(ok) => Ok(ok),
331            Err(error) => Err(match op(error) {
332                Ok(d) => d.into(),
333                Err(e) => Fault::new().with_source(e).into(),
334            }),
335        }
336    }
337
338    fn or_fault_no_source(self) -> Result<T, Fault> {
339        match self {
340            Ok(ok) => Ok(ok),
341            Err(_) => Err(Fault::new()),
342        }
343    }
344
345    fn or_fault_no_source_force(self) -> Result<T, Fault> {
346        match self {
347            Ok(ok) => Ok(ok),
348            Err(_) => Err(Fault::new_force()),
349        }
350    }
351
352    fn or_fault(self) -> Result<T, Fault>
353    where
354        S: StdError + 'static,
355    {
356        match self {
357            Ok(ok) => Ok(ok),
358            Err(error) => Err(Fault::new().with_source(error)),
359        }
360    }
361
362    fn or_fault_force(self) -> Result<T, Fault>
363    where
364        S: StdError + 'static,
365    {
366        match self {
367            Ok(ok) => Ok(ok),
368            Err(error) => Err(Fault::new_force().with_source(error)),
369        }
370    }
371}
372
373/// To use this trait on [Result] import the prelude `use explicit_error::prelude::*`
374pub trait ResultError<T, D>
375where
376    D: Domain,
377{
378    /// Pattern match on the [Error] source from either the [Error::Fault] or [Error::Domain] variant
379    /// if its type is the closure's parameter type.
380    /// # Examples
381    /// ```rust
382    /// # use actix_web::http::StatusCode;
383    /// # use http::Uri;
384    /// # use problem_details::ProblemDetails;
385    /// # use explicit_error_http::{prelude::*, HttpError, Result, derive::HttpError};
386    /// # #[derive(HttpError, Debug)]
387    /// # enum MyError {
388    /// #     Foo,
389    /// #     Bar,
390    /// # }
391    /// # impl From<&MyError> for HttpError {
392    /// #    fn from(value: &MyError) -> Self {
393    /// #        match value {
394    /// #            MyError::Foo | MyError::Bar => HttpError::new(
395    /// #                    StatusCode::BAD_REQUEST,
396    /// #                    ProblemDetails::new()
397    /// #                        .with_type(Uri::from_static("/errors/my-domain/foo"))
398    /// #                        .with_title("Foo format incorrect.")
399    /// #                ),
400    /// #        }
401    /// #    }
402    /// # }
403    /// # fn handler() -> Result<()> {
404    ///     let err: Result<()> = Err(MyError::Foo)?;
405    ///     
406    ///     // Do the map if the source's type of the Error is MyError
407    ///     err.try_map_on_source(|e| {
408    ///         match e {
409    ///             MyError::Foo => HttpError::new(
410    ///                 StatusCode::FORBIDDEN,
411    ///                 ProblemDetails::new()
412    ///                     .with_type(Uri::from_static("/errors/forbidden"))
413    ///                ),
414    ///             MyError::Bar => HttpError::new(
415    ///                 StatusCode::UNAUTHORIZED,
416    ///                 ProblemDetails::new()
417    ///                     .with_type(Uri::from_static("/errors/unauthorized"))
418    ///                ),
419    ///         }
420    ///     })?;
421    /// #     Ok(())
422    /// # }
423    /// ```
424    fn try_map_on_source<F, S, E>(self, op: F) -> Result<T, Error<D>>
425    where
426        F: FnOnce(S) -> E,
427        S: StdError + 'static,
428        E: Into<Error<D>>;
429
430    /// Add a context to any variant of an [Error] wrapped in a [Result::Err]
431    /// # Examples
432    /// ```rust
433    /// use explicit_error::{prelude::*, Fault};
434    /// Err::<(), _>(Fault::new()).with_context("Foo bar");
435    /// ```
436    fn with_context(self, context: impl Display) -> Result<T, Error<D>>;
437}
438
439impl<T, D> ResultError<T, D> for Result<T, Error<D>>
440where
441    D: Domain,
442    T: std::fmt::Debug,
443{
444    fn try_map_on_source<F, S, E>(self, op: F) -> Result<T, Error<D>>
445    where
446        F: FnOnce(S) -> E,
447        S: StdError + 'static,
448        E: Into<Error<D>>,
449    {
450        match self {
451            Ok(ok) => Ok(ok),
452            Err(error) => match error {
453                Error::Domain(d) => {
454                    if d.source().is_some() && (d.source().as_ref().unwrap()).is::<S>() {
455                        return Err(op(*d.into_source().unwrap().downcast::<S>().unwrap()).into());
456                    }
457
458                    Err(Error::Domain(d))
459                }
460                Error::Fault(b) => {
461                    if let Some(s) = &b.source {
462                        if s.is::<S>() {
463                            return Err(op(*b.source.unwrap().downcast::<S>().unwrap()).into());
464                        }
465                    }
466
467                    Err(Error::Fault(b))
468                }
469            },
470        }
471    }
472
473    fn with_context(self, context: impl Display) -> Result<T, Error<D>> {
474        match self {
475            Ok(ok) => Ok(ok),
476            Err(error) => Err(match error {
477                Error::Domain(explicit_error) => explicit_error.with_context(context).into(),
478                Error::Fault(fault) => fault.with_context(context).into(),
479            }),
480        }
481    }
482}
483
484/// To use this trait on [Option] import the prelude `use explicit_error::prelude::*`
485pub trait OptionFault<T> {
486    /// Transforms the `Option<T>` into a `Result<T, Fault>`, mapping Some(v) to Ok(v) and None to Err(Fault)
487    /// ```rust
488    /// # use std::fs::File;
489    /// # use explicit_error_exit::{Error, prelude::*};
490    /// fn foo() -> Result<(), Error> {
491    ///     let option: Option<u8> = None;
492    ///     option.ok_or_fault().with_context("Help debugging")?;
493    ///     # Ok(())
494    /// }
495    /// ```
496    fn ok_or_fault(self) -> Result<T, Fault>;
497
498    /// Transforms the `Option<T>` into a `Result<T, Fault>`, mapping Some(v) to Ok(v) and None to Err(Fault)
499    /// forcing backtrace capture
500    /// ```rust
501    /// # use std::fs::File;
502    /// # use explicit_error_exit::{Error, prelude::*};
503    /// fn foo() -> Result<(), Error> {
504    ///     let option: Option<u8> = None;
505    ///     option.ok_or_fault_force().with_context("Help debugging")?;
506    ///     # Ok(())
507    /// }
508    /// ```
509    fn ok_or_fault_force(self) -> Result<T, Fault>;
510}
511
512impl<T> OptionFault<T> for Option<T> {
513    fn ok_or_fault(self) -> Result<T, Fault> {
514        match self {
515            Some(ok) => Ok(ok),
516            None => Err(Fault::new()),
517        }
518    }
519
520    fn ok_or_fault_force(self) -> Result<T, Fault> {
521        match self {
522            Some(ok) => Ok(ok),
523            None => Err(Fault::new_force()),
524        }
525    }
526}
527
528/// To use this trait on [Result] import the prelude `use explicit_error::prelude::*`
529pub trait ResultFaultWithContext<T> {
530    /// Add a context to the [Fault] wrapped in a [Result::Err]
531    /// # Examples
532    /// ```rust
533    /// # use explicit_error::{prelude::*, Fault};
534    /// Err::<(), _>(Fault::new()).with_context("Foo bar");
535    /// ```
536    fn with_context(self, context: impl Display) -> Result<T, Fault>;
537}
538
539impl<T> ResultFaultWithContext<T> for Result<T, Fault> {
540    fn with_context(self, context: impl Display) -> Result<T, Fault> {
541        match self {
542            Ok(ok) => Ok(ok),
543            Err(b) => Err(b.with_context(context)),
544        }
545    }
546}
547
548#[cfg(test)]
549mod test;