into_response/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, DeriveInput};
4use utils::parse_status_code::parse_status_code;
5
6mod utils;
7
8/// # Examples
9///
10/// ### Simple usage
11/// ```rust
12/// use into_response::IntoResponse;
13///
14/// #[derive(IntoResponse)]
15/// struct MyResponse {
16///     message: String,
17/// }
18///
19/// let response = MyResponse {
20///     message: "Hello, world!".to_string(),
21/// };
22///
23/// let response = response.into_response();
24///
25/// assert_eq!(response.status(), axum::http::StatusCode::OK);
26/// ```
27///
28/// ---
29///
30/// ### You can also specify a custom status code using the `into_response` attribute
31/// ```rust
32/// use into_response::IntoResponse;
33///
34/// #[derive(IntoResponse)]
35/// #[into_response(status = 201)]
36/// struct MyResponse2 {
37///     message: String,
38/// }
39///
40/// let response = MyResponse2 {
41///     message: "Hello, world!".to_string(),
42/// };
43///
44/// let response = response.into_response();
45///
46/// assert_eq!(response.status(), axum::http::StatusCode::CREATED);
47/// ```
48#[proc_macro_derive(IntoResponse, attributes(into_response))]
49pub fn into_response_derive(input: TokenStream) -> TokenStream {
50    let input = parse_macro_input!(input as DeriveInput);
51    let name = &input.ident;
52    let generics = &input.generics;
53    let attrs = &input.attrs;
54
55    let custom_status_code = match parse_status_code(attrs) {
56        Ok(code) => code,
57        Err(e) => return e.to_compile_error().into(),
58    };
59
60    let status_code_tokens = if let Some(code) = custom_status_code {
61        quote! { Some(axum::http::StatusCode::from_u16(#code).unwrap())}
62    } else {
63        quote! { None }
64    };
65
66    let mut generics_with_bounds = generics.clone();
67    for type_param in &mut generics_with_bounds.type_params_mut() {
68        type_param.bounds.push(syn::parse_quote!(serde::Serialize));
69    }
70
71    let (impl_generics, ty_generics, where_clause) = generics_with_bounds.split_for_impl();
72
73    let expanded = quote! {
74        impl #impl_generics axum::response::IntoResponse for #name #ty_generics #where_clause {
75            fn into_response(self) -> axum::response::Response {
76                let json = axum::Json(self);
77                match #status_code_tokens {
78                    Some(status_code) => {
79                        let response: (axum::http::StatusCode, axum::Json<Self>) = (status_code, json);
80                        response.into_response()
81                    },
82                    None => json.into_response(),
83                }
84            }
85        }
86    };
87
88    expanded.into()
89}