axum-extra/query: Use rejection macros for QueryRejection (#3122)

This commit is contained in:
Tobias Bieniek 2024-12-27 23:02:41 +01:00 committed by GitHub
parent 28d8d9b747
commit 5f82540cc1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,11 +1,8 @@
use axum::{ use axum::extract::FromRequestParts;
extract::FromRequestParts, use axum_core::__composite_rejection as composite_rejection;
response::{IntoResponse, Response}, use axum_core::__define_rejection as define_rejection;
Error, use http::request::Parts;
};
use http::{request::Parts, StatusCode};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use std::fmt;
/// Extractor that deserializes query strings into some type. /// Extractor that deserializes query strings into some type.
/// ///
@ -93,63 +90,27 @@ where
let deserializer = let deserializer =
serde_html_form::Deserializer::new(form_urlencoded::parse(query.as_bytes())); serde_html_form::Deserializer::new(form_urlencoded::parse(query.as_bytes()));
let value = serde_path_to_error::deserialize(deserializer) let value = serde_path_to_error::deserialize(deserializer)
.map_err(|err| QueryRejection::FailedToDeserializeQueryString(Error::new(err)))?; .map_err(FailedToDeserializeQueryString::from_err)?;
Ok(Query(value)) Ok(Query(value))
} }
} }
axum_core::__impl_deref!(Query); axum_core::__impl_deref!(Query);
/// Rejection used for [`Query`]. define_rejection! {
/// #[status = BAD_REQUEST]
/// Contains one variant for each way the [`Query`] extractor can fail. #[body = "Failed to deserialize query string"]
#[derive(Debug)] /// Rejection type used if the [`Query`] extractor is unable to
#[non_exhaustive] /// deserialize the query string into the target type.
#[cfg(feature = "query")] pub struct FailedToDeserializeQueryString(Error);
pub enum QueryRejection {
#[allow(missing_docs)]
FailedToDeserializeQueryString(Error),
} }
impl QueryRejection { composite_rejection! {
/// Get the status code used for this rejection. /// Rejection used for [`Query`].
pub fn status(&self) -> StatusCode { ///
match self { /// Contains one variant for each way the [`Query`] extractor can fail.
Self::FailedToDeserializeQueryString(_) => StatusCode::BAD_REQUEST, pub enum QueryRejection {
} FailedToDeserializeQueryString,
}
}
impl IntoResponse for QueryRejection {
fn into_response(self) -> Response {
let status = self.status();
match self {
Self::FailedToDeserializeQueryString(inner) => {
let body = format!("Failed to deserialize query string: {inner}");
axum_core::__log_rejection!(
rejection_type = Self,
body_text = body,
status = status,
);
(status, body).into_response()
}
}
}
}
impl fmt::Display for QueryRejection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FailedToDeserializeQueryString(inner) => inner.fmt(f),
}
}
}
impl std::error::Error for QueryRejection {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::FailedToDeserializeQueryString(inner) => Some(inner),
}
} }
} }
@ -207,9 +168,8 @@ where
if let Some(query) = parts.uri.query() { if let Some(query) = parts.uri.query() {
let deserializer = let deserializer =
serde_html_form::Deserializer::new(form_urlencoded::parse(query.as_bytes())); serde_html_form::Deserializer::new(form_urlencoded::parse(query.as_bytes()));
let value = serde_path_to_error::deserialize(deserializer).map_err(|err| { let value = serde_path_to_error::deserialize(deserializer)
OptionalQueryRejection::FailedToDeserializeQueryString(Error::new(err)) .map_err(FailedToDeserializeQueryString::from_err)?;
})?;
Ok(OptionalQuery(Some(value))) Ok(OptionalQuery(Some(value)))
} else { } else {
Ok(OptionalQuery(None)) Ok(OptionalQuery(None))
@ -233,42 +193,12 @@ impl<T> std::ops::DerefMut for OptionalQuery<T> {
} }
} }
/// Rejection used for [`OptionalQuery`]. composite_rejection! {
/// /// Rejection used for [`OptionalQuery`].
/// Contains one variant for each way the [`OptionalQuery`] extractor can fail. ///
#[derive(Debug)] /// Contains one variant for each way the [`OptionalQuery`] extractor can fail.
#[non_exhaustive] pub enum OptionalQueryRejection {
#[cfg(feature = "query")] FailedToDeserializeQueryString,
pub enum OptionalQueryRejection {
#[allow(missing_docs)]
FailedToDeserializeQueryString(Error),
}
impl IntoResponse for OptionalQueryRejection {
fn into_response(self) -> Response {
match self {
Self::FailedToDeserializeQueryString(inner) => (
StatusCode::BAD_REQUEST,
format!("Failed to deserialize query string: {inner}"),
)
.into_response(),
}
}
}
impl fmt::Display for OptionalQueryRejection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FailedToDeserializeQueryString(inner) => inner.fmt(f),
}
}
}
impl std::error::Error for OptionalQueryRejection {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::FailedToDeserializeQueryString(inner) => Some(inner),
}
} }
} }
@ -279,6 +209,7 @@ mod tests {
use axum::routing::{get, post}; use axum::routing::{get, post};
use axum::Router; use axum::Router;
use http::header::CONTENT_TYPE; use http::header::CONTENT_TYPE;
use http::StatusCode;
use serde::Deserialize; use serde::Deserialize;
#[tokio::test] #[tokio::test]