1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
//! Provide tools to have an explicit and concise error syntax for binary crates.
//!
//! To achieve this goal it provides [`explicit_error::Error`](crate::error::Error) an enum to explicitly differentiates
//! [`Fault`] errors that cannot panic from `Domain` errors that return informative feedbacks
//! to users. To generate an idiomatic syntax, `explicit-error` also provides traits implemented for [`std::result::Result`]
//! and [`std::option::Option`] .
//!
//! ```rust
//! use explicit_error_exit::{prelude::*, ExitError, derive::ExitError, Result, Fault};
//! use std::process::ExitCode;
//! # #[derive(ExitError, Debug)]
//! # enum MyError {
//! # Foo,
//! # Bar,
//! # }
//! # impl From<&MyError> for ExitError {
//! # fn from(value: &MyError) -> Self {
//! # ExitError::new(
//! # "", ExitCode::SUCCESS
//! # )
//! # }
//! # }
//!
//! fn business_logic() -> Result<()> {
//! let one = Ok::<_, MyError>(())
//! .or_fault()
//! .with_context("Usefull context to help debug.")?;
//!
//! let two = Some(2).ok_or_fault()?;
//!
//! if 1 < 2 {
//! Err(MyError::Foo)?;
//! }
//!
//! Err(MyError::Bar).map_err_or_fault(|e| {
//! match e {
//! MyError::Foo => Ok(ExitError::new(
//! "Informative feedback",
//! ExitCode::FAILURE
//! )),
//! _ => Err(e) // Convert to a Fault with the original error as its std::error::Error source
//! }
//! })?;
//!
//! Ok(())
//! }
//! ```
//!
//! [`explicit_error::Error`](crate::error::Error) is not an opaque type because its [`Domain`](crate::error::Error::Domain) variant wraps generic.
//! Usually wrapped types are containers of one error output format that optionnaly carries the underlying error as an `std::error::Error` source.
//!
//! Two crates are built on top of `explicit-error`:
//! - [`explicit-error-http`](https://crates.io/crates/explicit-error-http) provides tools and derives to idiomatically manage and monitor errors that generate an HTTP response. It has dedicated feature flag to integrate well with most populars web frameworks (actix-web, axum).
//! - [`explicit-error-exit`](https://crates.io/crates/explicit-error-exit) to manage errors that end a process/program.
//!
//! If you want to have more examples to understand the benefits have a look at [`explicit-error-http`](https://crates.io/crates/explicit-error-http) doc and examples.
//!
//! If you want to use this crate to build you own tooling for your custom error output format. Having a look at how [
//! `explicit-error-exit`](https://crates.io/crates/explicit-error-exit) is implemented is a good starting point.
//!
//! If you want to understand the crate's genesis and why it brings something new to the table, read the next two sections.
//!
//! ## Comparaison to Anyhow
//!
//! Anyhow and Explicit both aims to help error management in binary crates but they have opposite trade-offs.
//! The former favor maximum flexibility for implicitness while `explicit-error`
//! favor explicitness and output format enforcement for less flexibility.
//!
//! With Anyhow the `?` operator can be used on any error that implements `std::error::Error` in any function
//! returning `Result<_, anyhow::Error>`. There is no meaningfull information required about what the error means
//! exactly: caller must match on it? domain error? bug?
//!
//! On the contrary `explicit-error::Error` is not an opaque type. It is an enum with two variants:
//!
//! To illustrate, below an example from the `explicit-error-http` crate.
//!
//! ```rust
//! # use explicit_error::{Fault, Domain};
//! pub enum Error<D> {
//! Domain(Box<D>), // Box for size: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
//! Fault(Fault), // Can be generated from any `Result::Err`, `Option::None` or out of the box
//! }
//! ```
//!
//! The chore principle is that the `?` operator can be used on errors in functions
//! that return a `Result<T, explicit_error::Error<D>>` if they are either:
//! - marked as `Fault`
//! - convertible to D. Usually D represents the error output format.
//!
//! For example in the `explicit-error-http` crate, `D` is the type `HttpError`. Any faillible function
//! returning errors that convert to an HTTP response can have as a return type `Result<T, explicit_error::Error<HttpError>`.
//! To help application's domain errors represented as enums or structs to be convertible to `D`, crates provide derives to reduce boilerplate.
//!
//! Below an example from the `explicit-error-http` crate to show what the syntax looks like.
//!
//! ```rust
//! # use problem_details::ProblemDetails;
//! # use http::{StatusCode, Uri};
//! # use explicit_error_http::{prelude::*, HttpError, Result, Fault, derive::HttpError};
//! #[derive(HttpError, Debug)]
//! enum MyError {
//! Foo,
//! }
//!
//! impl From<&MyError> for HttpError {
//! fn from(value: &MyError) -> Self {
//! match value {
//! MyError::Foo => HttpError::new(
//! StatusCode::BAD_REQUEST,
//! ProblemDetails::new()
//! .with_type(Uri::from_static("/errors/my-domain/foo"))
//! .with_title("Foo format incorrect.")
//! ),
//! }
//! }
//! }
//!
//! fn business_logic() -> Result<()> {
//! // Error from a library that should not happen
//! Err(sqlx::Error::RowNotFound)
//! .or_fault()?;
//!
//! // Application error
//! if 1 > 2 {
//! Err(MyError::Foo)?;
//! }
//!
//! // Inline error
//! Err(42).map_err(|_|
//! HttpError::new(
//! StatusCode::BAD_REQUEST,
//! ProblemDetails::new()
//! .with_type(Uri::from_static("/errors/business-logic"))
//! .with_title("Informative feedback for the user."),
//! )
//! )?;
//!
//! Ok(())
//! }
//!```
//!
//! As you can see, function's error flow logic is straightforward, explicit and remain concise!
//!
//! Most of the time, for "lib functions", relying on the caller to generate a proper domain error,
//! the best implementation is to return a dedicated error type for idiomatic pattern matching.
//! For rare cases when you have to pattern match on the source error of an `explict_error::Error`,
//! [`explict_error::try_map_on_source`](crate::error::ResultError::try_map_on_source) can be used.
//!
//! ## Comparaison to ThisError
//!
//! Thiserror is a great tool for library errors. That's why it supplements well with `explicit-error`
//! which is designed for binary crates.
//!
//! _Why only relying on it for application can be a footgun?_
//!
//! When using ThisError you naturally tend to use enums as errors everywhere and heavily rely on the derive `#[from]`
//! to have conversion between types giving the ability to use the `?` operator almost everywhere without thinking.
//!
//! It is fine in small applications as the combinatory between errors remains limited. But as the code base grows
//! everything becomes more and more implicit. Understand the error logic flow starts to be really painfull as you
//! have to read multiple implementations spread in different places.
//!
//! Moreover boilerplate and coupling increase (also maintenance cost) because enums have multiple meaningless variants
//! from a domain point of view:
//! - encapsulate types from dependencies
//! - represent stuff that should not happen (aka Fault) and cannot panic.
//!
//! Finally, it is also painfull to have consistency in error output format and monitoring:
//! - Without a type to unified errors the implementation are spread
//! - With one type to unify errors (eg: a big enum called AppError), cohesion is discreased with more boilerplate
pub use *;
pub use *;
pub use *;
!