explicit_error/
error.rs

1use crate::bug::*;
2use crate::domain::Domain;
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::Bug] 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    Bug(Bug),
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) => Some(explicit_error.as_ref()),
24            Error::Bug(bug) => bug.source.as_ref().map(|e| e.as_ref()),
25        }
26    }
27}
28
29impl<D> Display for Error<D>
30where
31    D: Domain,
32{
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            Error::Domain(explicit_error) => Display::fmt(&explicit_error, f),
36            Error::Bug(bug) => bug.fmt(f),
37        }
38    }
39}
40
41impl<D> Error<D>
42where
43    D: Domain,
44{
45    /// Return true if it's a [Error::Domain] variant
46    pub fn is_domain(&self) -> bool {
47        matches!(*self, Error::Domain(_))
48    }
49
50    /// Return true if it's a [Error::Bug] variant
51    pub fn is_bug(&self) -> bool {
52        !self.is_domain()
53    }
54
55    /// Unwrap the [Error::Domain] variant, panic otherwise
56    pub fn unwrap(self) -> D {
57        match self {
58            Self::Domain(e) => *e,
59            Self::Bug(b) => unwrap_failed("called `Error::unwrap()` on an `Bug` value", &b),
60        }
61    }
62
63    /// Unwrap the [Error::Bug] variant, panic otherwise
64    pub fn unwrap_bug(self) -> Bug {
65        match self {
66            Self::Bug(b) => b,
67            Self::Domain(e) => {
68                unwrap_failed("called `Error::unwrap_err()` on an `Domain` value", &e)
69            }
70        }
71    }
72
73    /// Unwrap the source of either [Error::Domain] or [Error::Bug] variant, panic otherwise
74    pub fn unwrap_source(self) -> Box<dyn StdError + 'static> {
75        match self {
76            Error::Domain(domain) => domain.into_source().unwrap(),
77            Error::Bug(bug) => bug.source.unwrap(),
78        }
79    }
80}
81
82pub fn errors_chain_debug(source: &dyn StdError) -> String {
83    use std::fmt::Write;
84    let mut source = source;
85    let mut str = format!("{:?}", source);
86
87    while source.source().is_some() {
88        source = source.source().unwrap();
89        let _ = write!(&mut str, "->{:?}", source);
90    }
91
92    str
93}
94
95/// To use this trait on [Result] import the prelude `use explicit_error::prelude::*`
96pub trait ResultBug<T, S> {
97    /// Convert with a closure any error wrapped in a [Result] to an [Error]. Returning an [Ok] convert the wrapped type to
98    /// [Error::Domain].
99    /// Returning an [Err] generates a [Bug] with the orginal error has its source.
100    /// # Examples
101    /// Pattern match to convert to an [Error::Domain]
102    /// ```rust
103    /// # use actix_web::http::StatusCode;
104    /// # use problem_details::ProblemDetails;
105    /// # use http::Uri;
106    /// # use explicit_error_http::{Error, prelude::*, HttpError, derive::HttpError};
107    /// fn authz_middleware(public_identifier: &str) -> Result<(), Error> {
108    ///     let entity = fetch_bar(&public_identifier).map_err_or_bug(|e|
109    ///         match e {
110    ///             sqlx::Error::RowNotFound => Ok(
111    ///                 NotFoundError::Bar(
112    ///                     public_identifier.to_string())),
113    ///             _ => Err(e), // Convert to Error::Bug
114    ///         }
115    ///     )?;
116    ///
117    ///     Ok(entity)
118    /// }
119    /// # fn fetch_bar(public_identifier: &str) -> Result<(), sqlx::Error> {
120    /// #    Err(sqlx::Error::RowNotFound)
121    /// # }
122    /// # #[derive(HttpError, Debug)]
123    /// # enum NotFoundError {
124    /// #     Bar(String)
125    /// # }
126    /// # impl From<&NotFoundError> for HttpError {
127    /// #   fn from(value: &NotFoundError) -> Self {
128    /// #       let (label, id) = match value {
129    /// #           NotFoundError::Bar(public_identifier) => ("Bar", public_identifier)
130    /// #       };
131    /// #       HttpError::new(
132    /// #           StatusCode::NOT_FOUND,
133    /// #           ProblemDetails::new()
134    /// #               .with_type(Uri::from_static("/errors/not-found"))
135    /// #               .with_title("Not found")
136    /// #               .with_detail(format!("Unknown {label} with identifier {id}."))
137    /// #       )
138    /// #   }
139    /// # }
140    /// ```
141    fn map_err_or_bug<F, E, D>(self, op: F) -> Result<T, Error<D>>
142    where
143        F: FnOnce(S) -> Result<E, S>,
144        E: Into<Error<D>>,
145        S: StdError + 'static,
146        D: Domain;
147
148    /// Convert any [Result::Err] into a [Result::Err] wrapping a [Bug]
149    /// Use [bug](ResultBug::bug) instead if the error implements [std::error::Error]
150    ///  ```rust
151    /// # use std::fs::File;
152    /// # use explicit_error_exit::{Error, prelude::*};
153    /// fn foo() -> Result<(), Error> {
154    ///     let file: Result<File, std::io::Error> = File::open("foo.conf");
155    ///     file.bug_no_source().with_context("Configuration file foo.conf is missing.")?;
156    ///
157    ///     Err("error message").bug_no_source()?;
158    ///     # Ok(())
159    /// }
160    /// ```
161    fn bug_no_source(self) -> Result<T, Bug>;
162
163    /// Convert any [Result::Err] wrapping an error that implements
164    /// [std::error::Error] into a [Result::Err] wrapping a [Bug]
165    ///  ```rust
166    /// # use std::fs::File;
167    /// # use explicit_error_exit::{Error, prelude::*};
168    /// fn foo() -> Result<(), Error> {
169    ///     Err(sqlx::Error::RowNotFound)
170    ///         .bug()
171    ///         .with_context("Configuration file foo.conf is missing.")?;
172    ///     # Ok(())
173    /// }
174    /// ```
175    fn bug(self) -> Result<T, Bug>
176    where
177        S: StdError + 'static;
178
179    /// Convert any [Result::Err] into a [Result::Err] wrapping a [Bug] forcing backtrace capture
180    /// Use [bug_force](ResultBug::bug_force) instead if the error implements [std::error::Error]
181    ///  ```rust
182    /// # use std::fs::File;
183    /// # use explicit_error_exit::{Error, prelude::*};
184    /// fn foo() -> Result<(), Error> {
185    ///     let file: Result<File, std::io::Error> = File::open("foo.conf");
186    ///     file.bug_force().with_context("Configuration file foo.conf is missing.")?;
187    ///     # Ok(())
188    /// }
189    /// ```
190    fn bug_no_source_force(self) -> Result<T, Bug>;
191
192    /// Convert any [Result::Err] wrapping an error that implements
193    /// [std::error::Error] into a [Result::Err] wrapping a [Bug] forcing backtrace capture
194    ///  ```rust
195    /// # use std::fs::File;
196    /// # use explicit_error_exit::{Error, prelude::*};
197    /// fn foo() -> Result<(), Error> {
198    ///     Err(sqlx::Error::RowNotFound)
199    ///         .bug_force()
200    ///         .with_context("Configuration file foo.conf is missing.")?;
201    ///     # Ok(())
202    /// }
203    /// ```
204    fn bug_force(self) -> Result<T, Bug>
205    where
206        S: StdError + 'static;
207}
208
209impl<T, S> ResultBug<T, S> for Result<T, S> {
210    fn map_err_or_bug<F, E, D>(self, op: F) -> Result<T, Error<D>>
211    where
212        F: FnOnce(S) -> Result<E, S>,
213        E: Into<Error<D>>,
214        S: StdError + 'static,
215        D: Domain,
216    {
217        match self {
218            Ok(ok) => Ok(ok),
219            Err(error) => Err(match op(error) {
220                Ok(d) => d.into(),
221                Err(e) => Bug::new().with_source(e).into(),
222            }),
223        }
224    }
225
226    fn bug_no_source(self) -> Result<T, Bug> {
227        match self {
228            Ok(ok) => Ok(ok),
229            Err(_) => Err(Bug::new()),
230        }
231    }
232
233    fn bug_no_source_force(self) -> Result<T, Bug> {
234        match self {
235            Ok(ok) => Ok(ok),
236            Err(_) => Err(Bug::new_force()),
237        }
238    }
239
240    fn bug(self) -> Result<T, Bug>
241    where
242        S: StdError + 'static,
243    {
244        match self {
245            Ok(ok) => Ok(ok),
246            Err(error) => Err(Bug::new().with_source(error)),
247        }
248    }
249
250    fn bug_force(self) -> Result<T, Bug>
251    where
252        S: StdError + 'static,
253    {
254        match self {
255            Ok(ok) => Ok(ok),
256            Err(error) => Err(Bug::new_force().with_source(error)),
257        }
258    }
259}
260
261/// To use this trait on [Result] import the prelude `use explicit_error::prelude::*`
262pub trait ResultError<T, D>
263where
264    D: Domain,
265{
266    /// Pattern match on the [Error] source from either the [Error::Bug] or [Error::Domain] variant
267    /// if its type is the closure's parameter type.
268    /// # Examples
269    /// ```rust
270    /// # use actix_web::http::StatusCode;
271    /// # use http::Uri;
272    /// # use problem_details::ProblemDetails;
273    /// # use explicit_error_http::{prelude::*, HttpError, Result, derive::HttpError};
274    /// # #[derive(HttpError, Debug)]
275    /// # enum MyError {
276    /// #     Foo,
277    /// #     Bar,
278    /// # }
279    /// # impl From<&MyError> for HttpError {
280    /// #    fn from(value: &MyError) -> Self {
281    /// #        match value {
282    /// #            MyError::Foo | MyError::Bar => HttpError::new(
283    /// #                    StatusCode::BAD_REQUEST,
284    /// #                    ProblemDetails::new()
285    /// #                        .with_type(Uri::from_static("/errors/my-domain/foo"))
286    /// #                        .with_title("Foo format incorrect.")
287    /// #                ),
288    /// #        }
289    /// #    }
290    /// # }
291    /// # fn handler() -> Result<()> {
292    ///     let err: Result<()> = Err(MyError::Foo)?;
293    ///     
294    ///     // Do the map if the source's type of the Error is MyError
295    ///     err.try_map_on_source(|e| {
296    ///         match e {
297    ///             MyError::Foo => HttpError::new(
298    ///                 StatusCode::FORBIDDEN,
299    ///                 ProblemDetails::new()
300    ///                     .with_type(Uri::from_static("/errors/forbidden"))
301    ///                ),
302    ///             MyError::Bar => HttpError::new(
303    ///                 StatusCode::UNAUTHORIZED,
304    ///                 ProblemDetails::new()
305    ///                     .with_type(Uri::from_static("/errors/unauthorized"))
306    ///                ),
307    ///         }
308    ///     })?;
309    /// #     Ok(())
310    /// # }
311    /// ```
312    fn try_map_on_source<F, S, E>(self, op: F) -> Result<T, Error<D>>
313    where
314        F: FnOnce(S) -> E,
315        S: StdError + 'static,
316        E: Into<Error<D>>;
317
318    /// Add a context to any variant of an [Error] wrapped in a [Result::Err]
319    /// # Examples
320    /// ```rust
321    /// use explicit_error::{prelude::*, Bug};
322    /// Err::<(), _>(Bug::new()).with_context("Foo bar");
323    /// ```
324    fn with_context(self, context: impl Display) -> Result<T, Error<D>>;
325
326    /// Unwrap and downcast the source of either [Error::Domain] or [Error::Bug] variant, panic otherwise.
327    /// Usefull to assert_eq! in tests
328    /// # Examples
329    /// ```rust
330    /// use explicit_error_exit::{ExitError, derive::ExitError, Error};
331    /// # use std::process::ExitCode;
332    /// #[test]
333    /// fn test() {
334    ///     asser_eq!(to_test().unwrap_err_source::<MyError>(), MyError::Foo);
335    /// }
336    ///
337    /// #[derive(ExitError, Debug)]
338    /// enum MyError {
339    ///     Foo,
340    /// }
341    ///
342    /// # impl From<&MyError> for ExitError {
343    /// #     fn from(value: &MyError) -> Self {
344    /// #         match value {
345    /// #             MyError::Foo => ExitError::new(
346    /// #                     "Something went wrong because ..",
347    /// #                     ExitCode::from(42)
348    /// #                 ),
349    /// #         }
350    /// #     }
351    /// # }
352    ///
353    /// fn to_test() -> Result<(), Error> {
354    ///     Err(MyError::Foo)?;
355    ///     Ok(())
356    /// }
357    /// ```
358    fn unwrap_err_source<E>(self) -> E
359    where
360        E: StdError + 'static;
361}
362
363impl<T, D> ResultError<T, D> for Result<T, Error<D>>
364where
365    D: Domain,
366    T: std::fmt::Debug,
367{
368    fn try_map_on_source<F, S, E>(self, op: F) -> Result<T, Error<D>>
369    where
370        F: FnOnce(S) -> E,
371        S: StdError + 'static,
372        E: Into<Error<D>>,
373    {
374        match self {
375            Ok(ok) => Ok(ok),
376            Err(error) => match error {
377                Error::Domain(d) => {
378                    if d.source().is_some() && (d.source().as_ref().unwrap()).is::<S>() {
379                        return Err(op(*d.into_source().unwrap().downcast::<S>().unwrap()).into());
380                    }
381
382                    Err(Error::Domain(d))
383                }
384                Error::Bug(b) => {
385                    if let Some(s) = &b.source {
386                        if s.is::<S>() {
387                            return Err(op(*b.source.unwrap().downcast::<S>().unwrap()).into());
388                        }
389                    }
390
391                    Err(Error::Bug(b))
392                }
393            },
394        }
395    }
396
397    fn with_context(self, context: impl Display) -> Result<T, Error<D>> {
398        match self {
399            Ok(ok) => Ok(ok),
400            Err(error) => Err(match error {
401                Error::Domain(explicit_error) => explicit_error.with_context(context).into(),
402                Error::Bug(bug) => bug.with_context(context).into(),
403            }),
404        }
405    }
406
407    fn unwrap_err_source<E>(self) -> E
408    where
409        E: StdError + 'static,
410    {
411        *self.unwrap_err().unwrap_source().downcast::<E>().unwrap()
412    }
413}
414
415/// To use this trait on [Option] import the prelude `use explicit_error::prelude::*`
416pub trait OptionBug<T> {
417    /// Convert an [Option::None] into a [Result::Err] wrapping a [Bug]
418    /// ```rust
419    /// # use std::fs::File;
420    /// # use explicit_error_exit::{Error, prelude::*};
421    /// fn foo() -> Result<(), Error> {
422    ///     let option: Option<u8> = None;
423    ///     option.bug().with_context("Help debugging")?;
424    ///     # Ok(())
425    /// }
426    /// ```
427    fn bug(self) -> Result<T, Bug>;
428
429    /// Convert an [Option::None] into a [Result::Err] wrapping a [Bug] forcing backtrace capture
430    /// ```rust
431    /// # use std::fs::File;
432    /// # use explicit_error_exit::{Error, prelude::*};
433    /// fn foo() -> Result<(), Error> {
434    ///     let option: Option<u8> = None;
435    ///     option.bug_force().with_context("Help debugging")?;
436    ///     # Ok(())
437    /// }
438    /// ```
439    fn bug_force(self) -> Result<T, Bug>;
440}
441
442impl<T> OptionBug<T> for Option<T> {
443    fn bug(self) -> Result<T, Bug> {
444        match self {
445            Some(ok) => Ok(ok),
446            None => Err(Bug::new()),
447        }
448    }
449
450    fn bug_force(self) -> Result<T, Bug> {
451        match self {
452            Some(ok) => Ok(ok),
453            None => Err(Bug::new_force()),
454        }
455    }
456}
457
458/// To use this trait on [Result] import the prelude `use explicit_error::prelude::*`
459pub trait ResultBugWithContext<T> {
460    /// Add a context to the [Bug] wrapped in a [Result::Err]
461    /// # Examples
462    /// ```rust
463    /// # use explicit_error::{prelude::*, Bug};
464    /// Err::<(), _>(Bug::new()).with_context("Foo bar");
465    /// ```
466    fn with_context(self, context: impl Display) -> Result<T, Bug>;
467}
468
469impl<T> ResultBugWithContext<T> for Result<T, Bug> {
470    fn with_context(self, context: impl Display) -> Result<T, Bug> {
471        match self {
472            Ok(ok) => Ok(ok),
473            Err(b) => Err(b.with_context(context)),
474        }
475    }
476}