axum_controller_macros/
lib.rs

1#![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        // parse while stuff returns
29        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                // let fn_name = &impl_item_fn.sig.ident;
80                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// TODO add better docs
97/// A macro that generates a into_router(\_: State<_>) impl which automatically wires up all `route`'s and the given middlewares, path-prefix etc
98///
99/// ## Syntax:
100/// ```ignore
101/// #[controller(
102///   path = "/asd",
103///   state = AppState,
104///   middleware=my_middleware
105/// )]
106/// impl ExampleController { /* ... */ }
107/// ```
108/// - path
109///   - optional, 0-1 allowed, defaults to `"/"`
110///   - A path to prefix `.nest` the `routes` in the controller Struct under
111/// - state
112///   - optional, 0-1 allowed, defaults to `"()"`)
113///   - The type signature of the state given to the routes
114/// - middleware
115///   - optional, 0-n allowed, default to [] (no middlewares)
116///   - Middlewares to `.layer` in the created router
117///
118#[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    // TODO Checck if 2 possible to make 2 impls
168    // where state of parent router is ()
169    // one where it's #state
170    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}