feat(axum-xitca): add json validation

This commit is contained in:
xfy
2024-09-27 13:50:30 +08:00
parent ce801b95b9
commit e6fc2f86e6
7 changed files with 248 additions and 14 deletions

View File

@ -71,7 +71,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.77",
] ]
[[package]] [[package]]
@ -228,6 +228,41 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.77",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn 2.0.77",
]
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.3.11" version = "0.3.11"
@ -376,6 +411,22 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]] [[package]]
name = "io-uring" name = "io-uring"
version = "0.6.4" version = "0.6.4"
@ -643,6 +694,7 @@ dependencies = [
"tower-http", "tower-http",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"validator",
"xitca-http", "xitca-http",
"xitca-io", "xitca-io",
"xitca-server", "xitca-server",
@ -675,6 +727,30 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.86" version = "1.0.86"
@ -802,7 +878,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.77",
] ]
[[package]] [[package]]
@ -835,7 +911,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.77",
] ]
[[package]] [[package]]
@ -927,6 +1003,22 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"unicode-ident",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.77" version = "2.0.77"
@ -967,7 +1059,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.77",
] ]
[[package]] [[package]]
@ -1011,6 +1103,21 @@ dependencies = [
"time-core", "time-core",
] ]
[[package]]
name = "tinyvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.40.0" version = "1.40.0"
@ -1037,7 +1144,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.77",
] ]
[[package]] [[package]]
@ -1144,7 +1251,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.77",
] ]
[[package]] [[package]]
@ -1195,18 +1302,44 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "unicode-bidi"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.13" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-normalization"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.9.0" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.10.0" version = "1.10.0"
@ -1216,6 +1349,36 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "validator"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e"
dependencies = [
"idna",
"once_cell",
"regex",
"serde",
"serde_derive",
"serde_json",
"url",
"validator_derive",
]
[[package]]
name = "validator_derive"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df0bcf92720c40105ac4b2dda2a4ea3aa717d4d6a862cc217da653a4bd5c6b10"
dependencies = [
"darling",
"once_cell",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.77",
]
[[package]] [[package]]
name = "valuable" name = "valuable"
version = "0.1.0" version = "0.1.0"
@ -1256,7 +1419,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.77",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -1278,7 +1441,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.77",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]

View File

@ -8,6 +8,7 @@ tokio = { version = "1.40.0", features = ["full"] }
axum = { version = "0.7.6", default-features = false, features = [ axum = { version = "0.7.6", default-features = false, features = [
"json", "json",
"query", "query",
"form",
] } ] }
xitca-server = { version = "0.4.0", features = ["io-uring"] } xitca-server = { version = "0.4.0", features = ["io-uring"] }
xitca-http = { version = "0.6.0", features = ["io-uring"] } xitca-http = { version = "0.6.0", features = ["io-uring"] }
@ -29,3 +30,4 @@ serde_repr = "0.1.19"
http-body = "1.0.1" http-body = "1.0.1"
xitca-unsafe-collection = "0.2.0" xitca-unsafe-collection = "0.2.0"
jsonwebtoken = "9.3.0" jsonwebtoken = "9.3.0"
validator = { version = "0.18.1", features = ["derive"] }

View File

