添加宏路由(临时提交)

This commit is contained in:
李运家 2024-10-02 17:21:46 +08:00
parent a488c82067
commit 684095231d
14 changed files with 371 additions and 134 deletions

View File

@ -18,7 +18,7 @@ i18n = { path = "i18n" }
[workspace.dependencies]
tokio = "1.36"
axum = "0.7"
axum = "0.7.7"
tracing = "0.1"
tower-http = "0.5"
validator = "0.17"

View File

@ -4,4 +4,8 @@
- [*] 参考example中的jwt实现方式移除context对extension的依赖那么language-tag该怎么处理
- [ ] 参考rocket移除参数的元组类型
- [ ] 模拟request_util能否避免涉及到的parts.clone()?
- [ ] 能否自定义实现宏路由
- [ ] 能否自定义实现宏路由
### note
- 展开宏cargo expand --manifest-path .\server\Cargo.toml controller::feedback_controller

View File

@ -1,4 +1,4 @@
use derive::Responsable;
use macros::Responsable;
use serde::{Deserialize, Serialize};
use sqlx::types::JsonValue;

View File

@ -1,4 +1,4 @@
use derive::Responsable;
use macros::Responsable;
use serde::Serialize;
use crate::entities::feedback::Feedback;

0
feeback_controller_ex.rs Normal file
View File

0
feedback_controller_e.rs Normal file
View File

View File

