1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
use proc_macro::{self, TokenStream};
use syn::Error;

mod http_error;

/// Derives a [`From`] implementation for dedicated errors that behave like
/// [`HttpError`]s
///
/// This derive macro is similar to [`thiserror`] just for [`HttpError`]s.
///
/// **Note: Currently only enums are supported.**
///
/// Dedicated [`HttpError`]-like types are a good fit in places where errors need to be statically
/// defined and reused. Furthermore, they help make the code more readable and maintainable.
///
/// Consider the following example where we would like to define a dedicated error for a specific
/// error case (request failed). We want the error to behave as a [`HttpError`] because it should
/// be passed along and eventually produce a specific Http response. In this case we want the
/// `RequestFailed` variant to emit a error response with status `502` and `reason` `request failed`.
/// [`FromHttpError`] derives a [`From`] implementation that allows the `CustomError` to be
/// converted into [`HttpError`] (and `anyhow::Error` consequently) according to the attributes we
/// define with `#[http_error(..)]`.
/// ```
/// # use anyhow::Result;
/// # use anyhow_http_derive::FromHttpError;
/// # use bytes::Bytes;
/// # use http::StatusCode;
/// # use std::future::Future;
/// #[derive(FromHttpError)]
/// enum CustomError {
///     #[http_error(status(502), reason("request failed"))]
///     RequestFailed,
/// }
///
/// async fn process_request(req: impl Future<Output = Result<Bytes>>) -> anyhow::Result<Bytes> {
///     let resp = req
///         .await
///         .map_err(|_| CustomError::RequestFailed)?;
///
///     Ok(resp)
/// }
/// ```
///
/// Supported arguments to the `#[http_error(..)]` attribute are `status`, `reason` and `data`.
/// `data` allows to set one or more key-value pairs to the [`HttpError`]'s data.
/// ```
/// # use anyhow_http_derive::FromHttpError;
/// #[derive(FromHttpError)]
/// enum CustomError {
///     #[http_error(status(502), data(code = 1234, ctx = "more ctx"))]
///     RequestFailed,
/// }
/// ```
///
/// Similar to [`thiserror`] a `#[from]` attribute is provided to automatically generate a
/// [`From`] implementation for the specific variant. `#[from]` also sets the source of the
/// resulting [`HttpError`]. If only the source should be set without generating a [`From`]
/// implementation `#[source]` should be set.
/// ```
/// # use anyhow_http_derive::FromHttpError;
/// #[derive(FromHttpError)]
/// enum CustomError {
///     #[http_error(status(502), reason("request failed"))]
///     RequestFailed(#[from] anyhow::Error),
/// }
/// ```
///
/// Formatting on the `reason(..)` and `data(..)` attribute is supported on both named and unnamed
/// variants.
/// ```
/// # use anyhow_http_derive::FromHttpError;
/// #[derive(FromHttpError)]
/// enum CustomError {
///     #[http_error(status(502), reason("named: {ctx}"))]
///     Named { ctx: String },
///     #[http_error(status(502), reason("unnamed: {0}"))]
///     Unnamed(String),
/// }
/// ```
///
/// `transparent` allows to forward the source error as-is. It required either `#[source]` or
/// `#[from]`.
/// ```
/// # use anyhow_http_derive::FromHttpError;
/// #[derive(FromHttpError)]
/// enum CustomError {
///     #[http_error(transparent)]
///     Inner(#[source] anyhow::Error)
/// }
/// ```
///
/// [`From`]: std::convert::From
/// [`HttpError`]: https://docs.rs/anyhow-http/latest/anyhow_http/struct.HttpError.html
/// [`thiserror`]: https://docs.rs/thiserror/latest/thiserror/#derives
#[proc_macro_derive(FromHttpError, attributes(http_error, from, source, data))]
pub fn derive_from_http_error(input: TokenStream) -> TokenStream {
    syn::parse(input)
        .and_then(http_error::expand_http_error)
        .unwrap_or_else(Error::into_compile_error)
        .into()
}