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}