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 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}