变更宏路由实现

This commit is contained in:
liyunjia 2024-10-06 11:32:00 +08:00
parent 44bbc846df
commit 2ea3813a32
5 changed files with 149 additions and 46 deletions

View File

@ -1,6 +1,6 @@
use axum::routing::MethodRouter; use axum::routing::MethodRouter;
type TypedHandler<S = ()> = fn() -> (&'static str, MethodRouter<S>); type TypedHandler<S = ()> = fn() -> Vec<(&'static str, MethodRouter<S>)>;
/// A trait that allows typed routes, created with the [`route`] macro to /// A trait that allows typed routes, created with the [`route`] macro to
/// be added to an axum router. /// be added to an axum router.
@ -25,9 +25,10 @@ where
S: Send + Sync + Clone + 'static, S: Send + Sync + Clone + 'static,
{ {
type State = S; type State = S;
fn typed_route(self, handler: TypedHandler<Self::State>) -> Self { fn typed_route(self, handler: TypedHandler<Self::State>) -> Self {
let (path, method_router) = handler(); let routes = handler();
self.route(path, method_router) routes.into_iter().fold(self, |router, (path, method_router)| {
router.route(path, method_router)
})
} }
} }

View File

@ -23,45 +23,45 @@ pub fn responsable(input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
route::gen_route(attr, item) route::gen_route(attr, item, "route")
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream {
route::gen_dyn_route(attr, item, "get") route::gen_route(attr, item, "get")
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream {
route::gen_dyn_route(attr, item, "post") route::gen_route(attr, item, "post")
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn put(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn put(attr: TokenStream, item: TokenStream) -> TokenStream {
route::gen_dyn_route(attr, item, "put") route::gen_route(attr, item, "put")
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn patch(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn patch(attr: TokenStream, item: TokenStream) -> TokenStream {
route::gen_dyn_route(attr, item, "patch") route::gen_route(attr, item, "patch")
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream {
route::gen_dyn_route(attr, item, "delete") route::gen_route(attr, item, "delete")
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn options(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn options(attr: TokenStream, item: TokenStream) -> TokenStream {
route::gen_dyn_route(attr, item, "options") route::gen_route(attr, item, "options")
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn head(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn head(attr: TokenStream, item: TokenStream) -> TokenStream {
route::gen_dyn_route(attr, item, "head") route::gen_route(attr, item, "head")
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn trace(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn trace(attr: TokenStream, item: TokenStream) -> TokenStream {
route::gen_dyn_route(attr, item, "trace") route::gen_route(attr, item, "trace")
} }

View File

@ -5,14 +5,89 @@ extern crate syn;
extern crate proc_macro; extern crate proc_macro;
use parse::{Parse, ParseStream}; use parse::{Parse, ParseStream};
use proc_macro::{Span, TokenStream}; use punctuated::Punctuated; use proc_macro::{Span, TokenStream};
use punctuated::Punctuated;
// 用于处理宏输入 // 用于处理宏输入
use quote::quote; use spanned::Spanned; use quote::quote;
use spanned::Spanned;
// 用于生成代码 // 用于生成代码
use syn::*; use syn::*;
use syn::{parse_macro_input, ItemFn};
struct RouteArgs {
path: String,
methods: Vec<String>,
}
// 实现 Parse trait 以支持解析参数
impl Parse for RouteArgs {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
// 使用Meta解析 begin
// let args = Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated(input)?;
// let mut path = None;
// let mut methods = Vec::new();
// for arg in args {
// if let Meta::NameValue(nv) = arg {
// if nv.path.is_ident("method") {
// if let Expr::Array(ExprArray { elems, .. }) = nv.value {
// for elem in elems {
// if let Expr::Lit(syn::ExprLit { lit: Lit::Str(lit_str), .. }) = elem {
// methods.push(lit_str.value());
// }
// }
// }
// } else if nv.path.is_ident("path") {
// if let Expr::Lit(ExprLit { lit: Lit::Str(lit_str), .. }) = nv.value {
// path = Some(lit_str.value());
// }
// }
// }
// }
// let path = path.expect("Expected a path argument");
// 使用Meta解析 end
let args = Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated(input)?;
let mut path = None;
let mut methods = Vec::new();
for expr in args {
match expr {
Expr::Lit(ExprLit {
lit: Lit::Str(lit_str),
..
}) => {
path = Some(lit_str.value());
}
Expr::Assign(assign) => {
if let Expr::Path(path) = *assign.left {
if path.path.is_ident("method") {
if let Expr::Array(ExprArray { elems, .. }) = *assign.right {
for elem in elems {
if let Expr::Lit(ExprLit {
lit: Lit::Str(lit_str),
..
}) = elem
{
methods.push(lit_str.value());
}
}
}
}
}
}
_ => {}
}
}
let path = path.expect("路由参数不能为空");
Ok(RouteArgs { path, methods })
}
}
struct Args { struct Args {
vars: Vec<syn::Expr> vars: Vec<syn::Expr>,
} }
impl Parse for Args { impl Parse for Args {
@ -32,52 +107,54 @@ impl Args {
None => { None => {
// 第一个参数使路由url必须存在其他的参数根据实际需求进一步解析 // 第一个参数使路由url必须存在其他的参数根据实际需求进一步解析
if index != 0 { if index != 0 {
return Ok(None) return Ok(None);
} else { } else {
return Err(syn::Error::new( return Err(syn::Error::new(
Span::call_site().into(), Span::call_site().into(),
"route must have one argument" "route must have one argument",
)) ));
} }
} }
} }
} }
} }
#[allow(dead_code)] pub fn gen_route(attr: TokenStream, item: TokenStream, method: &str) -> TokenStream {
pub fn gen_route(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as Args);
let func = parse_macro_input!(item as ItemFn); let func = parse_macro_input!(item as ItemFn);
// 解析宏的参数
let RouteArgs { path, methods } = parse_macro_input!(attr as RouteArgs);
// 将 method 转换为小写并生成标识符
let vis = func.vis.clone(); let vis = func.vis.clone();
let ident = func.sig.ident.clone(); let ident = func.sig.ident.clone();
let method = args.get_arg(0).unwrap(); let mut method_routers = Vec::new();
let route = args.get_arg(1).unwrap(); if methods.is_empty() {
let method_name = syn::Ident::new(&method.to_lowercase(), method.span());
method_routers.push(quote! {
(#path, axum::routing::#method_name(#ident))
});
} else {
for method in methods {
let method_name = syn::Ident::new(&method.to_lowercase(), method.span());
method_routers.push(quote! {
(#path, axum::routing::#method_name(#ident))
});
}
}
let expanded = quote! { let generated = quote! {
#[allow(non_camel_case_types)] #vis fn #ident () -> Vec<(&'static str, axum::routing::method_routing::MethodRouter)> {
#vis struct #ident; #func
impl #ident { vec![#(#method_routers),*]
#vis fn route() -> axum::Router {
#func
axum::Router::new().route(#route, #method (#ident))
}
} }
}; };
expanded.into() TokenStream::from(generated)
} }
/// todo #[allow(dead_code)]
///
/// 计划采用
/// ```#[route("path"), methods = [Get, Post]]```
/// 的形式进行完善,使方法支持多种网络请求
///
/// gen_dyn_route 生成的代码不再是的单独的MethodRouter而是MethodRouter集合
pub fn gen_dyn_route(attr: TokenStream, item: TokenStream, method: &str) -> TokenStream { pub fn gen_dyn_route(attr: TokenStream, item: TokenStream, method: &str) -> TokenStream {
let args = parse_macro_input!(attr as Args); let args = parse_macro_input!(attr as Args);
let func = parse_macro_input!(item as ItemFn); let func = parse_macro_input!(item as ItemFn);
@ -87,6 +164,26 @@ pub fn gen_dyn_route(attr: TokenStream, item: TokenStream, method: &str) -> Toke
let route = args.get_arg(0).unwrap().unwrap(); let route = args.get_arg(0).unwrap().unwrap();
let method_option = args.get_arg(1);
match method_option {
Ok(method) => {
if let Some(Expr::Assign(methods_value)) = method {
println!("method is {:?}", methods_value);
// if let Expr::Path(left) = methods_value.left {
// left.path.segments[0].ident.eq("d") {
// }
// }
// if let Expr::Lit(right) = methods_value.right {
// }
}
}
Err(err) => {
println!("error is {}", err);
}
}
let method_name: Ident = Ident::new(method, route.span()); let method_name: Ident = Ident::new(method, route.span());
let expanded = quote! { let expanded = quote! {
@ -97,4 +194,4 @@ pub fn gen_dyn_route(attr: TokenStream, item: TokenStream, method: &str) -> Toke
} }
}; };
expanded.into() expanded.into()
} }

View File

@ -8,7 +8,7 @@ use library::extractor::path_extractor::PathVar;
use library::model::response::ResResult; use library::model::response::ResResult;
use library::extractor::body_extractor::JsonBody; use library::extractor::body_extractor::JsonBody;
use library::extractor::query_extractor::QueryParams; use library::extractor::query_extractor::QueryParams;
use macros::{get, post}; use macros::{get, post, route};
use crate::service; use crate::service;
@ -35,7 +35,7 @@ pub async fn get_feedback_list_by_page(
.await .await
} }
#[get("/feedback/:page/:pageSize", d = "a")] #[route("/feedback/:page/:pageSize", method = ["get", "post"])]
pub async fn get_feedback_list( pub async fn get_feedback_list(
context: Context, context: Context,
PathVar(page_params): PathVar<PageParams>, PathVar(page_params): PathVar<PageParams>,

View File

@ -5,7 +5,7 @@ pub mod account_controller;
pub mod feedback_controller; pub mod feedback_controller;
pub fn init() -> Router { pub fn init() -> Router {
Router::new() let mut app = Router::new()
// 用户登录、用户管理相关路由 // 用户登录、用户管理相关路由
.typed_route(account_controller::authenticate_google) .typed_route(account_controller::authenticate_google)
.typed_route(account_controller::authenticate_with_password) .typed_route(account_controller::authenticate_with_password)
@ -13,5 +13,10 @@ pub fn init() -> Router {
// 反馈相关路由 // 反馈相关路由
.typed_route(feedback_controller::add_feedback) .typed_route(feedback_controller::add_feedback)
.typed_route(feedback_controller::get_feedback_list_by_page) .typed_route(feedback_controller::get_feedback_list_by_page)
.typed_route(feedback_controller::get_feedback_list) // .typed_route(feedback_controller::get_feedback_list)
;
for (path, method_router) in feedback_controller::get_feedback_list() {
app = app.route(path, method_router);
}
app
} }