mirror of
https://github.com/tokio-rs/axum.git
synced 2026-09-07 00:00:12 +02:00
Query/Form: Use serde_path_to_error to report fields that failed to parse (#3081)
This commit is contained in:
@@ -56,7 +56,9 @@ where
|
||||
.await
|
||||
.map_err(FormRejection::RawFormRejection)?;
|
||||
|
||||
serde_html_form::from_bytes::<T>(&bytes)
|
||||
let deserializer = serde_html_form::Deserializer::new(form_urlencoded::parse(&bytes));
|
||||
|
||||
serde_path_to_error::deserialize::<_, T>(deserializer)
|
||||
.map(Self)
|
||||
.map_err(|err| FormRejection::FailedToDeserializeForm(Error::new(err)))
|
||||
}
|
||||
@@ -115,8 +117,10 @@ impl std::error::Error for FormRejection {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_helpers::*;
|
||||
use axum::{routing::post, Router};
|
||||
use axum::routing::{on, post, MethodFilter};
|
||||
use axum::Router;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use mime::APPLICATION_WWW_FORM_URLENCODED;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -143,4 +147,41 @@ mod tests {
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.text().await, "one,two");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn deserialize_error_status_codes() {
|
||||
#[allow(dead_code)]
|
||||
#[derive(Deserialize)]
|
||||
struct Payload {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let app = Router::new().route(
|
||||
"/",
|
||||
on(
|
||||
MethodFilter::GET.or(MethodFilter::POST),
|
||||
|_: Form<Payload>| async {},
|
||||
),
|
||||
);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/?a=false").await;
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(
|
||||
res.text().await,
|
||||
"Failed to deserialize form: a: invalid digit found in string"
|
||||
);
|
||||
|
||||
let res = client
|
||||
.post("/")
|
||||
.header(CONTENT_TYPE, APPLICATION_WWW_FORM_URLENCODED.as_ref())
|
||||
.body("a=false")
|
||||
.await;
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(
|
||||
res.text().await,
|
||||
"Failed to deserialize form: a: invalid digit found in string"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,9 @@ where
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
||||
let query = parts.uri.query().unwrap_or_default();
|
||||
let value = serde_html_form::from_str(query)
|
||||
let deserializer =
|
||||
serde_html_form::Deserializer::new(form_urlencoded::parse(query.as_bytes()));
|
||||
let value = serde_path_to_error::deserialize(deserializer)
|
||||
.map_err(|err| QueryRejection::FailedToDeserializeQueryString(Error::new(err)))?;
|
||||
Ok(Query(value))
|
||||
}
|
||||
@@ -121,7 +123,9 @@ where
|
||||
_state: &S,
|
||||
) -> Result<Option<Self>, Self::Rejection> {
|
||||
if let Some(query) = parts.uri.query() {
|
||||
let value = serde_html_form::from_str(query)
|
||||
let deserializer =
|
||||
serde_html_form::Deserializer::new(form_urlencoded::parse(query.as_bytes()));
|
||||
let value = serde_path_to_error::deserialize(deserializer)
|
||||
.map_err(|err| QueryRejection::FailedToDeserializeQueryString(Error::new(err)))?;
|
||||
Ok(Some(Self(value)))
|
||||
} else {
|
||||
@@ -230,7 +234,9 @@ where
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
||||
if let Some(query) = parts.uri.query() {
|
||||
let value = serde_html_form::from_str(query).map_err(|err| {
|
||||
let deserializer =
|
||||
serde_html_form::Deserializer::new(form_urlencoded::parse(query.as_bytes()));
|
||||
let value = serde_path_to_error::deserialize(deserializer).map_err(|err| {
|
||||
OptionalQueryRejection::FailedToDeserializeQueryString(Error::new(err))
|
||||
})?;
|
||||
Ok(OptionalQuery(Some(value)))
|
||||
@@ -302,7 +308,8 @@ impl std::error::Error for OptionalQueryRejection {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_helpers::*;
|
||||
use axum::{routing::post, Router};
|
||||
use axum::routing::{get, post};
|
||||
use axum::Router;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -331,6 +338,27 @@ mod tests {
|
||||
assert_eq!(res.text().await, "one,two");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn correct_rejection_status_code() {
|
||||
#[derive(Deserialize)]
|
||||
#[allow(dead_code)]
|
||||
struct Params {
|
||||
n: i32,
|
||||
}
|
||||
|
||||
async fn handler(_: Query<Params>) {}
|
||||
|
||||
let app = Router::new().route("/", get(handler));
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/?n=hi").await;
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(
|
||||
res.text().await,
|
||||
"Failed to deserialize query string: n: invalid digit found in string"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn optional_query_supports_multiple_values() {
|
||||
#[derive(Deserialize)]
|
||||
|
||||
Reference in New Issue
Block a user