fix: 改进错误处理机制,支持查询参数反序列化和多种Axum响应类型转换

This commit is contained in:
caoqianming 2026-03-04 14:11:59 +08:00
parent 550c7b3974
commit f9284303f6
4 changed files with 211 additions and 12 deletions

157
Cargo.lock generated
View File

@ -467,8 +467,18 @@ version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core",
"darling_macro",
"darling_core 0.20.11",
"darling_macro 0.20.11",
]
[[package]]
name = "darling"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
dependencies = [
"darling_core 0.21.3",
"darling_macro 0.21.3",
]
[[package]]
@ -485,13 +495,38 @@ dependencies = [
"syn",
]
[[package]]
name = "darling_core"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"darling_core 0.20.11",
"quote",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
dependencies = [
"darling_core 0.21.3",
"quote",
"syn",
]
@ -533,6 +568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
"powerfmt",
"serde_core",
]
[[package]]
@ -570,6 +606,12 @@ version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dyn-clone"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]]
name = "either"
version = "1.15.0"
@ -771,6 +813,7 @@ dependencies = [
"dotenv",
"serde",
"serde_json",
"serde_with",
"sqlx",
"time",
"tokio",
@ -838,6 +881,12 @@ dependencies = [
"wasip3",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.15.5"
@ -1122,6 +1171,17 @@ dependencies = [
"icu_properties",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]]
name = "indexmap"
version = "2.13.0"
@ -1604,6 +1664,26 @@ dependencies = [
"bitflags",
]
[[package]]
name = "ref-cast"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "regex"
version = "1.12.3"
@ -1680,6 +1760,30 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "schemars"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
dependencies = [
"dyn-clone",
"ref-cast",
"serde",
"serde_json",
]
[[package]]
name = "schemars"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
dependencies = [
"dyn-clone",
"ref-cast",
"serde",
"serde_json",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -1758,13 +1862,44 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_with"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9"
dependencies = [
"base64",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.13.0",
"schemars 0.9.0",
"schemars 1.2.1",
"serde_core",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0"
dependencies = [
"darling 0.21.3",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"indexmap 2.13.0",
"itoa",
"ryu",
"serde",
@ -1904,7 +2039,7 @@ dependencies = [
"futures-util",
"hashbrown 0.15.5",
"hashlink",
"indexmap",
"indexmap 2.13.0",
"log",
"memchr",
"once_cell",
@ -2558,7 +2693,7 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca"
dependencies = [
"darling",
"darling 0.20.11",
"once_cell",
"proc-macro-error2",
"proc-macro2",
@ -2676,7 +2811,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"indexmap 2.13.0",
"wasm-encoder",
"wasmparser",
]
@ -2689,7 +2824,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"indexmap 2.13.0",
"semver",
]
@ -3012,7 +3147,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"indexmap 2.13.0",
"prettyplease",
"syn",
"wasm-metadata",
@ -3043,7 +3178,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"indexmap 2.13.0",
"log",
"serde",
"serde_derive",
@ -3062,7 +3197,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"indexmap 2.13.0",
"log",
"semver",
"serde",

View File

@ -17,6 +17,7 @@ sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "chrono", "uu
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_with = "3.0"
# Time handling
chrono = "0.4"

View File

@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use validator::Validate;
/// 分页响应结构
@ -31,12 +32,15 @@ impl<T> PaginatedResponse<T> {
}
/// 分页查询参数
#[serde_as]
#[derive(Deserialize, Validate)]
pub struct PaginationParams {
#[validate(range(min = 1))]
#[serde_as(as = "serde_with::DisplayFromStr")]
#[serde(default = "default_page")]
pub page: u32,
#[validate(range(min = -1, max = 100))]
#[serde_as(as = "serde_with::DisplayFromStr")]
#[serde(default = "default_page_size")]
pub page_size: i32,
}

View File

@ -1,5 +1,15 @@
use anyhow::Error;
use axum::{Json, http::StatusCode, response::IntoResponse};
use axum::{
Json,
http::StatusCode,
response::IntoResponse,
extract::rejection::{
QueryRejection,
PathRejection,
JsonRejection,
FormRejection,
},
};
use serde::Serialize;
use serde_json::Value;
use sqlx::Error as SqlxError;
@ -89,3 +99,52 @@ impl From<SqlxError> for ApiErr {
}
}
}
impl From<QueryRejection> for ApiErr {
fn from(rejection: QueryRejection) -> Self {
tracing::warn!("Query parameter error: {}", rejection);
ApiErr::BadRequest(
"Invalid query parameters".to_string(),
Some(serde_json::json!({
"detail": rejection.to_string()
}))
)
}
}
impl From<PathRejection> for ApiErr {
fn from(rejection: PathRejection) -> Self {
tracing::warn!("Path parameter error: {}", rejection);
ApiErr::BadRequest(
"Invalid path parameter".to_string(),
Some(serde_json::json!({
"detail": rejection.to_string()
}))
)
}
}
impl From<JsonRejection> for ApiErr {
fn from(rejection: JsonRejection) -> Self {
tracing::warn!("JSON parsing error: {}", rejection);
ApiErr::BadRequest(
"Invalid JSON format".to_string(),
Some(serde_json::json!({
"detail": rejection.to_string()
}))
)
}
}
impl From<FormRejection> for ApiErr {
fn from(rejection: FormRejection) -> Self {
tracing::warn!("Form data error: {}", rejection);
ApiErr::BadRequest(
"Invalid form data".to_string(),
Some(serde_json::json!({
"detail": rejection.to_string()
}))
)
}
}