extern crate proc_macro;
use itertools::Itertools;
use manyhow::manyhow;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data::Enum, DeriveInput, Error, Expr, parse2, spanned::Spanned};
#[manyhow(proc_macro_derive(StatusCode))]
pub fn derive_status_code(input: TokenStream) -> syn::Result<TokenStream> {
let input: DeriveInput = parse2(input)?;
let enum_name = &input.ident;
let enum_span = input.span();
let default = input
.attrs
.iter()
.find_map(|attr| {
if attr.path().is_ident("derive") {
match attr.parse_args::<Expr>() {
Ok(attr) => match attr {
Expr::Path(path) => path.path.is_ident("StatusCode").then_some(Ok(None)),
Expr::Call(call) => {
if let Expr::Path(path) = *call.func {
path.path.is_ident("StatusCode").then_some(Ok(call
.args
.first()
.cloned()
.map(Ok)))
} else {
None
}
}
_ => Some(Err(Error::new(attr.span(), "invalid attribute arguments"))),
},
Err(error) => Some(Err(error)),
}
} else {
None
}
})
.ok_or(Error::new(enum_span, "missing derive macro"))??;
if let Enum(data) = input.data {
data.variants
.iter()
.map(|variant| {
if let Some(value) = variant
.attrs
.iter()
.find_map(|attr| {
attr.path()
.is_ident("status_code")
.then_some(attr.parse_args::<Expr>())
})
.or(default.clone())
{
let value = value?;
Ok(quote! {
#enum_name::#variant.ident => #value
})
} else {
Err(Error::new(variant.span(), "variant is missing status_code"))
}
})
.process_results(|iter| {
iter.tree_reduce(|a, b| {
quote! {
#a
#b
}
})
})?
.map(|contents| {
quote! {
impl enum_status_code::StatusCode for #enum_name {
fn status_code(&self) -> enum_status_code::http::StatusCode {
match self {
#contents
}
}
}
}
})
.ok_or(Error::new(enum_span, "expcted enum body"))
} else {
Err(Error::new(enum_span, "expected enum"))
}
}