Skip to main content

chopin_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{ItemFn, parse_macro_input};
4
5#[proc_macro_attribute]
6pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream {
7    generate_route("Get", attr, item)
8}
9
10#[proc_macro_attribute]
11pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream {
12    generate_route("Post", attr, item)
13}
14
15#[proc_macro_attribute]
16pub fn put(attr: TokenStream, item: TokenStream) -> TokenStream {
17    generate_route("Put", attr, item)
18}
19
20#[proc_macro_attribute]
21pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream {
22    generate_route("Delete", attr, item)
23}
24
25#[proc_macro_attribute]
26pub fn patch(attr: TokenStream, item: TokenStream) -> TokenStream {
27    generate_route("Patch", attr, item)
28}
29
30#[proc_macro_attribute]
31pub fn head(attr: TokenStream, item: TokenStream) -> TokenStream {
32    generate_route("Head", attr, item)
33}
34
35#[proc_macro_attribute]
36pub fn options(attr: TokenStream, item: TokenStream) -> TokenStream {
37    generate_route("Options", attr, item)
38}
39
40#[proc_macro_attribute]
41pub fn trace(attr: TokenStream, item: TokenStream) -> TokenStream {
42    generate_route("Trace", attr, item)
43}
44
45#[proc_macro_attribute]
46pub fn connect(attr: TokenStream, item: TokenStream) -> TokenStream {
47    generate_route("Connect", attr, item)
48}
49
50fn generate_route(method: &str, attr: TokenStream, item: TokenStream) -> TokenStream {
51    let path = parse_macro_input!(attr as syn::LitStr).value();
52    let input_fn = parse_macro_input!(item as ItemFn);
53
54    let fn_name = &input_fn.sig.ident;
55    let method_ident = syn::Ident::new(method, proc_macro2::Span::call_site());
56
57    // Extract doc comments
58    let mut docs = Vec::new();
59    for attr in &input_fn.attrs {
60        if attr.path().is_ident("doc")
61            && let syn::Meta::NameValue(nv) = &attr.meta
62            && let syn::Expr::Lit(syn::ExprLit {
63                lit: syn::Lit::Str(s),
64                ..
65            }) = &nv.value
66        {
67            docs.push(s.value().trim().to_string());
68        }
69    }
70
71    let summary = docs.first().cloned().unwrap_or_default();
72    let description = if docs.len() > 1 {
73        docs[1..].join("\n")
74    } else {
75        String::new()
76    };
77
78    let expanded = quote! {
79        #input_fn
80
81        ::chopin_core::inventory::submit! {
82            ::chopin_core::RouteDef {
83                method: ::chopin_core::http::Method::#method_ident,
84                path: #path,
85                handler: #fn_name,
86                summary: #summary,
87                description: #description,
88            }
89        }
90    };
91
92    TokenStream::from(expanded)
93}