@ -1,5 +1,3 @@
use std::fmt::Display;
use axum::{async_trait, extract::{path::ErrorKind, rejection::PathRejection, FromRequestParts}};
use http::request::Parts;
use i18n::{message, message_ids::MessageId};
@ -13,7 +11,7 @@ pub struct PathVar<T>(pub T);
#[async_trait]
impl<S, T> FromRequestParts<S> for PathVar<T>
where
T: DeserializeOwned + Send + Display,
T: DeserializeOwned + Send,
S: Send + Sync,
{
type Rejection = ResErr;
@ -25,7 +23,6 @@ where
match axum::extract::Path::<T>::from_request_parts(parts, state).await {
Ok(value) => {
tracing::info!("path params: {}", value.0);
Ok(Self(value.0))
},
Err(rejection) => {

View File

@ -27,7 +27,9 @@ where
type State = S;
fn typed_route(self, handler: TypedHandler<Self::State>) -> Self {
tracing::info!("start add router");
let (path, method_router) = handler();
tracing::info!("adding typed route: {} - {:?}", path, method_router);
self.route(path, method_router)
}
}

View File

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[lib]
name = "derive"
name = "macros"
path = "src/lib.rs"
proc-macro = true

View File

@ -4,10 +4,11 @@ extern crate syn;
extern crate proc_macro;
use proc_macro::TokenStream; // 用于处理宏输入
use quote::quote; // 用于生成代码
use syn::{parse_macro_input, Data, DeriveInput, Fields}; // 用于解析宏输入
use proc_macro::TokenStream; // 用于生成代码
use syn::{parse_macro_input, parse_quote, DeriveInput}; // 用于解析宏输入
mod responsable;
mod route;
// 定义一个名为`responsable`的过程宏
// #[proc_macro_derive(Responsable, attributes(status, headers))]
@ -15,128 +16,23 @@ use syn::{parse_macro_input, Data, DeriveInput, Fields}; // 用于解析宏输
pub fn responsable(input: TokenStream) -> TokenStream {
// 解析宏输入为`DeriveInput`
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident; // 获取结构体名称
// 根据数据类型生成不同的代码
let gen = match input.data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref _fields) => {
// // 处理命名字段
// let field_names = fields.named.iter().map(|f| &f.ident); // 获取所有字段名称
// let field_types = fields.named.iter().map(|f| &f.ty); // 获取所有字段类型
responsable::gen_responsable(input)
}
// // 获取`status`属性
// let status = match input.attrs.iter().find(|attr| attr.path().is_ident("status")) {
// Some(attr) => {
// let status_lit = attr.parse_args::<Ident>().unwrap(); // 解析`status`属性值
// quote! { #status_lit } // 生成状态码
// },
// None => quote! { hyper::StatusCode::OK }, // 默认状态码为`OK`
// };
// #[proc_macro_attribute]
// pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
// route::gen_route(attr, item)
// // item
// }
// // 获取`headers`属性
// let headers = match input.attrs.iter().find(|attr| attr.path().is_ident("headers")) {
// Some(attr) => {
// let headers_lit = attr.parse_args::<Ident>().unwrap(); // 解析`headers`属性值
// quote! { #headers_lit } // 生成头部信息
// },
// None => quote! { hyper::HeaderMap::new() }, // 默认头部信息为空
// };
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
route::gen_route(attr, item)
}
// 生成实现`IntoResponse` trait的代码
quote! {
impl axum::response::IntoResponse for #name {
fn into_response(self) -> axum::http::Response<axum::body::Body> {
let mut builder = hyper::Response::builder();
// .status(#status) // 设置状态码
// .headers(#headers); // 设置头部信息
let body = serde_json::to_string(&self).expect("Failed to serialize response"); // 序列化结构体为JSON字符串
axum::http::Response::new(axum::body::Body::from(body)) // 构建响应
}
}
}
},
Fields::Unnamed(ref _fields) => {
// // 处理未命名字段
// let field_names = fields.unnamed.iter().enumerate().map(|(i, _)| i); // 获取所有字段索引
// let field_types = fields.unnamed.iter().map(|f| &f.ty); // 获取所有字段类型
// // 获取`status`属性
// let status = match input.attrs.iter().find(|attr| attr.path().is_ident("status")) {
// Some(attr) => {
// let status_lit = attr.parse_args::<Ident>().unwrap(); // 解析`status`属性值
// quote! { #status_lit } // 生成状态码
// },
// None => quote! { hyper::StatusCode::OK }, // 默认状态码为`OK`
// };
// // 获取`headers`属性
// let headers = match input.attrs.iter().find(|attr| attr.path().is_ident("headers")) {
// Some(attr) => {
// let headers_lit = attr.parse_args::<Ident>().unwrap(); // 解析`headers`属性值
// quote! { #headers_lit } // 生成头部信息
// },
// None => quote! { hyper::HeaderMap::new() }, // 默认头部信息为空
// };
// 生成实现`IntoResponse` trait的代码
quote! {
impl axum::response::IntoResponse for #name {
fn into_response(self) -> axum::http::Response<axum::body::Body> {
// let mut builder = hyper::Response::builder();
// .status(#status) // 设置状态码
// .headers(#headers); // 设置头部信息
let body = serde_json::to_string(&self).expect("Failed to serialize response"); // 序列化结构体为JSON字符串
axum::http::Response::new(axum::body::Body::from(body)) // 构建响应
}
}
}
},
Fields::Unit => {
// // 处理无字段的结构体
// // 获取`status`属性
// let status = match input.attrs.iter().find(|attr| attr.path().is_ident("status")) {
// Some(attr) => {
// let status_lit = attr.parse_args::<Ident>().unwrap(); // 解析`status`属性值
// quote! { #status_lit } // 生成状态码
// },
// None => quote! { hyper::StatusCode::OK }, // 默认状态码为`OK`
// };
// // 获取`headers`属性
// let headers = match input.attrs.iter().find(|attr| attr.path().is_ident("headers")) {
// Some(attr) => {
// let headers_lit = attr.parse_args::<Ident>().unwrap(); // 解析`headers`属性值
// quote! { #headers_lit } // 生成头部信息
// },
// None => quote! { hyper::HeaderMap::new() }, // 默认头部信息为空
// };
// 生成实现`IntoResponse` trait的代码
quote! {
impl axum::response::IntoResponse for #name {
fn into_response(self) -> axum::http::Response<axum::body::Body> {
// let mut builder = hyper::Response::builder();
// .status(#status) // 设置状态码
// .headers(#headers); // 设置头部信息
let body = serde_json::to_string(&self).expect("Failed to serialize response"); // 序列化结构体为JSON字符串
axum::http::Response::new(hyper::body::Body::from(body)) // 构建响应
}
}
}
},
},
Data::Enum(_) | Data::Union(_) => {
// 不支持枚举或联合体
unimplemented!("IntoResponse derive is not implemented for enums or unions.")
},
};
gen.into() // 将生成的代码转换为`TokenStream`
#[proc_macro_attribute]
pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream {
route::gen_get_route(attr, item)
}

133
macro/src/responsable.rs Normal file
View File

