explicit_error_http/lib.rs
1//! Built on top of [`explicit-error`](https://crates.io/crates/explicit-error), it provides idiomatic tools to manage errors that generate an HTTP response.
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//! - Explicitly mark any error wrapped in a [Result] as a [Bug]. A backtrace is captured and a 500 Internal Server HTTP response generated.
6//! - A derive macro [HttpError](derive::HttpError) to easily declare how enum or struct errors transform into an [Error], i.e. defines the generated HTTP response.
7//! - Inline transformation of any errors wrapped in a [Result] into an [Error].
8//! - Add context to errors to help debug.
9//! - Monitor errors before they are transformed into proper HTTP responses. The implementation is different depending on the web framework used, to have more details refer to the `Web frameworks` section.
10//!
11//! # A tour of explicit-error-http
12//!
13//! The cornerstone of the library is the [Error] type. Use `Result<T, explicit_error_http::Error>`, or equivalently `explicit_error_http::Result<T>`, as the return type of any faillible function returning errors that convert to an HTTP response.
14//! Usually, it is mostly functions either called by handlers or middlewares.
15//!
16//! ## Inline
17//!
18//! In the body of the function you can explicitly turn errors into HTTP response using [HttpError] or marking them as [Bug].
19//!
20//! ```rust
21//! use actix_web::http::StatusCode;
22//! use problem_details::ProblemDetails;
23//! use http::Uri;
24//! use explicit_error_http::{prelude::*, HttpError, Result, Bug};
25//! // Import the prelude to enable functions on std::result::Result
26//!
27//! fn business_logic() -> Result<()> {
28//! Err("error message").bug()?;
29//!
30//! Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"))
31//! .bug_with_source()?; // Same behavior as bug() but capture the wrapped std::error::Error as a source
32//!
33//! if 1 > 2 {
34//! Err(Bug::new()
35//! .with_context("Usefull context to help debug."))?;
36//! }
37//!
38//! Err(42).map_err(|_|
39//! HttpError::new(
40//! StatusCode::BAD_REQUEST,
41//! ProblemDetails::new()
42//! .with_type(Uri::from_static("/errors/business-logic"))
43//! .with_title("Informative feedback for the user."),
44//! )
45//! )?;
46//!
47//! Ok(())
48//! }
49//!```
50//!
51//! Note: The crate [problem_details] is used as an example for the HTTP response body. You can, of course, use whatever you would like that implements [Serialize](serde::Serialize).
52//!
53//! ## Enum and struct
54//!
55//! Domain errors are often represented as enum or struct as they are raised in different places.
56//! To easily enable the conversion to [Error] use the [HttpError](derive::HttpError) derive and implement `From<&MyError> for HttpError`.
57//!
58//! ```rust
59//! use actix_web::http::StatusCode;
60//! use problem_details::ProblemDetails;
61//! use http::Uri;
62//! use explicit_error_http::{prelude::*, Result, derive::HttpError, HttpError};
63//!
64//! #[derive(HttpError, Debug)]
65//! enum MyError {
66//! Foo,
67//! }
68//!
69//! impl From<&MyError> for HttpError {
70//! fn from(value: &MyError) -> Self {
71//! match value {
72//! MyError::Foo => HttpError::new(
73//! StatusCode::BAD_REQUEST,
74//! ProblemDetails::new()
75//! .with_type(Uri::from_static("/errors/my-domain/foo"))
76//! .with_title("Foo format incorrect.")
77//! ),
78//! }
79//! }
80//! }
81//!
82//! fn business_logic() -> Result<()> {
83//! Err(MyError::Foo)?;
84//!
85//! Ok(())
86//! }
87//! ```
88//!
89//! Note: The [HttpError](derive::HttpError) derive implements the conversion to [Error], the impl of [Display](std::fmt::Display) (json format) and [std::error::Error].
90//!
91//! # Pattern matching
92//!
93//! 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.
94//! 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_http::Result<T>`.
95//!
96//! ```rust
97//! # use actix_web::http::StatusCode;
98//! # use http::Uri;
99//! # use problem_details::ProblemDetails;
100//! # use explicit_error_http::{prelude::*, HttpError, Result, derive::HttpError};
101//! #[derive(HttpError, Debug)]
102//! enum MyError {
103//! Foo,
104//! Bar,
105//! }
106//!
107//! # impl From<&MyError> for HttpError {
108//! # fn from(value: &MyError) -> Self {
109//! # match value {
110//! # MyError::Foo | MyError::Bar => HttpError::new(
111//! # StatusCode::BAD_REQUEST,
112//! # ProblemDetails::new()
113//! # .with_type(Uri::from_static("/errors/my-domain/foo"))
114//! # .with_title("Foo format incorrect.")
115//! # ),
116//! # }
117//! # }
118//! # }
119//!
120//! fn handler() -> Result<()> {
121//! let err: Result<()> = Err(MyError::Foo)?;
122//!
123//! // Do the map if the source's type of the Error is MyError
124//! err.try_map_on_source(|e| {
125//! match e {
126//! MyError::Foo => HttpError::new(
127//! StatusCode::FORBIDDEN,
128//! ProblemDetails::new()
129//! .with_type(Uri::from_static("/errors/forbidden"))
130//! ),
131//! MyError::Bar => HttpError::new(
132//! StatusCode::UNAUTHORIZED,
133//! ProblemDetails::new()
134//! .with_type(Uri::from_static("/errors/unauthorized"))
135//! ),
136//! }
137//! })?;
138//!
139//! Ok(())
140//! }
141//! ```
142//!
143//! Note: under the hood [try_map_on_source](explicit_error::ResultError::try_map_on_source) perform some downcasting.
144//!
145//! ## Web frameworks
146//!
147//! explicit-error-http integrates well with most popular web frameworks by providing a feature flag for each of them.
148//!
149//! ### Actix web
150//!
151//! The type [Error] cannot directly be used as handlers or middlewares returned [Err] variant. A dedicated type is required.
152//! The easiest implementation is to declare a [Newtype](https://doc.rust-lang.org/rust-by-example/generics/new_types.html),
153//! derive it with the [HandlerError] and implement the [HandlerError] trait.
154//!
155//! ```rust
156//! # use actix_web::{App, HttpResponse, HttpServer, get};
157//! # use env_logger::Env;
158//! # use explicit_error_http::{Bug, Error, HandlerError, derive::HandlerError};
159//! # use log::{debug, error};
160//! # use problem_details::ProblemDetails;
161//! # use serde::Serialize;
162//! #[derive(HandlerError)]
163//! struct MyHandlerError(Error);
164//!
165//! impl HandlerError for MyHandlerError {
166//! // Used by the derive for conversion
167//! fn from_http_error(value: Error) -> Self {
168//! MyHandlerError(value)
169//! }
170//!
171//! // Set-up monitoring and your custom HTTP response body for bugs
172//! fn public_bug_response(bug: &Bug) -> impl Serialize {
173//! #[cfg(debug_assertions)]
174//! error!("{bug}");
175//!
176//! #[cfg(not(debug_assertions))]
177//! error!("{}", serde_json::json!(bug));
178//!
179//! ProblemDetails::new()
180//! .with_type(http::Uri::from_static("/errors/internal-server-error"))
181//! .with_title("Internal server error")
182//! }
183//!
184//! fn http_error(&self) -> &Error {
185//! &self.0
186//! }
187//!
188//! // Monitor domain variant of your errors
189//! fn on_domain_response(error: &explicit_error_http::DomainError) {
190//! if error.output.http_status_code.as_u16() < 500 {
191//! debug!("{error}");
192//! } else {
193//! error!("{error}");
194//! }
195//! }
196//! }
197//!
198//! #[get("/my-handler")]
199//! async fn my_handler() -> Result<HttpResponse, MyHandlerError> {
200//! Ok(HttpResponse::Ok().finish())
201//! }
202//! ```
203#[cfg(feature = "actix-web")]
204mod actix;
205mod domain;
206mod error;
207mod handler;
208
209pub use domain::*;
210pub use error::*;
211pub use handler::*;
212
213/// Re-import from [explicit_error] crate.
214pub use explicit_error::Bug;
215
216pub type Error = explicit_error::Error<DomainError>;
217pub type Result<T> = std::result::Result<T, explicit_error::Error<DomainError>>;
218
219pub mod prelude {
220 pub use explicit_error::prelude::*;
221}
222
223pub mod derive {
224 pub use explicit_error_derive::HandlerError;
225 pub use explicit_error_derive::HttpError;
226}