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}