actix_web_thiserror/lib.rs
1//! # actix-web-thiserror
2//!
3//! [](https://github.com/enzious/actix-web-thiserror/blob/master/LICENSE.md)
4//! [](https://github.com/enzious/actix-web-thiserror/graphs/contributors)
5//! [](https://github.com/enzious/actix-web-thiserror)
6//! [](https://crates.io/crates/actix-web-thiserror)
7//!
8//! A crate that extends the [thiserror] crate functionality to automatically
9//! return a proper [actix-web] response.
10//!
11//! ## Documentation
12//!
13//! - [API Documentation](https://docs.rs/actix-web-thiserror)
14//!
15//! ## Error definition
16//! ```rust
17//! use actix_web_thiserror::ResponseError;
18//! use thiserror::Error;
19//!
20//! #[derive(Debug, Error, ResponseError)]
21//! pub enum Base64ImageError {
22//! #[response(reason = "INVALID_IMAGE_FORMAT")]
23//! #[error("invalid image format")]
24//! InvalidImageFormat,
25//! #[response(reason = "INVALID_STRING")]
26//! #[error("invalid string")]
27//! InvalidString,
28//! }
29//! ```
30//!
31//! ## Error implementation
32//! ```rust
33//! # use actix_web_thiserror::ResponseError;
34//! # use actix_web::*;
35//! # use thiserror::Error;
36//! #
37//! # #[derive(Debug, Error, ResponseError)]
38//! # pub enum Base64ImageError {
39//! # #[response(reason = "INVALID_IMAGE_FORMAT")]
40//! # #[error("invalid image format")]
41//! # InvalidImageFormat,
42//! # }
43//! #
44//! pub async fn error_test() -> Result<HttpResponse, Error> {
45//! Err(Base64ImageError::InvalidImageFormat)?
46//! }
47//! ```
48//!
49//! ## Error response
50//!
51//! The `reason` is a string that may be given to the client in some form to explain
52//! the error, if appropriate. Here it is as an enum that can be localized.
53//!
54//! **Note:** This response has been formatted by a [`ResponseTransform`][response_transform].
55//!
56//! ```json
57//! {
58//! "result": 0,
59//! "reason": "INVALID_IMAGE_FORMAT"
60//! }
61//! ```
62//!
63//! ## Error logging
64//!
65//! The error text automatically prints to the log when the error is returned out
66//! through a http response.
67//!
68//! ```text
69//! Apr 23 02:19:35.211 ERROR Response error: invalid image format
70//! Base64ImageError(InvalidImageFormat), place: example/src/handler.rs:5 example::handler
71//! ```
72//!
73//! [thiserror]: https://docs.rs/thiserror
74//! [actix-web]: https://docs.rs/actix-web
75//! [response_transform]: crate::ResponseTransform
76
77use std::sync::Arc;
78
79use actix_web::HttpResponse;
80use arc_swap::ArcSwap;
81use lazy_static::lazy_static;
82
83/// A trait that transforms information about an [thiserror] error into
84/// a response as desired by the implementor.
85///
86/// [thiserror]: https://docs.rs/thiserror
87#[allow(unused)]
88pub trait ResponseTransform {
89 fn transform(
90 &self,
91 name: &str,
92 err: &dyn std::error::Error,
93 status_code: actix_web::http::StatusCode,
94 reason: Option<serde_json::Value>,
95 _type: Option<String>,
96 details: Option<serde_json::Value>,
97 ) -> HttpResponse {
98 actix_web::HttpResponse::build(status_code).finish()
99 }
100
101 fn default_error_status_code(&self) -> actix_web::http::StatusCode {
102 actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
103 }
104}
105
106struct ReflexiveTransform;
107
108impl ResponseTransform for ReflexiveTransform {}
109
110lazy_static! {
111 static ref RESPONSE_TRANSFORM: ArcSwap<Box<dyn ResponseTransform + Sync + Send>> =
112 ArcSwap::from(Arc::new(Box::new(ReflexiveTransform {}) as _));
113}
114
115/// Sets the default global transform for errors into responses.
116pub fn set_global_transform(transform: impl ResponseTransform + Sync + Send + 'static) {
117 RESPONSE_TRANSFORM.swap(Arc::new(Box::new(transform)));
118}
119
120#[doc(hidden)]
121pub fn apply_global_transform(
122 name: &str,
123 err: &dyn std::error::Error,
124 status_code: actix_web::http::StatusCode,
125 reason: Option<serde_json::Value>,
126 _type: Option<String>,
127 details: Option<serde_json::Value>,
128) -> HttpResponse {
129 ResponseTransform::transform(
130 (**RESPONSE_TRANSFORM.load()).as_ref(),
131 name,
132 err,
133 status_code,
134 reason,
135 _type,
136 details,
137 )
138}
139
140#[doc(hidden)]
141pub fn default_global_error_status_code() -> actix_web::http::StatusCode {
142 ResponseTransform::default_error_status_code((**RESPONSE_TRANSFORM.load()).as_ref())
143}
144
145#[doc(hidden)]
146pub trait ThiserrorResponse {
147 fn status_code(&self) -> Option<actix_web::http::StatusCode> {
148 None
149 }
150
151 fn reason(&self) -> Option<Option<serde_json::Value>> {
152 None
153 }
154
155 fn _type(&self) -> Option<Option<String>> {
156 None
157 }
158
159 fn details(&self) -> Option<Option<serde_json::Value>> {
160 None
161 }
162}
163
164#[allow(unused_imports)]
165#[macro_use]
166extern crate actix_web_thiserror_derive;
167
168/// The derive implementation for extending [thiserror]
169///
170/// [thiserror]: https://docs.rs/thiserror
171pub use actix_web_thiserror_derive::ResponseError;