actix_web_thiserror/
lib.rs

1//! # actix-web-thiserror
2//!
3//! [![License](https://img.shields.io/github/license/enzious/actix-web-thiserror)](https://github.com/enzious/actix-web-thiserror/blob/master/LICENSE.md)
4//! [![Contributors](https://img.shields.io/github/contributors/enzious/actix-web-thiserror)](https://github.com/enzious/actix-web-thiserror/graphs/contributors)
5//! [![GitHub Repo stars](https://img.shields.io/github/stars/enzious/actix-web-thiserror?style=social)](https://github.com/enzious/actix-web-thiserror)
6//! [![crates.io](https://img.shields.io/crates/v/actix-web-thiserror.svg)](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;