1use proc_macro::TokenStream;
29use proc_macro2::TokenStream as TokenStream2;
30use quote::quote;
31use syn::{Attribute, Data, DeriveInput, Expr, ExprLit, Fields, Ident, Lit, parse_macro_input};
32
33#[proc_macro_derive(Response, attributes(response))]
60pub fn response_derive(input: TokenStream) -> TokenStream {
61 let input = parse_macro_input!(input as DeriveInput);
62 match expand_response_derive(&input) {
63 Ok(tokens) => tokens.into(),
64 Err(err) => err.to_compile_error().into(),
65 }
66}
67
68fn expand_response_derive(input: &DeriveInput) -> syn::Result<TokenStream2> {
69 let name = &input.ident;
70 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
71
72 let data = match &input.data {
73 Data::Enum(data) => data,
74 Data::Struct(_) => {
75 return Err(syn::Error::new_spanned(
76 input,
77 "Response can only be derived for enums",
78 ));
79 }
80 Data::Union(_) => {
81 return Err(syn::Error::new_spanned(
82 input,
83 "Response cannot be derived for unions",
84 ));
85 }
86 };
87
88 let arms: Vec<_> = data
90 .variants
91 .iter()
92 .map(|v| {
93 let attrs = parse_response_attrs(&v.attrs)?;
94 let pattern = build_pattern(name, &v.ident, &v.fields, attrs.transparent);
95
96 let code_expr = if attrs.transparent {
97 quote! { inner.error_code() }
98 } else if let Some(code) = attrs.code {
99 quote! { #code }
100 } else {
101 return Err(syn::Error::new_spanned(
102 v,
103 "Variant must specify an error code with `#[response(code = N)]`.\n\
104 Example: #[response(code = 1001)]",
105 ));
106 };
107
108 let status_expr = if attrs.transparent {
109 quote! { inner.http_status_code() }
110 } else {
111 let status = attrs.status.unwrap_or(200);
112 quote! { #status }
113 };
114
115 Ok((
116 quote! { #pattern => #code_expr },
117 quote! { #pattern => #status_expr },
118 ))
119 })
120 .collect::<syn::Result<Vec<_>>>()?;
121
122 let (code_arms, status_arms): (Vec<_>, Vec<_>) = arms.into_iter().unzip();
123
124 Ok(quote! {
125 impl #impl_generics apiresponse::Response for #name #ty_generics #where_clause {
126 fn error_code(&self) -> u64 {
127 match self {
128 #(#code_arms),*
129 }
130 }
131
132 fn message(&self) -> String {
133 self.to_string()
134 }
135
136 fn http_status_code(&self) -> u16 {
137 match self {
138 #(#status_arms),*
139 }
140 }
141 }
142 })
143}
144
145fn parse_response_attrs(attrs: &[Attribute]) -> syn::Result<ResponseAttrs> {
147 let mut result = ResponseAttrs::default();
148
149 for attr in attrs {
150 if !attr.path().is_ident("response") {
151 continue;
152 }
153
154 attr.parse_nested_meta(|meta| {
155 if meta.path.is_ident("code") {
156 let value: Expr = meta.value()?.parse()?;
157 if let Expr::Lit(ExprLit {
158 lit: Lit::Int(lit), ..
159 }) = value
160 {
161 result.code = Some(lit.base10_parse()?);
162 } else {
163 return Err(meta.error("code must be an integer"));
164 }
165 } else if meta.path.is_ident("status") {
166 let value: Expr = meta.value()?.parse()?;
167 if let Expr::Lit(ExprLit {
168 lit: Lit::Int(lit), ..
169 }) = value
170 {
171 result.status = Some(lit.base10_parse()?);
172 } else {
173 return Err(meta.error("status must be an integer"));
174 }
175 } else if meta.path.is_ident("transparent") {
176 result.transparent = true;
177 }
178 Ok(())
179 })?;
180 }
181
182 Ok(result)
183}
184
185#[derive(Default)]
186struct ResponseAttrs {
187 code: Option<u64>,
188 status: Option<u16>,
189 transparent: bool,
190}
191
192fn build_pattern(
194 enum_name: &Ident,
195 variant_name: &Ident,
196 fields: &Fields,
197 transparent: bool,
198) -> TokenStream2 {
199 match fields {
200 Fields::Unit => quote! { #enum_name::#variant_name },
201 Fields::Unnamed(fields) => {
202 if transparent && fields.unnamed.len() == 1 {
203 quote! { #enum_name::#variant_name(inner) }
204 } else {
205 quote! { #enum_name::#variant_name(..) }
206 }
207 }
208 Fields::Named(_) => {
209 if transparent {
210 quote! { #enum_name::#variant_name { ref inner, .. } }
211 } else {
212 quote! { #enum_name::#variant_name { .. } }
213 }
214 }
215 }
216}