enum_status_code_macros/
lib.rs1extern crate proc_macro;
2
3use std::mem::take;
4
5use itertools::Itertools;
6use manyhow::manyhow;
7use proc_macro2::TokenStream;
8use quote::{ToTokens, quote};
9use syn::{Attribute, Error, Expr, ItemEnum, TypeGenerics, Variant, parse2, spanned::Spanned};
10
11#[manyhow(proc_macro_attribute)]
12pub fn status_code(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
13 let mut item: ItemEnum = parse2(item)?;
15 let enum_name = &item.ident;
16 let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
17 let enum_span = item.span();
18
19 let default = (!attr.is_empty()).then(|| parse2(attr));
21
22 item.variants
23 .iter_mut()
25 .map(parse_variant(default, enum_name, &ty_generics))
27 .process_results(|iter| {
29 iter.reduce(|a, b| {
30 quote! {
31 #a
32 #b
33 }
34 })
35 })?
36 .map(|contents| {
38 quote! {
39 #item
40
41 impl #impl_generics enum_status_code::AsStatusCode for #enum_name #ty_generics #where_clause {
42 fn as_status_code(&self) -> enum_status_code::http::StatusCode {
43 match self {
44 #contents
45 }
46 }
47 }
48 }
49 })
50 .ok_or(Error::new(enum_span, "expcted enum body"))
52}
53
54fn parse_variant(
55 default: Option<Result<Expr, Error>>,
56 enum_name: impl ToTokens,
57 ty_generics: &TypeGenerics,
58) -> impl Fn(&mut Variant) -> Result<TokenStream, Error> {
59 move |variant| {
60 let (pattern_attribute, code_attribute) = get_attributes(variant)?;
62
63 code_attribute
64 .as_ref()
65 .map(Attribute::parse_args::<Expr>)
67 .or(default.clone())
69 .ok_or(Error::new(
71 variant.span(),
72 "variant is missing status code and no default set",
73 ))
74 .map(parse_attribute(
76 variant,
77 ty_generics,
78 &pattern_attribute,
79 &enum_name,
80 ))?
81 }
82}
83
84fn get_attributes(variant: &mut Variant) -> Result<(Option<Attribute>, Option<Attribute>), Error> {
85 let mut attributes = (None, None);
87 for attribute in take(&mut variant.attrs) {
89 let path = attribute.path();
91 if path.is_ident("pattern") {
93 if let Some(replaced) = attributes.0.replace(attribute) {
94 return Err(Error::new(replaced.span(), "duplicate pattern attribute"));
95 }
96 } else if path.is_ident("code") {
97 if let Some(replaced) = attributes.1.replace(attribute) {
98 return Err(Error::new(replaced.span(), "duplicate code attribute"));
99 }
100 }
101 if attributes.0.is_some() && attributes.1.is_some() {
103 break;
104 };
105 }
106 Ok(attributes)
107}
108
109fn parse_attribute(
110 variant: &Variant,
111 ty_generics: &TypeGenerics,
112 pattern_attribute: &Option<Attribute>,
113 enum_name: impl ToTokens,
114) -> impl Fn(Result<Expr, Error>) -> Result<TokenStream, Error> {
115 move |value| {
116 let variant_name = &variant.ident;
118 let expression = value?.into_token_stream();
119 let turbofish = ty_generics.as_turbofish();
120
121 let pattern = if !matches!(variant.fields, syn::Fields::Unit)
123 && let Some(pattern) = pattern_attribute
125 {
126 Some(pattern.parse_args::<TokenStream>()?)
128 } else {
129 None
130 };
131 Ok(quote! {
133 #enum_name #turbofish :: #variant_name #pattern => #expression,
134 })
135 }
136}