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}