explicit_error_exit/
domain.rs

1use crate::{Error, ExitError};
2use explicit_error::{Domain, Error as ExplicitError};
3use std::{error::Error as StdError, fmt::Display};
4
5/// Wrapper for errors that are not a [Bug](explicit_error::Bug). It is used as the [explicit_error::Error::Domain] variant generic type.
6///
7/// It is highly recommended to implement the derive [ExitError](crate::derive::ExitError) which generates the boilerplate
8/// for your domain errors. Otherwise you can implement the [ToDomainError] trait.
9///
10/// [Error] implements `From<DomainError>`, use `?` and `.into()` in functions and closures to convert to the [Error::Domain](explicit_error::Error::Domain) variant.
11/// # Examples
12/// [DomainError] can be generated because of a predicate
13/// ```rust
14/// use explicit_error_exit::{prelude::*, ExitError, Result, derive::ExitError};
15/// use std::process::ExitCode;
16///
17/// #[derive(ExitError, Debug)]
18/// enum MyError {
19///     Foo,
20/// }
21///
22/// impl From<&MyError> for ExitError {
23///     fn from(value: &MyError) -> Self {
24///         match value {
25///             MyError::Foo => ExitError::new(
26///                     "Something went wrong because ..",
27///                     ExitCode::from(42)
28///                 ),
29///         }
30///     }
31/// }
32///
33/// fn business_logic() -> Result<()> {
34///     if 1 < 2 {
35///         Err(MyError::Foo)
36///             .with_context("Usefull context to debug or monitor")?;
37///     }
38/// #   Ok(())
39/// }
40/// ```
41/// Or from a [Result]
42/// ```rust
43/// # use explicit_error_exit::{prelude::*, ExitError, Result, derive::ExitError};
44/// # use std::process::ExitCode;
45/// # #[derive(Debug)]
46/// # enum ErrorKind {
47/// #     Foo,
48/// #     Bar,
49/// # }
50/// # impl std::fmt::Display for ErrorKind {
51/// #    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52/// #       write!(f, "ErrorKind")
53/// #    }
54/// # }
55/// # impl std::error::Error for ErrorKind {}
56/// #[derive(ExitError, Debug)]
57/// enum MyError {
58///     Foo,
59/// }
60///
61/// # impl From<&MyError> for ExitError {
62/// #     fn from(value: &MyError) -> Self {
63/// #         match value {
64/// #             MyError::Foo => ExitError::new(
65/// #                     "Something went wrong because ..",
66/// #                     ExitCode::from(42)
67/// #                 ),
68/// #         }
69/// #     }
70/// # }
71///
72/// fn business_logic() -> Result<()> {   
73///    Err(ErrorKind::Foo).map_err_or_bug(|e|
74///         match e {
75///             ErrorKind::Foo => Ok(MyError::Foo),
76///             _ => Err(e)
77///         }
78///     )?;
79/// # Ok(())
80/// }
81/// ```
82/// Or an [Option]
83/// ```rust
84/// # use explicit_error_exit::ExitError;
85/// # use std::process::ExitCode;
86/// Some(12).ok_or(ExitError::new("Something went wrong because ..", ExitCode::from(42)))?;
87/// # Ok::<(), ExitError>(())
88/// ```
89#[derive(Debug)]
90pub struct DomainError {
91    pub output: ExitError,
92    pub source: Option<Box<dyn StdError>>,
93}
94
95impl Display for DomainError {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        write!(f, "{}", self.output)
98    }
99}
100
101impl StdError for DomainError {
102    fn source(&self) -> Option<&(dyn StdError + 'static)> {
103        self.source.as_ref().map(|o| o.as_ref())
104    }
105}
106
107impl From<DomainError> for ExplicitError<DomainError> {
108    fn from(value: DomainError) -> Self {
109        Error::Domain(Box::new(value))
110    }
111}
112
113impl Domain for DomainError {
114    fn into_source(self) -> Option<Box<dyn std::error::Error>> {
115        self.source
116    }
117
118    fn with_context(mut self, context: impl Display) -> Self {
119        self.output = self.output.with_context(context);
120        self
121    }
122}
123
124/// Internally used by [ExitError](crate::derive::ExitError) derive.
125pub trait ToDomainError
126where
127    Self: StdError + 'static + Into<Error>,
128    for<'a> &'a Self: Into<ExitError>,
129{
130    fn to_domain_error(self) -> DomainError {
131        DomainError {
132            output: (&self).into(),
133            source: Some(Box::new(self)),
134        }
135    }
136
137    fn display(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        let bin_error: ExitError = self.into();
139        write!(f, "{}", bin_error)
140    }
141}
142
143/// To use this trait on [Result] import the prelude `use explicit_error_exit::prelude::*`
144pub trait ResultDomainWithContext<T, D>
145where
146    D: ToDomainError,
147    for<'a> &'a D: Into<ExitError>,
148{
149    /// Add a context to an error that convert to [Error] wrapped in a [Result::Err]
150    /// # Examples
151    /// ```rust
152    /// use explicit_error::{prelude::*, Bug};
153    /// Err::<(), _>(Bug::new()).with_context("Foo bar");
154    /// ```
155    fn with_context(self, context: impl std::fmt::Display) -> std::result::Result<T, DomainError>;
156}
157
158impl<T, D> ResultDomainWithContext<T, D> for std::result::Result<T, D>
159where
160    D: ToDomainError,
161    for<'a> &'a D: Into<ExitError>,
162{
163    fn with_context(self, context: impl std::fmt::Display) -> std::result::Result<T, DomainError> {
164        match self {
165            Ok(ok) => Ok(ok),
166            Err(e) => Err(e.to_domain_error().with_context(context)),
167        }
168    }
169}