http_derive_impl/
lib.rs

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}