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