axum_controller_macros/
lib.rs1#![feature(proc_macro_diagnostic)]
2use proc_macro::TokenStream;
3use proc_macro2::Ident;
4use syn::{
5 parse::{Parse, ParseStream},
6 punctuated::Punctuated,
7 ItemImpl, MetaNameValue,
8};
9#[macro_use]
10extern crate quote;
11
12#[macro_use]
13extern crate syn;
14
15#[derive(Clone, Default)]
16struct MyAttrs {
17 middlewares: Vec<syn::Expr>,
18 path: Option<syn::Expr>,
19 state: Option<syn::Expr>,
20}
21
22impl Parse for MyAttrs {
23 fn parse(input: ParseStream) -> syn::Result<Self> {
24 let mut path: Option<syn::Expr> = None;
25 let mut state: Option<syn::Expr> = None;
26 let mut middlewares: Vec<syn::Expr> = Vec::new();
27
28 for nv in Punctuated::<MetaNameValue, Token![,]>::parse_terminated(input)?.into_iter() {
30 let segs = nv.path.segments.clone().into_pairs();
31 let seg = segs.into_iter().next().unwrap().into_value();
32 let ident = seg.ident;
33 match ident.to_string().as_str() {
34 "path" => {
35 if path.is_some() {
36 return Err(syn::Error::new_spanned(path, "duplicate `path` attribute"));
37 }
38 path = Some(nv.value);
39 }
40 "state" => {
41 if state.is_some() {
42 return Err(syn::Error::new_spanned(
43 state,
44 "duplicate `state` attribute",
45 ));
46 }
47 state = Some(nv.value);
48 }
49 "middleware" => middlewares.push(nv.value),
50 _ => {
51 panic!(
52 "Unknown attribute given to controller macro, only path,state & middleware allowed"
53 )
54 }
55 }
56 }
57 Ok(Self {
58 state,
59 path,
60 middlewares,
61 })
62 }
63}
64
65#[derive(Clone)]
66struct MyItem {
67 struct_name: syn::Type,
68 route_fns: Vec<syn::Ident>,
69}
70
71impl Parse for MyItem {
72 fn parse(input: ParseStream) -> syn::Result<Self> {
73 let ast: ItemImpl = input.parse()?;
74 let struct_name = *(ast.clone().self_ty.clone());
75 let mut route_fns: Vec<syn::Ident> = vec![];
76
77 for item in ast.items.iter() {
78 if let syn::ImplItem::Fn(impl_item_fn) = item {
79 for attr in impl_item_fn.attrs.clone() {
81 if attr.path().is_ident("route") {
82 let fn_name: Ident = impl_item_fn.sig.ident.clone();
83 route_fns.push(fn_name);
84 }
85 }
86 }
87 }
88
89 Ok(Self {
90 route_fns,
91 struct_name,
92 })
93 }
94}
95
96#[proc_macro_attribute]
119pub fn controller(attr: TokenStream, item: TokenStream) -> TokenStream {
120 let args = parse_macro_input!(attr as MyAttrs);
121 let item2: proc_macro2::TokenStream = item.clone().into();
122 let myimpl = parse_macro_input!(item as MyItem);
123
124 let state = args.state.unwrap_or(parse_quote!(()));
125 let route_fns = myimpl.route_fns;
126 let struct_name = &myimpl.struct_name;
127 let route = args.path.unwrap_or(syn::parse_quote!("/"));
128
129 let route_calls = route_fns
130 .into_iter()
131 .map(move |route| {
132 quote! {
133 .typed_route(#struct_name :: #route)
134
135 }
136 })
137 .collect::<Vec<_>>();
138
139 let nesting_call = quote! {
140 .nest(#route, __nested_router)
141 };
142
143 let nested_router_qoute = quote! {
144 axum::Router::new()
145 #nesting_call
146 };
147 let unnested_router_quote = quote! {
148 __nested_router
149 };
150 let maybe_nesting_call = if let syn::Expr::Lit(lit) = route {
151 if lit.eq(&syn::parse_quote!("/")) {
152 unnested_router_quote
153 } else {
154 nested_router_qoute
155 }
156 } else {
157 nested_router_qoute
158 };
159
160 let middleware_calls = args
161 .middlewares
162 .clone()
163 .into_iter()
164 .map(|middleware| quote! {.layer(#middleware)})
165 .collect::<Vec<_>>();
166
167 let from_controller_into_router_impl = quote! {
171 impl #struct_name {
172 pub fn into_router(state: #state) -> axum::Router<#state> {
173 let __nested_router = axum::Router::new()
174 #(#route_calls)*
175 #(#middleware_calls)*
176 .with_state(state)
177 ;
178
179 #maybe_nesting_call
180 }
181 }
182 };
183
184 let res: TokenStream = quote! {
185 #item2
186 #from_controller_into_router_impl
187 }
188 .into();
189
190 res
191}