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}