anyhow_http_derive/
lib.rs

1use proc_macro::{self, TokenStream};
2use syn::Error;
3
4mod http_error;
5
6/// Derives a [`From`] implementation for dedicated errors that behave like
7/// [`HttpError`]s
8///
9/// This derive macro is similar to [`thiserror`] just for [`HttpError`]s.
10///
11/// **Note: Currently only enums are supported.**
12///
13/// Dedicated [`HttpError`]-like types are a good fit in places where errors need to be statically
14/// defined and reused. Furthermore, they help make the code more readable and maintainable.
15///
16/// Consider the following example where we would like to define a dedicated error for a specific
17/// error case (request failed). We want the error to behave as a [`HttpError`] because it should
18/// be passed along and eventually produce a specific Http response. In this case we want the
19/// `RequestFailed` variant to emit a error response with status `502` and `reason` `request failed`.
20/// [`FromHttpError`] derives a [`From`] implementation that allows the `CustomError` to be
21/// converted into [`HttpError`] (and `anyhow::Error` consequently) according to the attributes we
22/// define with `#[http_error(..)]`.
23/// ```
24/// # use anyhow::Result;
25/// # use anyhow_http_derive::FromHttpError;
26/// # use bytes::Bytes;
27/// # use http::StatusCode;
28/// # use std::future::Future;
29/// #[derive(FromHttpError)]
30/// enum CustomError {
31///     #[http_error(status(502), reason("request failed"))]
32///     RequestFailed,
33/// }
34///
35/// async fn process_request(req: impl Future<Output = Result<Bytes>>) -> anyhow::Result<Bytes> {
36///     let resp = req
37///         .await
38///         .map_err(|_| CustomError::RequestFailed)?;
39///
40///     Ok(resp)
41/// }
42/// ```
43///
44/// Supported arguments to the `#[http_error(..)]` attribute are `status`, `reason` and `data`.
45/// `data` allows to set one or more key-value pairs to the [`HttpError`]'s data. Values may
46/// be literals and any valid expressions.
47/// ```
48/// # use anyhow_http_derive::FromHttpError;
49/// #[derive(FromHttpError)]
50/// enum CustomError {
51///     #[http_error(status(502), data(code = 1234, ctx = "more ctx"))]
52///     RequestFailed,
53/// }
54/// ```
55///
56/// Similar to [`thiserror`] a `#[from]` attribute is provided to automatically generate a
57/// [`From`] implementation for the specific variant. `#[from]` also sets the source of the
58/// resulting [`HttpError`]. If only the source should be set without generating a [`From`]
59/// implementation `#[source]` should be set.
60/// ```
61/// # use anyhow_http_derive::FromHttpError;
62/// #[derive(FromHttpError)]
63/// enum CustomError {
64///     #[http_error(status(502), reason("request failed"))]
65///     RequestFailed(#[from] anyhow::Error),
66/// }
67/// ```
68///
69/// Formatting on the `reason(..)` and `data(..)` attribute is supported on both named and unnamed
70/// variants.
71/// ```
72/// # use anyhow_http_derive::FromHttpError;
73/// #[derive(FromHttpError)]
74/// enum CustomError {
75///     #[http_error(status(502), reason("named: {ctx}"))]
76///     Named { ctx: String },
77///     #[http_error(status(502), reason("unnamed: {0}"))]
78///     Unnamed(String),
79/// }
80/// ```
81///
82/// `transparent` allows to forward the source error as-is. It required either `#[source]` or
83/// `#[from]`.
84/// ```
85/// # use anyhow_http_derive::FromHttpError;
86/// #[derive(FromHttpError)]
87/// enum CustomError {
88///     #[http_error(transparent)]
89///     Inner(#[source] anyhow::Error)
90/// }
91/// ```
92///
93/// [`From`]: std::convert::From
94/// [`HttpError`]: https://docs.rs/anyhow-http/latest/anyhow_http/struct.HttpError.html
95/// [`thiserror`]: https://docs.rs/thiserror/latest/thiserror/#derives
96#[proc_macro_derive(FromHttpError, attributes(http_error, from, source, data))]
97pub fn derive_from_http_error(input: TokenStream) -> TokenStream {
98    syn::parse(input)
99        .and_then(http_error::expand_http_error)
100        .unwrap_or_else(Error::into_compile_error)
101        .into()
102}