axum_extra/extract/
form.rsuse axum::{
async_trait,
extract::{rejection::RawFormRejection, FromRequest, RawForm, Request},
response::{IntoResponse, Response},
Error, RequestExt,
};
use http::StatusCode;
use serde::de::DeserializeOwned;
use std::fmt;
#[derive(Debug, Clone, Copy, Default)]
#[cfg(feature = "form")]
pub struct Form<T>(pub T);
axum_core::__impl_deref!(Form);
#[async_trait]
impl<T, S> FromRequest<S> for Form<T>
where
T: DeserializeOwned,
S: Send + Sync,
{
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)?;
serde_html_form::from_bytes::<T>(&bytes)
.map(Self)
.map_err(|err| FormRejection::FailedToDeserializeForm(Error::new(err)))
}
}
#[derive(Debug)]
#[non_exhaustive]
#[cfg(feature = "form")]
pub enum FormRejection {
#[allow(missing_docs)]
RawFormRejection(RawFormRejection),
#[allow(missing_docs)]
FailedToDeserializeForm(Error),
}
impl IntoResponse for FormRejection {
fn into_response(self) -> Response {
match self {
Self::RawFormRejection(inner) => inner.into_response(),
Self::FailedToDeserializeForm(inner) => {
let body = format!("Failed to deserialize form: {inner}");
let status = StatusCode::BAD_REQUEST;
axum_core::__log_rejection!(
rejection_type = Self,
body_text = body,
status = status,
);
(status, body).into_response()
}
}
}
}
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),
}
}
}
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),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helpers::*;
use axum::{routing::post, Router};
use http::header::CONTENT_TYPE;
use serde::Deserialize;
#[tokio::test]
async fn supports_multiple_values() {
#[derive(Deserialize)]
struct Data {
#[serde(rename = "value")]
values: Vec<String>,
}
let app = Router::new().route(
"/",
post(|Form(data): Form<Data>| async move { data.values.join(",") }),
);
let client = TestClient::new(app);
let res = client
.post("/")
.header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.body("value=one&value=two")
.await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.text().await, "one,two");
}
}