Skip to content

Commit

Permalink
axum-extra/query: Use rejection macros for QueryRejection (#3122)
Browse files Browse the repository at this point in the history
  • Loading branch information
Turbo87 authored Dec 27, 2024
1 parent 28d8d9b commit 5f82540
Showing 1 changed file with 26 additions and 95 deletions.
121 changes: 26 additions & 95 deletions axum-extra/src/extract/query.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
use axum::{
extract::FromRequestParts,
response::{IntoResponse, Response},
Error,
};
use http::{request::Parts, StatusCode};
use axum::extract::FromRequestParts;
use axum_core::__composite_rejection as composite_rejection;
use axum_core::__define_rejection as define_rejection;
use http::request::Parts;
use serde::de::DeserializeOwned;
use std::fmt;

/// Extractor that deserializes query strings into some type.
///
Expand Down Expand Up @@ -93,63 +90,27 @@ where
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)))?;
.map_err(FailedToDeserializeQueryString::from_err)?;
Ok(Query(value))
}
}

axum_core::__impl_deref!(Query);

/// Rejection used for [`Query`].
///
/// Contains one variant for each way the [`Query`] extractor can fail.
#[derive(Debug)]
#[non_exhaustive]
#[cfg(feature = "query")]
pub enum QueryRejection {
#[allow(missing_docs)]
FailedToDeserializeQueryString(Error),
}

impl QueryRejection {
/// Get the status code used for this rejection.
pub fn status(&self) -> StatusCode {
match self {
Self::FailedToDeserializeQueryString(_) => StatusCode::BAD_REQUEST,
}
}
}

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),
}
}
define_rejection! {
#[status = BAD_REQUEST]
#[body = "Failed to deserialize query string"]
/// Rejection type used if the [`Query`] extractor is unable to
/// deserialize the query string into the target type.
pub struct FailedToDeserializeQueryString(Error);
}

impl std::error::Error for QueryRejection {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::FailedToDeserializeQueryString(inner) => Some(inner),
}
composite_rejection! {
/// Rejection used for [`Query`].
///
/// Contains one variant for each way the [`Query`] extractor can fail.
pub enum QueryRejection {
FailedToDeserializeQueryString,
}
}

Expand Down Expand Up @@ -207,9 +168,8 @@ where
if let Some(query) = parts.uri.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| {
OptionalQueryRejection::FailedToDeserializeQueryString(Error::new(err))
})?;
let value = serde_path_to_error::deserialize(deserializer)
.map_err(FailedToDeserializeQueryString::from_err)?;
Ok(OptionalQuery(Some(value)))
} else {
Ok(OptionalQuery(None))
Expand All @@ -233,42 +193,12 @@ impl<T> std::ops::DerefMut for OptionalQuery<T> {
}
}

/// Rejection used for [`OptionalQuery`].
///
/// Contains one variant for each way the [`OptionalQuery`] extractor can fail.
#[derive(Debug)]
#[non_exhaustive]
#[cfg(feature = "query")]
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),
}
composite_rejection! {
/// Rejection used for [`OptionalQuery`].
///
/// Contains one variant for each way the [`OptionalQuery`] extractor can fail.
pub enum OptionalQueryRejection {
FailedToDeserializeQueryString,
}
}

Expand All @@ -279,6 +209,7 @@ mod tests {
use axum::routing::{get, post};
use axum::Router;
use http::header::CONTENT_TYPE;
use http::StatusCode;
use serde::Deserialize;

#[tokio::test]
Expand Down

0 comments on commit 5f82540

Please sign in to comment.