1#![doc = include_str!("../README.md")]
2#![doc = include_str!("../examples/controller.rs")]
15#![forbid(unsafe_code)]
18#![feature(proc_macro_diagnostic)]
19use proc_macro::TokenStream;
20use proc_macro2::Ident;
21use syn::{
22 parse::{Parse, ParseStream},
23 punctuated::Punctuated,
24 ItemImpl, MetaNameValue,
25};
26#[macro_use]
27extern crate quote;
28
29#[macro_use]
30extern crate syn;
31
32#[derive(Clone, Default)]
33struct MyAttrs {
34 middlewares: Vec<syn::Expr>,
35 path: Option<syn::Expr>,
36 state: Option<syn::Expr>,
37}
38
39impl Parse for MyAttrs {
40 fn parse(input: ParseStream) -> syn::Result<Self> {
41 let mut path: Option<syn::Expr> = None;
42 let mut state: Option<syn::Expr> = None;
43 let mut middlewares: Vec<syn::Expr> = Vec::new();
44
45 for nv in Punctuated::<MetaNameValue, Token![,]>::parse_terminated(input)?.into_iter() {
47 let segs = nv.path.segments.clone().into_pairs();
48 let seg = segs.into_iter().next().unwrap().into_value();
49 let ident = seg.ident;
50 match ident.to_string().as_str() {
51 "path" => {
52 if path.is_some() {
53 return Err(syn::Error::new_spanned(path, "duplicate `path` attribute"));
54 }
55 path = Some(nv.value);
56 }
57 "state" => {
58 if state.is_some() {
59 return Err(syn::Error::new_spanned(
60 state,
61 "duplicate `state` attribute",
62 ));
63 }
64 state = Some(nv.value);
65 }
66 "middleware" => middlewares.push(nv.value),
67 _ => {
68 panic!(
69 "Unknown attribute given to controller macro, only path,state & middleware allowed"
70 )
71 }
72 }
73 }
74 Ok(Self {
75 state,
76 path,
77 middlewares,
78 })
79 }
80}
81
82#[derive(Clone)]
83struct MyItem {
84 struct_name: syn::Type,
85 route_fns: Vec<syn::Ident>,
86}
87
88impl Parse for MyItem {
89 fn parse(input: ParseStream) -> syn::Result<Self> {
90 let ast: ItemImpl = input.parse()?;
91 let struct_name = *(ast.clone().self_ty.clone());
92 let mut route_fns: Vec<syn::Ident> = vec![];
93
94 for item in ast.items.iter() {
95 if let syn::ImplItem::Fn(impl_item_fn) = item {
96 for attr in impl_item_fn.attrs.clone() {
98 if attr.path().is_ident("route") {
99 let fn_name: Ident = impl_item_fn.sig.ident.clone();
100 route_fns.push(fn_name);
101 }
102 }
103 }
104 }
105
106 Ok(Self {
107 route_fns,
108 struct_name,
109 })
110 }
111}
112
113#[proc_macro_attribute]
137pub fn controller(attr: TokenStream, item: TokenStream) -> TokenStream {
138 let args = parse_macro_input!(attr as MyAttrs);
139 let item2: proc_macro2::TokenStream = item.clone().into();
140 let myimpl = parse_macro_input!(item as MyItem);
141
142 let state = args.state.unwrap_or(parse_quote!(()));
143 let route_fns = myimpl.route_fns;
144 let struct_name = &myimpl.struct_name;
145 let route = args.path.unwrap_or(syn::parse_quote!("/"));
146
147 let route_calls = route_fns
148 .into_iter()
149 .map(move |route| {
150 quote! {
151 .typed_route(#struct_name :: #route)
152
153 }
154 })
155 .collect::<Vec<_>>();
156
157 let nesting_call = quote! {
158 .nest(#route, __nested_router)
159 };
160
161 let nested_router_qoute = quote! {
162 axum::Router::new()
163 #nesting_call
164 };
165 let unnested_router_quote = quote! {
166 __nested_router
167 };
168 let maybe_nesting_call = if let syn::Expr::Lit(lit) = route {
169 if lit.eq(&syn::parse_quote!("/")) {
170 unnested_router_quote
171 } else {
172 nested_router_qoute
173 }
174 } else {
175 nested_router_qoute
176 };
177
178 let middleware_calls = args
179 .middlewares
180 .clone()
181 .into_iter()
182 .map(|middleware| quote! {.layer(#middleware)})
183 .collect::<Vec<_>>();
184
185 let from_controller_into_router_impl = quote! {
189 impl #struct_name {
190 pub fn into_router(state: #state) -> axum::Router<#state> {
191 let __nested_router = axum::Router::new()
192 #(#route_calls)*
193 #(#middleware_calls)*
194 .with_state(state)
195 ;
196
197 #maybe_nesting_call
198 }
199 }
200 };
201
202 let res: TokenStream = quote! {
203 #item2
204 #from_controller_into_router_impl
205 }
206 .into();
207
208 res
209}