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