rustapi_macros/
lib.rs

1//! Procedural macros for RustAPI
2//!
3//! This crate provides the attribute macros used in RustAPI:
4//!
5//! - `#[rustapi::main]` - Main entry point macro
6//! - `#[rustapi::get("/path")]` - GET route handler
7//! - `#[rustapi::post("/path")]` - POST route handler
8//! - `#[rustapi::put("/path")]` - PUT route handler
9//! - `#[rustapi::patch("/path")]` - PATCH route handler
10//! - `#[rustapi::delete("/path")]` - DELETE route handler
11
12use proc_macro::TokenStream;
13use quote::quote;
14use syn::{parse_macro_input, ItemFn, LitStr};
15
16/// Main entry point macro for RustAPI applications
17///
18/// This macro wraps your async main function with the tokio runtime.
19///
20/// # Example
21///
22/// ```rust,ignore
23/// use rustapi_rs::prelude::*;
24///
25/// #[rustapi::main]
26/// async fn main() -> Result<()> {
27///     RustApi::new()
28///         .mount(hello)
29///         .run("127.0.0.1:8080")
30///         .await
31/// }
32/// ```
33#[proc_macro_attribute]
34pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
35    let input = parse_macro_input!(item as ItemFn);
36
37    let attrs = &input.attrs;
38    let vis = &input.vis;
39    let sig = &input.sig;
40    let block = &input.block;
41
42    let expanded = quote! {
43        #(#attrs)*
44        #[::tokio::main]
45        #vis #sig {
46            #block
47        }
48    };
49
50    TokenStream::from(expanded)
51}
52
53/// Internal helper to generate route handler macros
54fn generate_route_handler(method: &str, attr: TokenStream, item: TokenStream) -> TokenStream {
55    let path = parse_macro_input!(attr as LitStr);
56    let input = parse_macro_input!(item as ItemFn);
57
58    let fn_name = &input.sig.ident;
59    let fn_vis = &input.vis;
60    let fn_attrs = &input.attrs;
61    let fn_async = &input.sig.asyncness;
62    let fn_inputs = &input.sig.inputs;
63    let fn_output = &input.sig.output;
64    let fn_block = &input.block;
65    let fn_generics = &input.sig.generics;
66    
67    let path_value = path.value();
68    
69    // Generate a companion module with route info
70    let route_fn_name = syn::Ident::new(
71        &format!("{}_route", fn_name),
72        fn_name.span()
73    );
74    
75    // Pick the right route helper function based on method
76    let route_helper = match method {
77        "GET" => quote!(::rustapi_rs::get_route),
78        "POST" => quote!(::rustapi_rs::post_route),
79        "PUT" => quote!(::rustapi_rs::put_route),
80        "PATCH" => quote!(::rustapi_rs::patch_route),
81        "DELETE" => quote!(::rustapi_rs::delete_route),
82        _ => quote!(::rustapi_rs::get_route),
83    };
84
85    // Extract metadata from attributes to chain builder methods
86    let mut chained_calls = quote!();
87    
88    for attr in fn_attrs {
89        // Check for tag, summary, description
90        // Use loose matching on the last segment to handle crate renaming or fully qualified paths
91        if let Some(ident) = attr.path().segments.last().map(|s| &s.ident) {
92            let ident_str = ident.to_string();
93            if ident_str == "tag" {
94                if let Ok(lit) = attr.parse_args::<LitStr>() {
95                    let val = lit.value();
96                    chained_calls = quote! { #chained_calls .tag(#val) };
97                }
98            } else if ident_str == "summary" {
99                if let Ok(lit) = attr.parse_args::<LitStr>() {
100                    let val = lit.value();
101                    chained_calls = quote! { #chained_calls .summary(#val) };
102                }
103            } else if ident_str == "description" {
104                if let Ok(lit) = attr.parse_args::<LitStr>() {
105                    let val = lit.value();
106                    chained_calls = quote! { #chained_calls .description(#val) };
107                }
108            }
109        }
110    }
111
112    let expanded = quote! {
113        // The original handler function
114        #(#fn_attrs)*
115        #fn_vis #fn_async fn #fn_name #fn_generics (#fn_inputs) #fn_output #fn_block
116        
117        // Route info function - creates a Route for this handler
118        #[doc(hidden)]
119        #fn_vis fn #route_fn_name() -> ::rustapi_rs::Route {
120            #route_helper(#path_value, #fn_name)
121                #chained_calls
122        }
123    };
124
125    TokenStream::from(expanded)
126}
127
128/// GET route handler macro
129///
130/// # Example
131///
132/// ```rust,ignore
133/// #[rustapi::get("/users")]
134/// async fn list_users() -> Json<Vec<User>> {
135///     Json(vec![])
136/// }
137///
138/// #[rustapi::get("/users/{id}")]
139/// async fn get_user(Path(id): Path<i64>) -> Result<User> {
140///     Ok(User { id, name: "John".into() })
141/// }
142/// ```
143#[proc_macro_attribute]
144pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream {
145    generate_route_handler("GET", attr, item)
146}
147
148/// POST route handler macro
149#[proc_macro_attribute]
150pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream {
151    generate_route_handler("POST", attr, item)
152}
153
154/// PUT route handler macro
155#[proc_macro_attribute]
156pub fn put(attr: TokenStream, item: TokenStream) -> TokenStream {
157    generate_route_handler("PUT", attr, item)
158}
159
160/// PATCH route handler macro
161#[proc_macro_attribute]
162pub fn patch(attr: TokenStream, item: TokenStream) -> TokenStream {
163    generate_route_handler("PATCH", attr, item)
164}
165
166/// DELETE route handler macro
167#[proc_macro_attribute]
168pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream {
169    generate_route_handler("DELETE", attr, item)
170}
171
172// ============================================
173// Route Metadata Macros
174// ============================================
175
176/// Tag macro for grouping endpoints in OpenAPI documentation
177///
178/// # Example
179///
180/// ```rust,ignore
181/// #[rustapi::get("/users")]
182/// #[rustapi::tag("Users")]
183/// async fn list_users() -> Json<Vec<User>> {
184///     Json(vec![])
185/// }
186/// ```
187#[proc_macro_attribute]
188pub fn tag(attr: TokenStream, item: TokenStream) -> TokenStream {
189    let tag = parse_macro_input!(attr as LitStr);
190    let input = parse_macro_input!(item as ItemFn);
191    
192    let attrs = &input.attrs;
193    let vis = &input.vis;
194    let sig = &input.sig;
195    let block = &input.block;
196    let tag_value = tag.value();
197    
198    // Add a doc comment with the tag info for documentation
199    let expanded = quote! {
200        #[doc = concat!("**Tag:** ", #tag_value)]
201        #(#attrs)*
202        #vis #sig #block
203    };
204    
205    TokenStream::from(expanded)
206}
207
208/// Summary macro for endpoint summary in OpenAPI documentation
209///
210/// # Example
211///
212/// ```rust,ignore
213/// #[rustapi::get("/users")]
214/// #[rustapi::summary("List all users")]
215/// async fn list_users() -> Json<Vec<User>> {
216///     Json(vec![])
217/// }
218/// ```
219#[proc_macro_attribute]
220pub fn summary(attr: TokenStream, item: TokenStream) -> TokenStream {
221    let summary = parse_macro_input!(attr as LitStr);
222    let input = parse_macro_input!(item as ItemFn);
223    
224    let attrs = &input.attrs;
225    let vis = &input.vis;
226    let sig = &input.sig;
227    let block = &input.block;
228    let summary_value = summary.value();
229    
230    // Add a doc comment with the summary
231    let expanded = quote! {
232        #[doc = #summary_value]
233        #(#attrs)*
234        #vis #sig #block
235    };
236    
237    TokenStream::from(expanded)
238}
239
240/// Description macro for detailed endpoint description in OpenAPI documentation
241///
242/// # Example
243///
244/// ```rust,ignore
245/// #[rustapi::get("/users")]
246/// #[rustapi::description("Returns a list of all users in the system. Supports pagination.")]
247/// async fn list_users() -> Json<Vec<User>> {
248///     Json(vec![])
249/// }
250/// ```
251#[proc_macro_attribute]
252pub fn description(attr: TokenStream, item: TokenStream) -> TokenStream {
253    let desc = parse_macro_input!(attr as LitStr);
254    let input = parse_macro_input!(item as ItemFn);
255    
256    let attrs = &input.attrs;
257    let vis = &input.vis;
258    let sig = &input.sig;
259    let block = &input.block;
260    let desc_value = desc.value();
261    
262    // Add a doc comment with the description
263    let expanded = quote! {
264        #[doc = ""]
265        #[doc = #desc_value]
266        #(#attrs)*
267        #vis #sig #block
268    };
269    
270    TokenStream::from(expanded)
271}
272