@ -0,0 +1,133 @@
extern crate proc_macro2;
extern crate quote;
extern crate syn;
extern crate proc_macro;
use proc_macro::TokenStream; // 用于处理宏输入
use quote::quote; // 用于生成代码
use syn::{Data, DeriveInput, Fields}; // 用于解析宏输入
pub fn gen_responsable(input: DeriveInput) -> TokenStream {
let name = &input.ident; // 获取结构体名称
let gen = match input.data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref _fields) => {
// // 处理命名字段
// let field_names = fields.named.iter().map(|f| &f.ident); // 获取所有字段名称
// let field_types = fields.named.iter().map(|f| &f.ty); // 获取所有字段类型
// // 获取`status`属性
// let status = match input.attrs.iter().find(|attr| attr.path().is_ident("status")) {
// Some(attr) => {
// let status_lit = attr.parse_args::<Ident>().unwrap(); // 解析`status`属性值
// quote! { #status_lit } // 生成状态码
// },
// None => quote! { hyper::StatusCode::OK }, // 默认状态码为`OK`
// };
// // 获取`headers`属性
// let headers = match input.attrs.iter().find(|attr| attr.path().is_ident("headers")) {
// Some(attr) => {
// let headers_lit = attr.parse_args::<Ident>().unwrap(); // 解析`headers`属性值
// quote! { #headers_lit } // 生成头部信息
// },
// None => quote! { hyper::HeaderMap::new() }, // 默认头部信息为空
// };
// 生成实现`IntoResponse` trait的代码
quote! {
impl axum::response::IntoResponse for #name {
fn into_response(self) -> axum::http::Response<axum::body::Body> {
let mut builder = hyper::Response::builder();
// .status(#status) // 设置状态码
// .headers(#headers); // 设置头部信息
let body = serde_json::to_string(&self).expect("Failed to serialize response"); // 序列化结构体为JSON字符串
axum::http::Response::new(axum::body::Body::from(body)) // 构建响应
}
}
}
},
Fields::Unnamed(ref _fields) => {
// // 处理未命名字段
// let field_names = fields.unnamed.iter().enumerate().map(|(i, _)| i); // 获取所有字段索引
// let field_types = fields.unnamed.iter().map(|f| &f.ty); // 获取所有字段类型
// // 获取`status`属性
// let status = match input.attrs.iter().find(|attr| attr.path().is_ident("status")) {
// Some(attr) => {
// let status_lit = attr.parse_args::<Ident>().unwrap(); // 解析`status`属性值
// quote! { #status_lit } // 生成状态码
// },
// None => quote! { hyper::StatusCode::OK }, // 默认状态码为`OK`
// };
// // 获取`headers`属性
// let headers = match input.attrs.iter().find(|attr| attr.path().is_ident("headers")) {
// Some(attr) => {
// let headers_lit = attr.parse_args::<Ident>().unwrap(); // 解析`headers`属性值
// quote! { #headers_lit } // 生成头部信息
// },
// None => quote! { hyper::HeaderMap::new() }, // 默认头部信息为空
// };
// 生成实现`IntoResponse` trait的代码
quote! {
impl axum::response::IntoResponse for #name {
fn into_response(self) -> axum::http::Response<axum::body::Body> {
// let mut builder = hyper::Response::builder();
// .status(#status) // 设置状态码
// .headers(#headers); // 设置头部信息
let body = serde_json::to_string(&self).expect("Failed to serialize response"); // 序列化结构体为JSON字符串
axum::http::Response::new(axum::body::Body::from(body)) // 构建响应
}
}
}
},
Fields::Unit => {
// // 处理无字段的结构体
// // 获取`status`属性
// let status = match input.attrs.iter().find(|attr| attr.path().is_ident("status")) {
// Some(attr) => {
// let status_lit = attr.parse_args::<Ident>().unwrap(); // 解析`status`属性值
// quote! { #status_lit } // 生成状态码
// },
// None => quote! { hyper::StatusCode::OK }, // 默认状态码为`OK`
// };
// // 获取`headers`属性
// let headers = match input.attrs.iter().find(|attr| attr.path().is_ident("headers")) {
// Some(attr) => {
// let headers_lit = attr.parse_args::<Ident>().unwrap(); // 解析`headers`属性值
// quote! { #headers_lit } // 生成头部信息
// },
// None => quote! { hyper::HeaderMap::new() }, // 默认头部信息为空
// };
// 生成实现`IntoResponse` trait的代码
quote! {
impl axum::response::IntoResponse for #name {
fn into_response(self) -> axum::http::Response<axum::body::Body> {
// let mut builder = hyper::Response::builder();
// .status(#status) // 设置状态码
// .headers(#headers); // 设置头部信息
let body = serde_json::to_string(&self).expect("Failed to serialize response"); // 序列化结构体为JSON字符串
axum::http::Response::new(hyper::body::Body::from(body)) // 构建响应
}
}
}
},
},
Data::Enum(_) | Data::Union(_) => {
// 不支持枚举或联合体
unimplemented!("IntoResponse derive is not implemented for enums or unions.")
},
};
gen.into()
}

168
macro/src/route.rs Normal file
View File

