chuanyue-service/macro/src/route.rs
2025-02-08 16:13:54 +08:00

125 lines
3.8 KiB
Rust

extern crate proc_macro2;
extern crate quote;
extern crate syn;
extern crate proc_macro;
use parse::{Parse, ParseStream};
use proc_macro::{Span, TokenStream};
use punctuated::Punctuated;
// 用于处理宏输入
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) -> Result<Self> {
let args = Punctuated::<Expr, 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),
..
}) => {
let path_str = lit_str.value();
// 验证路由路径
validate_route_path(&path_str)?;
path = Some(path_str);
}
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.ok_or_else(|| Error::new(
Span::call_site().into(),
"路由路径参数不能为空",
))?;
Ok(RouteArgs { path, methods })
}
}
// 添加路由参数验证
fn validate_route_path(path: &str) -> Result<()> {
if !path.starts_with('/') {
return Err(Error::new(
Span::call_site().into(),
"路由路径必须以'/'开头",
));
}
Ok(())
}
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 mut method_routers = Vec::new();
if methods.is_empty() {
let method_name = Ident::new(&method.to_lowercase(), method.span());
method_routers.push(quote! {
(#path, axum::routing::#method_name(#ident::#ident))
});
} else {
for method in methods {
let method_name = Ident::new(&method.to_lowercase(), method.span());
method_routers.push(quote! {
(#path, axum::routing::#method_name(#ident::#ident))
});
}
}
let generated = quote! {
#[allow(non_camel_case_types)]
struct #ident;
impl #ident {
#func
}
impl library::typed_router::RouteMethod for #ident {
fn ge_router(&self, mut router: axum::Router) -> axum::Router {
let methods = vec![#(#method_routers),*];
for (path, method_router) in methods {
router = router.route(path, method_router);
}
router
}
}
::library::submit_router_method!(#ident);
};
TokenStream::from(generated)
}