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