explicit_error_exit/lib.rs
1//! Built on top of [`explicit-error`](https://crates.io/crates/explicit-error), it provides idiomatic tools to manage errors that ends a process/program.
2//! Based on the [explicit-error](explicit_error) crate, its chore tenet is to favor explicitness by inlining the error output while remaining concise.
3//!
4//! The key features are:
5//! - Provide [MainResult] as a returned type of crate's main function to have well formated error.
6//! - Explicitly mark any error wrapped in a [Result] as a [Fault], a backtrace is captured.
7//! - Inline transformation of any errors wrapped in a [Result] into an [Error].
8//! - A derive macro [ExitError](derive::ExitError) to easily declare how enum or struct errors transform into an [Error].
9//! - Add context to errors to help debug.
10//!
11//! # A tour of explicit-error-bin
12//!
13//! The cornerstone of the library is the [Error] type. Use `Result<T, explicit_error_http::Error>`, or equivalently `explicit_error_bin::Result<T>`, as the return type of any faillible function returning errors that can end the program.
14//!
15//! ## Inline
16//!
17//! In the body of the function you can explicitly turn errors as exit errors using [ExitError] or marking them as [Fault].
18//! ```no_run
19//! use explicit_error_exit::{prelude::*, ExitError, Result, Fault, MainResult};
20//! use std::process::ExitCode;
21//! // Import the prelude to enable functions on std::result::Result
22//!
23//! fn main() -> MainResult { // Error message returned: "Error: Something went wrong because .."
24//! business_logic()?;
25//! Ok(())
26//! }
27//!
28//! fn business_logic() -> Result<()> {
29//! Err(42).map_err(|e|
30//! ExitError::new(
31//! "Something went wrong because ..",
32//! ExitCode::from(e)
33//! )
34//! )?;
35//!
36//! Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"))
37//! .or_fault()?;
38//!
39//! // Same behavior as or_fault() but the error is not captured as a source because it does not implement `[std::error::Error]`
40//! Err("error message").or_fault_no_source()?;
41//!
42//! if 1 > 2 {
43//! Err(Fault::new()
44//! .with_context("Usefull context to help debug."))?;
45//! }
46//!
47//! Ok(())
48//! }
49//!```
50//!
51//! ## Enum and struct
52//!
53//! Domain errors are often represented as enum or struct as they are raised in different places.
54//! To easily enable the conversion to [Error] use the [ExitError](derive::ExitError) derive and implement `From<&MyError> for ExitError`.
55//!
56//! ```rust
57//! use explicit_error_exit::{prelude::*, ExitError, Result, derive::ExitError};
58//! use std::process::ExitCode;
59//!
60//! #[derive(ExitError, Debug)]
61//! enum MyError {
62//! Foo,
63//! }
64//!
65//! impl From<&MyError> for ExitError {
66//! fn from(value: &MyError) -> Self {
67//! match value {
68//! MyError::Foo => ExitError::new(
69//! "Something went wrong because ..",
70//! ExitCode::from(42)
71//! ),
72//! }
73//! }
74//! }
75//!
76//! fn business_logic() -> Result<()> {
77//! Err(MyError::Foo)?;
78//!
79//! Ok(())
80//! }
81//! ```
82//!
83//! Note: The [ExitError](derive::ExitError) derive implements the conversion to [Error], the impl of [Display](std::fmt::Display) and [std::error::Error].
84//!
85//! # Pattern matching
86//!
87//! One of the drawbacks of using one and only one return type for different domain functions is that callers loose the ability to pattern match on the returned error.
88//! A solution is provided using [try_map_on_source](explicit_error::ResultError::try_map_on_source) on any `Result<T, Error>`, or equivalently `explicit_error_exit::Result<T>`.
89//!
90//! ```rust
91//! use explicit_error_exit::{prelude::*, ExitError, Result, derive::ExitError};
92//! use std::process::ExitCode;
93//!
94//! #[derive(ExitError, Debug)]
95//! enum MyError {
96//! Foo,
97//! Bar
98//! }
99//!
100//! # impl From<&MyError> for ExitError {
101//! # fn from(value: &MyError) -> Self {
102//! # ExitError::new(
103//! # "Something went wrong because ..",
104//! # ExitCode::from(42))
105//! # }
106//! # }
107//! fn business_logic() -> Result<()> {
108//! let err: Result<()> = Err(MyError::Foo)?;
109//!
110//! // Do the map if the source's type of the Error is MyError
111//! err.try_map_on_source(|e| {
112//! match e {
113//! MyError::Foo => ExitError::new(
114//! "Foo",
115//! ExitCode::SUCCESS),
116//! MyError::Bar => ExitError::new(
117//! "Bar",
118//! ExitCode::FAILURE),
119//! }
120//! })?;
121//!
122//! Ok(())
123//! }
124//! ```
125//!
126//! Note: under the hood [try_map_on_source](explicit_error::ResultError::try_map_on_source) perform some downcasting.
127mod domain;
128mod error;
129
130use std::process::{ExitCode, Termination};
131
132pub use domain::*;
133pub use error::*;
134
135pub type Error = explicit_error::Error<DomainError>;
136pub type Result<T> = std::result::Result<T, Error>;
137pub type MainResult = std::result::Result<(), MainError>;
138
139/// Re-import from [explicit_error] crate.
140pub use explicit_error::Fault;
141
142pub mod prelude {
143 pub use crate::ResultDomainWithContext;
144 pub use explicit_error::prelude::*;
145}
146
147pub mod derive {
148 pub use explicit_error_derive::ExitError;
149}
150
151/// Crate's main function returned type. It implements [Termination] to properly format console error.
152///
153/// To have your own termination custom logic, you can re-implement an equivalent of [MainError]. Have a look at source it is straightforward.
154pub struct MainError(Error);
155
156impl Termination for MainError {
157 fn report(self) -> std::process::ExitCode {
158 match self.0 {
159 explicit_error::Error::Domain(domain) => domain.output.exit_code,
160 explicit_error::Error::Fault(_) => ExitCode::FAILURE,
161 }
162 }
163}
164
165impl std::fmt::Debug for MainError {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 write!(f, "{}", self.0)
168 }
169}
170
171impl From<Error> for MainError {
172 fn from(value: Error) -> Self {
173 Self(value)
174 }
175}
176
177impl From<DomainError> for MainError {
178 fn from(value: DomainError) -> Self {
179 Self(value.into())
180 }
181}
182
183impl From<ExitError> for MainError {
184 fn from(value: ExitError) -> Self {
185 Self(value.into())
186 }
187}
188
189impl From<Fault> for MainError {
190 fn from(value: Fault) -> Self {
191 Self(value.into())
192 }
193}