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//! [`Bug`] 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, Bug};
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//!         .bug()
27//!         .with_context("Usefull context to help debug.")?;
28//!
29//!     let two = Some(2).bug()?;
30//!
31//!     if 1 < 2 {
32//!         Err(MyError::Foo)?;
33//!     }
34//!     
35//!     Err(MyError::Bar).map_err_or_bug(|e| {
36//!         match e {
37//!             MyError::Foo => Ok(ExitError::new(
38//!                 "Informative feedback",
39//!                 ExitCode::FAILURE
40//!             )),
41//!             _ => Err(e) // Convert to a Bug 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 WIP).
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::{Bug, Domain};
79//! pub enum Error<D: Domain> {
80//!     Domain(Box<D>), // Box for size: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold
81//!     Bug(Bug), // 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 `Bug`
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 actix_web::http::StatusCode;
98//! # use problem_details::ProblemDetails;
99//! # use http::Uri;
100//! # use explicit_error_http::{prelude::*, HttpError, Result, Bug, derive::HttpError};
101//! #[derive(HttpError, Debug)]
102//! enum MyError {
103//!     Foo,
104//! }
105//!
106//! impl From<&MyError> for HttpError {
107//!     fn from(value: &MyError) -> Self {
108//!         match value {
109//!             MyError::Foo => HttpError::new(
110//!                     StatusCode::BAD_REQUEST,
111//!                     ProblemDetails::new()
112//!                         .with_type(Uri::from_static("/errors/my-domain/foo"))
113//!                         .with_title("Foo format incorrect.")
114//!                 ),
115//!         }
116//!     }
117//! }
118//!
119//! fn business_logic() -> Result<()> {
120//!     // Error from a library that should not happen
121//!     Err(sqlx::Error::RowNotFound)
122//!         .bug()?;
123//!
124//!     // Application error
125//!     if 1 > 2 {
126//!         Err(MyError::Foo)?;
127//!     }
128//!
129//!     // Inline error
130//!     Err(42).map_err(|_|
131//!         HttpError::new(
132//!             StatusCode::BAD_REQUEST,
133//!             ProblemDetails::new()
134//!                 .with_type(Uri::from_static("/errors/business-logic"))
135//!                 .with_title("Informative feedback for the user."),
136//!         )
137//!     )?;
138//!
139//!     Ok(())
140//! }
141//!```
142//!
143//! As you can see, function's error flow logic is straightforward, explicit and remain concise!
144//!
145//! Most of the time, for "lib functions", relying on the caller to generate a proper domain error,
146//! the best implementation is to return a dedicated error type for idiomatic pattern matching.
147//! For rare cases when you have to pattern match on the source error of an `explict_error::Error`,
148//! [`explict_error::try_map_on_source`](crate::error::ResultError::try_map_on_source) can be used.
149//!
150//! ## Comparaison to ThisError
151//!
152//! Thiserror is a great tool for library errors. That's why it supplements well with `explicit-error`
153//! which is designed for binary crates.
154//!
155//! _Why only relying on it for application can be a footgun?_
156//!
157//! When using ThisError you naturally tend to use enums as errors everywhere and heavily rely on the derive `#[from]`
158//! to have conversion between types giving the ability to use the `?` operator almost everywhere without thinking.
159//!
160//! It is fine in small applications as the combinatory between errors remains limited. But as the code base grows
161//! everything becomes more and more implicit. Understand the error logic flow starts to be really painfull as you
162//! have to read multiple implementations spread in different places.
163//!
164//! Moreover boilerplate and coupling increase (also maintenance cost) because enums have multiple meaningless variants
165//! from a domain point of view:
166//! - encapsulate types from dependencies
167//! - represent stuff that should not happen (aka Bug) and cannot panic.
168//!
169//! Finally, it is also painfull to have consistency in error output format and monitoring:
170//! - Without a type to unified errors the implementation are spread
171//! - With one type to unify errors (eg: a big enum called AppError), cohesion is discreased with more boilerplate
172mod bug;
173mod domain;
174mod error;
175
176pub use bug::*;
177pub use domain::*;
178pub use error::*;
179
180pub mod prelude {
181    pub use crate::error::{OptionBug, ResultBug, ResultBugWithContext, ResultError};
182}
183
184fn unwrap_failed(msg: &str, error: &dyn std::fmt::Debug) -> ! {
185    panic!("{msg}: {error:?}")
186}