在Axum Rust中实现ValidatorRequest时遭遇Trait冲突问题
解决Axum中请求验证器的Trait冲突问题
问题背景
在Rust的Axum框架中实现请求体验证器时,遇到了FromRequest trait的冲突错误,需求是能自动验证带有请求体的用户请求。
原实现代码
use async_trait::async_trait; use axum::extract::{rejection::JsonRejection, FromRequest}; use axum::{body::HttpBody, http::Request, BoxError, Json}; use axum::response::{IntoResponse, Response}; use serde::de::DeserializeOwned; use thiserror::Error; use validator::Validate; use crate::v3::config::response::ApiErrorResponse; #[derive(Debug, Error)] pub enum RequestError { #[error(transparent)] ValidationError(#[from] validator::ValidationErrors), #[error(transparent)] JsonRejection(#[from] JsonRejection), } #[derive(Debug, Clone, Copy, Default)] pub struct ValidatedRequest<T>(pub T); #[async_trait] impl<T, S, B> FromRequest<S, B> for ValidatedRequest<T> where T: DeserializeOwned + Validate, S: Send + Sync, B: HttpBody + Send + 'static, B::Data: Send, B::Error: Into<BoxError>, { type Rejection = RequestError; async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> { let Json(value) = Json::<T>::from_request(req, state).await?; value.validate()?; Ok(ValidatedRequest(value)) } } impl IntoResponse for RequestError { fn into_response(self) -> Response { match self { RequestError::ValidationError(_) => { ApiErrorResponse::send(400, Some(self.to_string().replace('\n', ", "))) } RequestError::JsonRejection(_) => ApiErrorResponse::send(400, Some(self.to_string())), } } }
错误信息
error[E0119]: conflicting implementations of trait `FromRequest<_, axum_core::extract::private::ViaParts>` for type `ValidatedRequest<_>` --> src/v3/error/request.rs:21:1 | 21 | / impl<T, S, B> FromRequest<S, B> for ValidatedRequest<T> 22 | | where 23 | | T: DeserializeOwned + Validate, 24 | | S: Send + Sync, 25 | | B: HttpBody + Send + 'static, 26 | | B::Data: Send, 27 | | B::Error: Into<BoxError>, | |_________________________________^ | = note: conflicting implementation in crate `axum_core`: - impl<S, T> FromRequest<S, axum_core::extract::private::ViaParts> for T where S: Send, S: Sync, T: FromRequestParts<S>;; = note: downstream crates may implement trait `axum::extract::FromRequestParts<_>` for type `v3::error::request::ValidatedRequest<_>` = note: upstream crates may add a new impl of trait `axum::body::HttpBody` for type `axum_core::extract::private::ViaParts` in future versions
问题原因
冲突源于Axum的axum_core crate中的一个泛型 blanket 实现:当类型T实现了FromRequestParts时,自动为其实现针对ViaParts类型的FromRequest。我们的ValidatedRequest<T>的泛型FromRequest实现覆盖了所有HttpBody类型,包括ViaParts,导致编译器无法区分两个可行的实现。
解决方法
方案一:排除冲突类型
通过引入Axum内部的Sealed trait(ViaParts实现了该trait,且外部类型无法实现),在FromRequest的约束中排除这类内部类型:
修改后的代码:
use async_trait::async_trait; use axum::extract::{rejection::JsonRejection, FromRequest}; use axum::{body::HttpBody, http::Request, BoxError, Json}; use axum::response::{IntoResponse, Response}; use serde::de::DeserializeOwned; use thiserror::Error; use validator::Validate; use crate::v3::config::response::ApiErrorResponse; // 引入Axum内部的Sealed trait use axum_core::extract::private::Sealed; #[derive(Debug, Error)] pub enum RequestError { #[error(transparent)] ValidationError(#[from] validator::ValidationErrors), #[error(transparent)] JsonRejection(#[from] JsonRejection), } #[derive(Debug, Clone, Copy, Default)] pub struct ValidatedRequest<T>(pub T); #[async_trait] impl<T, S, B> FromRequest<S, B> for ValidatedRequest<T> where T: DeserializeOwned + Validate, S: Send + Sync, B: HttpBody + Send + 'static + !Sealed, // 添加约束排除ViaParts类型 B::Data: Send, B::Error: Into<BoxError>, { type Rejection = RequestError; async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> { let Json(value) = Json::<T>::from_request(req, state).await?; value.validate()?; Ok(ValidatedRequest(value)) } } impl IntoResponse for RequestError { fn into_response(self) -> Response { match self { RequestError::ValidationError(_) => { ApiErrorResponse::send(400, Some(self.to_string().replace('\n', ", "))) } RequestError::JsonRejection(_) => ApiErrorResponse::send(400, Some(self.to_string())), } } }
方案二:handler内手动验证
如果不想依赖Axum内部私有trait,可直接在handler中使用Json<T>并手动触发验证:
// 假设User是实现了Validate的结构体 async fn create_user(Json(user): Json<User>) -> Result<impl IntoResponse, RequestError> { user.validate()?; // 业务逻辑处理 Ok(Json(UserResponse { id: 1 })) }
方案一更适合需要在多个handler中复用验证逻辑的场景,方案二更简洁直接。
内容的提问来源于stack exchange,提问作者Dan
相关产品推荐
相关产品推荐

