Skip to main content

actix_failwrap/
lib.rs

1#![doc = include_str!("../README.md")]
2// TODO: Check Clippy lints
3// TODO: Document each test module for tests
4// TODO: Add security mention in readme.md
5#![deny(missing_docs)]
6#![deny(warnings)]
7#![warn(clippy::pedantic)]
8#![warn(clippy::unwrap_used)]
9
10use proc_macro::TokenStream;
11use syn::parse_macro_input;
12
13use crate::macro_input::error_response::ErrorResponse;
14use crate::macro_input::proof_route::{ProofRouteBody, ProofRouteMeta};
15use crate::macro_output::error_response::error_response_output;
16use crate::macro_output::proof_route::proof_route_output;
17
18mod helpers;
19mod macro_input;
20mod macro_output;
21
22#[cfg(test)]
23mod tests;
24
25/// # `ErrorResponse` Derive Macro
26///
27/// This macro is a helper to implement `Into<actix_web::HttpResponse>`
28/// and `Into<actix_web::Error>` for `thiserror::error` marked enumerables.
29///
30/// Have in mind that nothing really enforces that the enum you apply this on
31/// has also derived `thiserror::error`, but this macro's generation will rely
32/// on you having implemented `Display`, and `thiserror::error` is a convenient
33/// way to implement `Display`.
34///
35/// ## Macro attributes
36///
37/// With `ErrorResponse` you can modify how your your response will look when
38/// returning an error from an endpoint.
39///
40/// **`#[transform_response(function_reference)]`**
41/// You can add this attribute to your enum and pass a static function reference
42/// the function should receive an `HttpResponseBuilder` which is the partially
43/// built response and a `String` as second argument, which is the result of
44/// `<Self as Display>::to_string()` where `Self` is the enum you applied this
45/// function to. The function should return an `HttpResponse` which is what's
46/// going to be used when an error is returned from an endpoint.
47///
48/// **`#[default_status_code(number_or_identifier)]`**
49/// You can add this attribute to your enum and pass or either a number
50/// representing the http error status code like `400` or `500`, or an
51/// identifier such as `BadRequest` or `InternalServerError`. This will set the
52/// status code by default if you don't use `status_code` in any enum variant.
53///
54/// **`#[status_code(number_or_identifier)]`**
55/// Like `default_status_code` you can pass a number or an HTTP status code
56/// identifier and it will be applied to the current enum variant.
57///
58/// By default all status codes will be `InternalServerError` and the enum's
59/// Display will be applied to the response body.
60///
61/// ## Example
62///
63/// ```rust
64/// use actix_failwrap::ErrorResponse;
65/// use thiserror::Error;
66/// use serde_json::json;
67/// use chrono::Utc;
68/// use actix_web::{HttpResponse, HttpResponseBuilder};
69///
70/// fn transformer(mut builder: HttpResponseBuilder, display: String) -> HttpResponse {
71///     builder
72///         .body(json! {
73///             {
74///                 "error": display,
75///                 "date": Utc::now()
76///                     .to_rfc3339()
77///                     .to_string()
78///             }
79///         }.to_string())
80/// }
81///
82/// #[derive(ErrorResponse, Error, Debug)]
83/// #[transform_response(transformer)]
84/// #[default_status_code(InternalServerError)] // this is already by default
85/// enum CustomError {
86///     #[error("This password is already in use by {email}, please chose another.")]
87///     #[status_code(BadRequest)]
88///     PasswordAlreadyInUse { email: String } // :)
89/// }
90///
91/// // This can then be used with the `proof_route` macro.
92/// ```
93#[proc_macro_derive(
94    ErrorResponse,
95    attributes(default_status_code, status_code, transform_response)
96)]
97pub fn error_response(input: TokenStream) -> TokenStream {
98    error_response_output(&parse_macro_input!(input as ErrorResponse)).into()
99}
100
101/// # `proof_route` Attribute Macro
102///
103/// You can replace the `actix_web::{get, post, put, ..}` macros by
104/// `proof_route` with the following syntax `#[proof_route("METHOD /path")]`
105/// resembling to the HTTP standard syntax.
106///
107/// **Before using this macro see [`ErrorResponse`] as you need it to use this**
108///
109/// This macro creates a new `actix_web` route, the syntax is the same as normal
110/// attribute marked routes, except the return type changes to be a `Result<T,
111/// E>` where `T` should implement `::actix_web::Responder` and `E` should
112/// implement `Into<::actix_web::HttpResponse>` which you can implement in your
113/// response type by using [`ErrorResponse`].
114///
115/// Since this thightly integrates with with `thiserror` you can join multiple
116/// error types and use `?` to make your error handling in routes ergonomic.
117///
118/// If you return a custom error not annotated with [`ErrorResponse`] this is
119/// considered undefined behavior, and no support will be given to that.
120///
121/// ## Macro Attributes
122///
123/// **`#[error_override(EnumVariant)]`**
124///
125/// You can also annotate your route extractors with the `error_override`
126/// attribute which expects a variant of the error enumerable being returned.
127/// This will replace any error that may be returned by the collector itself for
128/// a custom error variant instead.
129///
130/// ## Example
131///
132/// ```rust
133/// use actix_failwrap::{ErrorResponse, proof_route};
134/// use thiserror::Error;
135/// use actix_web::web::Json;
136/// use actix_web::HttpResponse;
137/// use serde::Deserialize;
138///
139/// // we should declare an example error enum
140///
141/// #[derive(ErrorResponse, Error, Debug)]
142/// enum CustomError {
143///     #[error("An invalid body was received.")]
144///     InvalidBody,
145///
146///     #[error("Something went wrong.")]
147///     SomeError // by default this is an InternalServerError.
148/// }
149///
150/// #[derive(Deserialize)]
151/// struct CreateAccountData {
152///     email: String,
153///     passowrd: String
154/// }
155///
156/// #[proof_route("POST /account")]
157/// async fn create_account(
158///     // in the case the user sends wrong data the error will be overriden.
159///     #[error_override(InvalidBody)] body: Json<CreateAccountData>
160/// ) -> Result<HttpResponse, CustomError> {
161///     Err(CustomError::SomeError) // we directly return the error here
162/// }
163/// ```
164#[proc_macro_attribute]
165pub fn proof_route(meta: TokenStream, body: TokenStream) -> TokenStream {
166    proof_route_output(
167        &parse_macro_input!(meta as ProofRouteMeta),
168        &parse_macro_input!(body as ProofRouteBody),
169    )
170    .into()
171}