diff --git a/library/src/typed_router.rs b/library/src/typed_router.rs index fd35d8f..34e47c2 100644 --- a/library/src/typed_router.rs +++ b/library/src/typed_router.rs @@ -1,6 +1,6 @@ use axum::routing::MethodRouter; -type TypedHandler = fn() -> (&'static str, MethodRouter); +type TypedHandler = fn() -> Vec<(&'static str, MethodRouter)>; /// A trait that allows typed routes, created with the [`route`] macro to /// be added to an axum router. @@ -25,9 +25,10 @@ where S: Send + Sync + Clone + 'static, { type State = S; - fn typed_route(self, handler: TypedHandler) -> Self { - let (path, method_router) = handler(); - self.route(path, method_router) + let routes = handler(); + routes.into_iter().fold(self, |router, (path, method_router)| { + router.route(path, method_router) + }) } } \ No newline at end of file diff --git a/macro/src/lib.rs b/macro/src/lib.rs index 01c2435..0416f3e 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -23,45 +23,45 @@ pub fn responsable(input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream { - route::gen_route(attr, item) + route::gen_route(attr, item, "route") } #[proc_macro_attribute] pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream { - route::gen_dyn_route(attr, item, "get") + route::gen_route(attr, item, "get") } #[proc_macro_attribute] pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream { - route::gen_dyn_route(attr, item, "post") + route::gen_route(attr, item, "post") } #[proc_macro_attribute] pub fn put(attr: TokenStream, item: TokenStream) -> TokenStream { - route::gen_dyn_route(attr, item, "put") + route::gen_route(attr, item, "put") } #[proc_macro_attribute] pub fn patch(attr: TokenStream, item: TokenStream) -> TokenStream { - route::gen_dyn_route(attr, item, "patch") + route::gen_route(attr, item, "patch") } #[proc_macro_attribute] pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream { - route::gen_dyn_route(attr, item, "delete") + route::gen_route(attr, item, "delete") } #[proc_macro_attribute] pub fn options(attr: TokenStream, item: TokenStream) -> TokenStream { - route::gen_dyn_route(attr, item, "options") + route::gen_route(attr, item, "options") } #[proc_macro_attribute] pub fn head(attr: TokenStream, item: TokenStream) -> TokenStream { - route::gen_dyn_route(attr, item, "head") + route::gen_route(attr, item, "head") } #[proc_macro_attribute] pub fn trace(attr: TokenStream, item: TokenStream) -> TokenStream { - route::gen_dyn_route(attr, item, "trace") + route::gen_route(attr, item, "trace") } \ No newline at end of file diff --git a/macro/src/route.rs b/macro/src/route.rs index 416be39..aa8c246 100644 --- a/macro/src/route.rs +++ b/macro/src/route.rs @@ -5,14 +5,89 @@ extern crate syn; extern crate proc_macro; 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::{parse_macro_input, ItemFn}; + +struct RouteArgs { + path: String, + methods: Vec, +} + +// 实现 Parse trait 以支持解析参数 +impl Parse for RouteArgs { + fn parse(input: ParseStream) -> syn::parse::Result { + // 使用Meta解析 begin + // let args = Punctuated::::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::::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 { - vars: Vec + vars: Vec, } impl Parse for Args { @@ -32,52 +107,54 @@ impl Args { None => { // 第一个参数使路由url,必须存在,其他的参数根据实际需求进一步解析 if index != 0 { - return Ok(None) + return Ok(None); } else { return Err(syn::Error::new( 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) -> TokenStream { - let args = parse_macro_input!(attr as Args); +pub fn gen_route(attr: TokenStream, item: TokenStream, method: &str) -> TokenStream { 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 ident = func.sig.ident.clone(); - let method = args.get_arg(0).unwrap(); - let route = args.get_arg(1).unwrap(); + let mut method_routers = Vec::new(); + 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! { - #[allow(non_camel_case_types)] - #vis struct #ident; + let generated = quote! { + #vis fn #ident () -> Vec<(&'static str, axum::routing::method_routing::MethodRouter)> { + #func - impl #ident { - #vis fn route() -> axum::Router { - #func - - axum::Router::new().route(#route, #method (#ident)) - } + vec![#(#method_routers),*] } }; - expanded.into() + TokenStream::from(generated) } -/// todo -/// -/// 计划采用 -/// ```#[route("path"), methods = [Get, Post]]``` -/// 的形式进行完善,使方法支持多种网络请求 -/// -/// gen_dyn_route 生成的代码不再是的单独的MethodRouter,而是MethodRouter集合 +#[allow(dead_code)] pub fn gen_dyn_route(attr: TokenStream, item: TokenStream, method: &str) -> TokenStream { let args = parse_macro_input!(attr as Args); 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 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 expanded = quote! { @@ -97,4 +194,4 @@ pub fn gen_dyn_route(attr: TokenStream, item: TokenStream, method: &str) -> Toke } }; expanded.into() -} \ No newline at end of file +} diff --git a/server/src/controller/feedback_controller.rs b/server/src/controller/feedback_controller.rs index bec9473..ea59a95 100644 --- a/server/src/controller/feedback_controller.rs +++ b/server/src/controller/feedback_controller.rs @@ -8,7 +8,7 @@ use library::extractor::path_extractor::PathVar; use library::model::response::ResResult; use library::extractor::body_extractor::JsonBody; use library::extractor::query_extractor::QueryParams; -use macros::{get, post}; +use macros::{get, post, route}; use crate::service; @@ -35,7 +35,7 @@ pub async fn get_feedback_list_by_page( .await } -#[get("/feedback/:page/:pageSize", d = "a")] +#[route("/feedback/:page/:pageSize", method = ["get", "post"])] pub async fn get_feedback_list( context: Context, PathVar(page_params): PathVar, diff --git a/server/src/controller/mod.rs b/server/src/controller/mod.rs index 87770fa..63ccd36 100644 --- a/server/src/controller/mod.rs +++ b/server/src/controller/mod.rs @@ -5,7 +5,7 @@ pub mod account_controller; pub mod feedback_controller; pub fn init() -> Router { - Router::new() + let mut app = Router::new() // 用户登录、用户管理相关路由 .typed_route(account_controller::authenticate_google) .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::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 }