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 [Fault](explicit_error::Fault). 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_fault(|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 + Send + Sync>>,
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_deref().map(|o| o as _)
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 + Send + Sync>> {
115        self.source
116    }
117
118    fn context(&self) -> Option<&str> {
119        self.output.context.as_deref()
120    }
121
122    fn with_context(mut self, context: impl Display) -> Self {
123        self.output = self.output.with_context(context);
124        self
125    }
126}
127
128/// Internally used by [ExitError](crate::derive::ExitError) derive.
129pub trait ToDomainError
130where
131    Self: StdError + 'static + Into<Error> + Send + Sync,
132    for<'a> &'a Self: Into<ExitError>,
133{
134    fn to_domain_error(self) -> DomainError {
135        DomainError {
136            output: (&self).into(),
137            source: Some(Box::new(self)),
138        }
139    }
140
141    fn display(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        let bin_error: ExitError = self.into();
143        write!(f, "{}", bin_error)
144    }
145}
146
147/// To use this trait on [Result] import the prelude `use explicit_error_exit::prelude::*`
148pub trait ResultDomainWithContext<T, D>
149where
150    D: ToDomainError,
151    for<'a> &'a D: Into<ExitError>,
152{
153    /// Add a context to an error that convert to [Error] wrapped in a [Result::Err]
154    /// # Examples
155    /// ```rust
156    /// use explicit_error::{prelude::*, Fault};
157    /// Err::<(), _>(Fault::new()).with_context("Foo bar");
158    /// ```
159    fn with_context(self, context: impl std::fmt::Display) -> std::result::Result<T, DomainError>;
160}
161
162impl<T, D> ResultDomainWithContext<T, D> for std::result::Result<T, D>
163where
164    D: ToDomainError,
165    for<'a> &'a D: Into<ExitError>,
166{
167    fn with_context(self, context: impl std::fmt::Display) -> std::result::Result<T, DomainError> {
168        match self {
169            Ok(ok) => Ok(ok),
170            Err(e) => Err(e.to_domain_error().with_context(context)),
171        }
172    }
173}