diff --git a/frameworks/Rust/axum-xitca/Cargo.lock b/frameworks/Rust/axum-xitca/Cargo.lock index 655b700..0a00e4d 100644 --- a/frameworks/Rust/axum-xitca/Cargo.lock +++ b/frameworks/Rust/axum-xitca/Cargo.lock @@ -71,7 +71,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -228,6 +228,41 @@ dependencies = [ "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]] name = "deranged" version = "0.3.11" @@ -376,6 +411,22 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "io-uring" version = "0.6.4" @@ -643,6 +694,7 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", + "validator", "xitca-http", "xitca-io", "xitca-server", @@ -675,6 +727,30 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "proc-macro2" version = "1.0.86" @@ -802,7 +878,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -835,7 +911,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -927,6 +1003,22 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "syn" version = "2.0.77" @@ -967,7 +1059,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -1011,6 +1103,21 @@ dependencies = [ "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]] name = "tokio" version = "1.40.0" @@ -1037,7 +1144,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -1144,7 +1251,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", ] [[package]] @@ -1195,18 +1302,44 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "uuid" version = "1.10.0" @@ -1216,6 +1349,36 @@ dependencies = [ "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]] name = "valuable" version = "0.1.0" @@ -1256,7 +1419,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -1278,7 +1441,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/frameworks/Rust/axum-xitca/Cargo.toml b/frameworks/Rust/axum-xitca/Cargo.toml index 9272be9..5ed3f74 100644 --- a/frameworks/Rust/axum-xitca/Cargo.toml +++ b/frameworks/Rust/axum-xitca/Cargo.toml @@ -8,6 +8,7 @@ tokio = { version = "1.40.0", features = ["full"] } axum = { version = "0.7.6", default-features = false, features = [ "json", "query", + "form", ] } xitca-server = { version = "0.4.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" xitca-unsafe-collection = "0.2.0" jsonwebtoken = "9.3.0" +validator = { version = "0.18.1", features = ["derive"] } diff --git a/frameworks/Rust/axum-xitca/src/error.rs b/frameworks/Rust/axum-xitca/src/error.rs index b92b11d..732171c 100644 --- a/frameworks/Rust/axum-xitca/src/error.rs +++ b/frameworks/Rust/axum-xitca/src/error.rs @@ -20,6 +20,8 @@ pub enum AppError { AxumFormRejection(#[from] FormRejection), #[error(transparent)] AxumJsonRejection(#[from] JsonRejection), + #[error(transparent)] + ValidationError(#[from] validator::ValidationErrors), // route // 路由通常错误 错误信息直接返回用户 // #[error("{0}")] @@ -74,17 +76,21 @@ impl IntoResponse for AppError { use ErrorCode::*; 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::AxumFormRejection(_) | AppError::AxumJsonRejection(_) => ( StatusCode::BAD_REQUEST, ParameterIncorrect, self.to_string(), ), - // route - // AppError::AuthorizeFailed(err) => { - // (StatusCode::UNAUTHORIZED, AuthorizeFailed, err.to_string()) - // } - // AppError::UserConflict(err) => (StatusCode::CONFLICT, UserConflict, err.to_string()), + AppError::ValidationError(_) => { + let message = format!("Input validation error: [{self}]").replace('\n', ", "); + (StatusCode::BAD_REQUEST, ParameterIncorrect, message) + } }; let body = Json(json!({ "code": code, diff --git a/frameworks/Rust/axum-xitca/src/routes/mod.rs b/frameworks/Rust/axum-xitca/src/routes/mod.rs index 633c501..06fd227 100644 --- a/frameworks/Rust/axum-xitca/src/routes/mod.rs +++ b/frameworks/Rust/axum-xitca/src/routes/mod.rs @@ -6,7 +6,7 @@ use axum::{ http::{request::Parts, StatusCode, Uri}, middleware, response::{IntoResponse, Response}, - routing::get, + routing::{get, post}, Json, RequestPartsExt, Router, }; use serde::Serialize; @@ -20,6 +20,7 @@ use crate::{ }; mod json; +mod user; #[derive(Debug, Serialize)] pub struct RouteResponse @@ -49,6 +50,7 @@ pub fn routes() -> Router { let router = Router::new() .route("/", get(hello).post(hello)) .route("/json", get(json::json).post(json::json)) + .route("/user", post(user::register)) .layer( ServiceBuilder::new() .layer(middleware::from_fn(add_version)) diff --git a/frameworks/Rust/axum-xitca/src/routes/user.rs b/frameworks/Rust/axum-xitca/src/routes/user.rs new file mode 100644 index 0000000..442d620 --- /dev/null +++ b/frameworks/Rust/axum-xitca/src/routes/user.rs @@ -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, +) -> RouteResult { + let data = RegisterData { + username: user.username, + }; + let res = RouteResponse { + data, + ..Default::default() + }; + Ok(Json(res)) +} diff --git a/frameworks/Rust/axum-xitca/src/utils/mod.rs b/frameworks/Rust/axum-xitca/src/utils/mod.rs index 2ebdbd4..fd53b51 100644 --- a/frameworks/Rust/axum-xitca/src/utils/mod.rs +++ b/frameworks/Rust/axum-xitca/src/utils/mod.rs @@ -2,6 +2,7 @@ use tokio::signal; use tracing_subscriber::{fmt, prelude::*, registry, EnvFilter}; pub mod jwt; +pub mod validator; /// Initializes the logger for tracing. pub fn init_logger() { diff --git a/frameworks/Rust/axum-xitca/src/utils/validator.rs b/frameworks/Rust/axum-xitca/src/utils/validator.rs new file mode 100644 index 0000000..f30c74a --- /dev/null +++ b/frameworks/Rust/axum-xitca/src/utils/validator.rs @@ -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(pub T); + +#[async_trait] +impl FromRequest for ValidatedJson +where + T: DeserializeOwned + Validate, + S: Send + Sync, + Form: FromRequest, +{ + type Rejection = AppError; + + async fn from_request(req: Request, state: &S) -> AppResult { + let Json(value) = Json::::from_request(req, state).await?; + value.validate()?; + Ok(ValidatedJson(value)) + } +}