@ -20,6 +20,8 @@ pub enum AppError {
AxumFormRejection(#[from] FormRejection), AxumFormRejection(#[from] FormRejection),
#[error(transparent)] #[error(transparent)]
AxumJsonRejection(#[from] JsonRejection), AxumJsonRejection(#[from] JsonRejection),
#[error(transparent)]
ValidationError(#[from] validator::ValidationErrors),
// route // route
// 路由通常错误 错误信息直接返回用户 // 路由通常错误 错误信息直接返回用户
// #[error("{0}")] // #[error("{0}")]
@ -74,17 +76,21 @@ impl IntoResponse for AppError {
use ErrorCode::*; use ErrorCode::*;
let (status_code, code, err_message) = match self { let (status_code, code, err_message) = match self {
// route
// AppError::AuthorizeFailed(err) => {
// (StatusCode::UNAUTHORIZED, AuthorizeFailed, err.to_string())
// }
// AppError::UserConflict(err) => (StatusCode::CONFLICT, UserConflict, err.to_string()),
AppError::Any(err) => log_internal_error(err), AppError::Any(err) => log_internal_error(err),
AppError::AxumFormRejection(_) | AppError::AxumJsonRejection(_) => ( AppError::AxumFormRejection(_) | AppError::AxumJsonRejection(_) => (
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
ParameterIncorrect, ParameterIncorrect,
self.to_string(), self.to_string(),
), ),
// route AppError::ValidationError(_) => {
// AppError::AuthorizeFailed(err) => { let message = format!("Input validation error: [{self}]").replace('\n', ", ");
// (StatusCode::UNAUTHORIZED, AuthorizeFailed, err.to_string()) (StatusCode::BAD_REQUEST, ParameterIncorrect, message)
// } }
// AppError::UserConflict(err) => (StatusCode::CONFLICT, UserConflict, err.to_string()),
}; };
let body = Json(json!({ let body = Json(json!({
"code": code, "code": code,

View File

@ -6,7 +6,7 @@ use axum::{
http::{request::Parts, StatusCode, Uri}, http::{request::Parts, StatusCode, Uri},
middleware, middleware,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
routing::get, routing::{get, post},
Json, RequestPartsExt, Router, Json, RequestPartsExt, Router,
}; };
use serde::Serialize; use serde::Serialize;
@ -20,6 +20,7 @@ use crate::{
}; };
mod json; mod json;
mod user;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct RouteResponse<T> pub struct RouteResponse<T>
@ -49,6 +50,7 @@ pub fn routes() -> Router {
let router = Router::new() let router = Router::new()
.route("/", get(hello).post(hello)) .route("/", get(hello).post(hello))
.route("/json", get(json::json).post(json::json)) .route("/json", get(json::json).post(json::json))
.route("/user", post(user::register))
.layer( .layer(
ServiceBuilder::new() ServiceBuilder::new()
.layer(middleware::from_fn(add_version)) .layer(middleware::from_fn(add_version))

View File

@ -0,0 +1,32 @@
use crate::utils::validator::ValidatedJson;
use super::{RouteResponse, RouteResult};
use axum::Json;
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Serialize, Deserialize, Validate)]
pub struct RegisterPayload {
#[validate(length(min = 1, message = "cannot be empyt"))]
pub username: String,
pub email: String,
#[validate(length(min = 1, message = "cannot be empyt"))]
pub password: String,
}
#[derive(Serialize, Deserialize, Default)]
pub struct RegisterData {
pub username: String,
}
pub async fn register(
ValidatedJson(user): ValidatedJson<RegisterPayload>,
) -> RouteResult<RegisterData> {
let data = RegisterData {
username: user.username,
};
let res = RouteResponse {
data,
..Default::default()
};
Ok(Json(res))
}

View File

@ -2,6 +2,7 @@ use tokio::signal;
use tracing_subscriber::{fmt, prelude::*, registry, EnvFilter}; use tracing_subscriber::{fmt, prelude::*, registry, EnvFilter};
pub mod jwt; pub mod jwt;
pub mod validator;
/// Initializes the logger for tracing. /// Initializes the logger for tracing.
pub fn init_logger() { pub fn init_logger() {

View File

@ -0,0 +1,28 @@
use axum::{
async_trait,
extract::{rejection::FormRejection, FromRequest, Request},
Form, Json,
};
use serde::de::DeserializeOwned;
use validator::Validate;
use crate::error::{AppError, AppResult};
#[derive(Debug, Clone, Copy, Default)]
pub struct ValidatedJson<T>(pub T);
#[async_trait]
impl<T, S> FromRequest<S> for ValidatedJson<T>
where
T: DeserializeOwned + Validate,
S: Send + Sync,
Form<T>: FromRequest<S, Rejection = FormRejection>,
{
type Rejection = AppError;
async fn from_request(req: Request, state: &S) -> AppResult<Self, Self::Rejection> {
let Json(value) = Json::<T>::from_request(req, state).await?;
value.validate()?;
Ok(ValidatedJson(value))
}
}