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()
}