1use darling::FromVariant;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{DeriveInput, Fields, Ident, parse_macro_input};
5
6#[proc_macro_derive(HttpStatus, attributes(http))]
7pub fn http_status_impl(input: TokenStream) -> TokenStream {
8 let DeriveInput {
9 ident,
10 data,
11 generics,
12 ..
13 } = parse_macro_input!(input as DeriveInput);
14 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
15
16 let syn::Data::Enum(data) = data else {
17 return quote! {
18 compile_error!("HttpStatus is only supported on enums");
19 }
20 .into();
21 };
22
23 let mut impl_variants = vec![];
24 for variant in data.variants {
25 let attrs = match VariantArgs::from_variant(&variant) {
26 Ok(val) => val,
27 Err(err) => return err.write_errors().into(),
28 };
29
30 let variant_ident = &variant.ident;
31
32 match (attrs.status, attrs.transparent) {
33 (Some(status), None) => {
34 match &variant.fields {
35 Fields::Unit => {
36 impl_variants.push(quote! { #ident::#variant_ident => Self::#status });
37 }
38 Fields::Unnamed(_) => {
39 impl_variants.push(quote! { #ident::#variant_ident(..) => Self::#status })
40 }
41 Fields::Named(_) => {
42 impl_variants
43 .push(quote! { #ident::#variant_ident { .. } => Self::#status });
44 }
45 };
46 }
47 (None, Some(_)) => {
48 match &variant.fields {
49 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
50 impl_variants.push(quote! { #ident::#variant_ident(field) => field.into() })
51 }
52 _ => {
53 return quote! {
54 compile_error!("#[http(transparent)] is only supported on variants with one unnamed field.");
55 }
56 .into();
57 }
58 };
59 }
60 (Some(_), Some(_)) => {
61 return quote! {
62 compile_error!("Either #[http(status = \"...\")] or #[http(transparent)] has to be used.");
63 }
64 .into();
65 }
66 (None, None) => {
67 return quote! {
68 compile_error!("One or more variants are missing an #[http(...)] attribute.");
69 }
70 .into();
71 }
72 }
73 }
74
75 #[allow(unused_mut)]
76 let mut http_crates = Vec::<proc_macro2::TokenStream>::new();
77 #[cfg(feature = "http-02")]
78 http_crates.push(quote! { http_derive::http_02 });
79
80 #[cfg(feature = "http-1")]
81 http_crates.push(quote! { http_derive::http_1 });
82
83 let mut impls = vec![];
84 for http_crate in http_crates {
85 impls.push(quote! {
86 impl #impl_generics From<&#ident #type_generics> for #http_crate::status::StatusCode #where_clause {
87 fn from(value: &#ident #type_generics) -> Self {
88 match value {
89 #( #impl_variants, )*
90 }
91 }
92 }
93 });
94 }
95
96 quote! {
97 #( #impls )*
98 }
99 .into()
100}
101
102#[derive(Debug, FromVariant)]
103#[darling(attributes(http))]
104struct VariantArgs {
105 status: Option<Ident>,
106 transparent: Option<bool>,
107}