anyhow-http-derive 0.4.4

Derive macros for HTTP errors
Documentation
use anyhow_http::{http_error, HttpError};
use anyhow_http_derive::FromHttpError;

const DATA_KEY: &str = "key";

#[derive(Debug, FromHttpError)]
enum CustomError {
    #[http_error(status(400), reason("reason {0}"))]
    From(#[from] anyhow::Error),
    #[http_error(status(400), reason("reason {count}"))]
    NamedWithSource {
        count: u64,
        #[source]
        source: anyhow::Error,
    },
    #[http_error(
        status(400),
        reason("reason {0}"),
        data(code = 1234, info = "info {0}")
    )]
    UnamedWithSource(u64, #[source] anyhow::Error),
    #[http_error(transparent)]
    Transparent(#[source] anyhow::Error),
    #[http_error(status(404), reason("reason {0}"), data({DATA_KEY} = ErrorCode::Invalid as u32))]
    ExprData(String),
}

#[derive(Debug)]
#[repr(u32)]
enum ErrorCode {
    Invalid = 1234,
}

#[test]
fn derive_enum_from() {
    let res: Result<(), CustomError> = (|| {
        Err(anyhow::anyhow!("source"))?;
        unreachable!()
    })();
    let err: HttpError = res.unwrap_err().into();

    assert_eq!(err.status_code(), 400);
    assert_eq!(err.reason(), Some("reason source".into()));
    assert_eq!(
        err.source().map(ToString::to_string),
        Some("CustomError::From".into())
    );
}

#[test]
fn derive_enum_named_with_source() {
    let source = anyhow::anyhow!("source");
    let source = source.context("outer");
    let err: HttpError = CustomError::NamedWithSource {
        source,
        count: 1234,
    }
    .into();

    assert_eq!(err.status_code(), 400);
    assert_eq!(err.reason(), Some("reason 1234".into()));
    assert_eq!(
        err.source().map(ToString::to_string),
        Some("CustomError::NamedWithSource { count: 1234 }".into())
    );
    assert_eq!(
        format!("{err:#}"),
        "HttpError(400) reason 1234: CustomError::NamedWithSource { count: 1234 }: outer: source"
    );
}

#[test]
fn derive_enum_unnamed_with_source() {
    let err: HttpError = CustomError::UnamedWithSource(1234, anyhow::anyhow!("source")).into();

    assert_eq!(err.status_code(), 400);
    assert_eq!(err.reason(), Some("reason 1234".into()));
    assert_eq!(err.get("code"), Some(1234));
    assert_eq!(err.get("info"), Some("info 1234".to_string()));
    assert_eq!(
        err.source().map(ToString::to_string),
        Some("CustomError::UnamedWithSource(1234)".into())
    );
}

#[test]
fn derive_enum_transparent() {
    let err: HttpError = CustomError::Transparent(http_error!(BAD_REQUEST, "bad request")).into();

    assert_eq!(err.status_code(), 400);
    assert_eq!(err.reason(), Some("bad request".into()));
    assert!(err.source().is_none());
}

#[test]
fn derive_enum_expr_data() {
    let err: HttpError = CustomError::ExprData("expr data".into()).into();

    assert_eq!(err.status_code(), 404);
    assert_eq!(err.get(DATA_KEY), Some(1234));
    assert_eq!(err.reason(), Some("reason expr data".into()));
    assert_eq!(
        err.source().map(ToString::to_string),
        Some("CustomError::ExprData(\"expr data\")".into()),
    );
}