变更宏路由实现

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;
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
/// 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::State>) -> 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)
})
}
}

View File

@ -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")
}

View File

@ -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<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 {
vars: Vec<syn::Expr>
vars: Vec<syn::Expr>,
}
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()
}
}

View File

@ -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<PageParams>,

View File

@ -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
}