@ -0,0 +1,168 @@
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 syn::*;
struct Args {
vars: Vec<syn::Expr>
}
impl Parse for Args {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
let vars = Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated(input)?;
Ok(Args {
vars: vars.into_iter().collect(),
})
}
}
impl Args {
pub fn get_method(&self) -> syn::Result<syn::Expr> {
match self.vars.get(0) {
Some(var) => Ok(var.clone()),
None => return Err(syn::Error::new(
Span::call_site().into(),
"No Method was provided"
))
}
}
pub fn get_route(&self) -> syn::Result<syn::Expr> {
match self.vars.get(0) {
Some(var) => Ok(var.clone()),
None => return Err(syn::Error::new(
Span::call_site().into(),
"No Route was provided"
))
}
}
}
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 vis = func.vis.clone();
let ident = func.sig.ident.clone();
let method = args.get_method().unwrap();
let route = args.get_route().unwrap();
let expanded = quote! {
#[allow(non_camel_case_types)]
#vis struct #ident;
impl #ident {
#vis fn route() -> axum::Router {
#func
axum::Router::new().route(#route, #method (#ident))
}
}
};
expanded.into()
}
pub fn gen_get_route(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as Args);
let func = parse_macro_input!(item as ItemFn);
let vis = func.vis.clone();
let ident = func.sig.ident.clone();
let route = args.get_route().unwrap();
let expanded = quote! {
#vis fn #ident () -> (&'static str, axum::routing::method_routing::MethodRouter) {
#func
(#route, axum::routing::get(#ident))
}
};
expanded.into()
}
pub fn gen_post_route(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as Args);
let func = parse_macro_input!(item as ItemFn);
let vis = func.vis.clone();
let ident = func.sig.ident.clone();
let route = args.get_route().unwrap();
let expanded = quote! {
#vis fn #ident () -> (&'static str, axum::routing::method_routing::MethodRouter) {
#func
(#route, axum::routing::post(#ident))
}
};
expanded.into()
}
pub fn gen_delete_route(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as Args);
let func = parse_macro_input!(item as ItemFn);
let vis = func.vis.clone();
let ident = func.sig.ident.clone();
let route = args.get_route().unwrap();
let expanded = quote! {
#vis fn #ident () -> (&'static str, axum::routing::method_routing::MethodRouter) {
#func
(#route, axum::routing::delete(#ident))
}
};
expanded.into()
}
pub fn gen_put_route(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as Args);
let func = parse_macro_input!(item as ItemFn);
let vis = func.vis.clone();
let ident = func.sig.ident.clone();
let route = args.get_route().unwrap();
let expanded = quote! {
#vis fn #ident () -> (&'static str, axum::routing::method_routing::MethodRouter) {
#func
(#route, axum::routing::put(#ident))
}
};
expanded.into()
}
pub fn gen_option_route(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as Args);
let func = parse_macro_input!(item as ItemFn);
let vis = func.vis.clone();
let ident = func.sig.ident.clone();
let route = args.get_route().unwrap();
let expanded = quote! {
#vis fn #ident () -> (&'static str, axum::routing::method_routing::MethodRouter) {
#func
(#route, axum::routing::option(#ident))
}
};
expanded.into()
}

View File

@ -1,10 +1,14 @@
extern crate macros;
use domain::dto::feedback::FeedbackAdd;
use domain::dto::pageable::PageParams;
use domain::vo::feedback::FeedbackPageable;
use library::context::Context;
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;
use crate::service;
@ -31,4 +35,18 @@ pub async fn get_feedback_list_by_page(
page_params.page_size.unwrap(),
)
.await
}
// #[route(get, "/feedback/:page/:pageSize")]
#[get("/feedback/:page/:pageSize")]
pub async fn get_feedback_list(
context: Context,
PathVar(page_params): PathVar<PageParams>,
) -> ResResult<FeedbackPageable> {
service::feedback_service::get_feedback_list_by_page(
context,
page_params.page.unwrap(),
page_params.page_size.unwrap(),
)
.await
}

View File

@ -1,10 +1,16 @@
use axum::{routing::post, Router};
use axum::{routing::{get, post, MethodRouter}, Router};
use library::typed_router::TypedRouter;
pub mod account_controller;
pub mod feedback_controller;
pub fn init() -> Router {
Router::new()
// let a = feedback_controller::get_feedback_list;
// axum::routing::method_routing::MethodRouter::new()
let router = Router::new()
.route(
"/account/google",
post(account_controller::authenticate_google),
@ -22,4 +28,17 @@ pub fn init() -> Router {
post(feedback_controller::add_feedback)
.get(feedback_controller::get_feedback_list_by_page)
)
}
.typed_route(feedback_controller::get_feedback_list)
// .merge(feedback_controller::get_feedback_list::route())
// .typed_route(route)
// .route(
// "/feedback/:page/:pageSize",
// get(feedback_controller::get_feedback_list)
// )
;
return router;
}
// fn route() -> (&'static str, MethodRouter) {
// ("/feedback", axum::routing::get(feedback_controller::get_feedback_list_by_page))
// }