Skip to content

Commit

Permalink
axum-extra/form: Use rejection macros for FormRejection (#3111)
Browse files Browse the repository at this point in the history
  • Loading branch information
Turbo87 authored Dec 27, 2024
1 parent 8e78a37 commit ed4d560
Showing 1 changed file with 23 additions and 66 deletions.
89 changes: 23 additions & 66 deletions axum-extra/src/extract/form.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use axum::extract::rejection::RawFormRejection;
use axum::{
extract::{rejection::RawFormRejection, FromRequest, RawForm, Request},
response::{IntoResponse, Response},
Error, RequestExt,
extract::{FromRequest, RawForm, Request},
RequestExt,
};
use http::StatusCode;
use axum_core::__composite_rejection as composite_rejection;
use axum_core::__define_rejection as define_rejection;
use serde::de::DeserializeOwned;
use std::fmt;

/// Extractor that deserializes `application/x-www-form-urlencoded` requests
/// into some type.
Expand Down Expand Up @@ -51,76 +51,32 @@ where
type Rejection = FormRejection;

async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
let RawForm(bytes) = req
.extract()
.await
.map_err(FormRejection::RawFormRejection)?;
let RawForm(bytes) = req.extract().await?;

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)))
}
}

/// Rejection used for [`Form`].
///
/// Contains one variant for each way the [`Form`] extractor can fail.
#[derive(Debug)]
#[non_exhaustive]
#[cfg(feature = "form")]
pub enum FormRejection {
#[allow(missing_docs)]
RawFormRejection(RawFormRejection),
#[allow(missing_docs)]
FailedToDeserializeForm(Error),
}

impl FormRejection {
/// Get the status code used for this rejection.
pub fn status(&self) -> StatusCode {
// Make sure to keep this in sync with `IntoResponse` impl.
match self {
Self::RawFormRejection(inner) => inner.status(),
Self::FailedToDeserializeForm(_) => StatusCode::BAD_REQUEST,
}
}
}
let value = serde_path_to_error::deserialize::<_, T>(deserializer)
.map_err(FailedToDeserializeForm::from_err)?;

impl IntoResponse for FormRejection {
fn into_response(self) -> Response {
let status = self.status();
match self {
Self::RawFormRejection(inner) => inner.into_response(),
Self::FailedToDeserializeForm(inner) => {
let body = format!("Failed to deserialize form: {inner}");
axum_core::__log_rejection!(
rejection_type = Self,
body_text = body,
status = status,
);
(status, body).into_response()
}
}
Ok(Self(value))
}
}

impl fmt::Display for FormRejection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RawFormRejection(inner) => inner.fmt(f),
Self::FailedToDeserializeForm(inner) => inner.fmt(f),
}
}
define_rejection! {
#[status = BAD_REQUEST]
#[body = "Failed to deserialize form"]
/// Rejection type used if the [`Form`](Form) extractor is unable to
/// deserialize the form into the target type.
pub struct FailedToDeserializeForm(Error);
}

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

Expand All @@ -131,6 +87,7 @@ mod tests {
use axum::routing::{on, post, MethodFilter};
use axum::Router;
use http::header::CONTENT_TYPE;
use http::StatusCode;
use mime::APPLICATION_WWW_FORM_URLENCODED;
use serde::Deserialize;

Expand Down

0 comments on commit ed4d560

Please sign in to comment.