From 2f64064650c02d447f5a05fcf9259a691d619236 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Fri, 17 Jun 2022 20:23:57 +0200 Subject: [PATCH] Implement `IntoResponse` for `Form` (#1095) --- axum/CHANGELOG.md | 2 + axum/src/extract/mod.rs | 7 +--- axum/src/{extract => }/form.rs | 67 ++++++++++++++++++++++++---------- axum/src/lib.rs | 6 +++ axum/src/response/mod.rs | 4 ++ 5 files changed, 61 insertions(+), 25 deletions(-) rename axum/src/{extract => }/form.rs (78%) diff --git a/axum/CHANGELOG.md b/axum/CHANGELOG.md index 2568c29c0a..b6e9b7e741 100644 --- a/axum/CHANGELOG.md +++ b/axum/CHANGELOG.md @@ -9,9 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **added:** Support resolving host name via `Forwarded` header in `Host` extractor ([#1078]) +- **added:** Implement `IntoResponse` for `Form` ([#1095]) - **change:** axum's MSRV is now 1.56 ([#1098]) [#1078]: https://github.com/tokio-rs/axum/pull/1078 +[#1095]: https://github.com/tokio-rs/axum/pull/1095 [#1098]: https://github.com/tokio-rs/axum/pull/1098 # 0.5.7 (08. June, 2022) diff --git a/axum/src/extract/mod.rs b/axum/src/extract/mod.rs index 6e17d973ab..41a255116a 100644 --- a/axum/src/extract/mod.rs +++ b/axum/src/extract/mod.rs @@ -39,11 +39,8 @@ pub use crate::Json; pub use crate::Extension; #[cfg(feature = "form")] -mod form; - -#[cfg(feature = "form")] -#[doc(inline)] -pub use self::form::Form; +#[doc(no_inline)] +pub use crate::form::Form; #[cfg(feature = "matched-path")] mod matched_path; diff --git a/axum/src/extract/form.rs b/axum/src/form.rs similarity index 78% rename from axum/src/extract/form.rs rename to axum/src/form.rs index 4f0295afd0..9974a46036 100644 --- a/axum/src/extract/form.rs +++ b/axum/src/form.rs @@ -1,24 +1,23 @@ -use super::{has_content_type, rejection::*, FromRequest, RequestParts}; use crate::body::{Bytes, HttpBody}; +use crate::extract::{has_content_type, rejection::*, FromRequest, RequestParts}; use crate::BoxError; use async_trait::async_trait; -use http::Method; +use axum_core::response::{IntoResponse, Response}; +use http::header::CONTENT_TYPE; +use http::{Method, StatusCode}; use serde::de::DeserializeOwned; +use serde::Serialize; use std::ops::Deref; -/// Extractor that deserializes `application/x-www-form-urlencoded` requests -/// into some type. +/// URL encoded extractor and response. /// -/// `T` is expected to implement [`serde::Deserialize`]. +/// # As extractor /// -/// # Example +/// If used as an extractor `Form` will deserialize `application/x-www-form-urlencoded` request +/// bodies into some target type via [`serde::Deserialize`]. /// -/// ```rust,no_run -/// use axum::{ -/// extract::Form, -/// routing::post, -/// Router, -/// }; +/// ```rust +/// use axum::Form; /// use serde::Deserialize; /// /// #[derive(Deserialize)] @@ -27,19 +26,31 @@ use std::ops::Deref; /// password: String, /// } /// -/// async fn accept_form(form: Form) { -/// let sign_up: SignUp = form.0; -/// +/// async fn accept_form(Form(sign_up): Form) { /// // ... /// } +/// ``` +/// +/// Note that `Content-Type: multipart/form-data` requests are not supported. Use [`Multipart`] +/// instead. +/// +/// # As response +/// +/// ```rust +/// use axum::Form; +/// use serde::Serialize; +/// +/// #[derive(Serialize)] +/// struct Payload { +/// value: String, +/// } /// -/// let app = Router::new().route("/sign_up", post(accept_form)); -/// # async { -/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); -/// # }; +/// async fn handler() -> Form { +/// Form(Payload { value: "foo".to_owned() }) +/// } /// ``` /// -/// Note that `Content-Type: multipart/form-data` requests are not supported. +/// [`Multipart`]: crate::extract::Multipart #[cfg_attr(docsrs, doc(cfg(feature = "form")))] #[derive(Debug, Clone, Copy, Default)] pub struct Form(pub T); @@ -74,6 +85,22 @@ where } } +impl IntoResponse for Form +where + T: Serialize, +{ + fn into_response(self) -> Response { + match serde_urlencoded::to_string(&self.0) { + Ok(body) => ( + [(CONTENT_TYPE, mime::APPLICATION_WWW_FORM_URLENCODED.as_ref())], + body, + ) + .into_response(), + Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(), + } + } +} + impl Deref for Form { type Target = T; diff --git a/axum/src/lib.rs b/axum/src/lib.rs index ac977976ef..7fbf51c267 100644 --- a/axum/src/lib.rs +++ b/axum/src/lib.rs @@ -395,6 +395,8 @@ pub(crate) mod macros; mod extension; +#[cfg(feature = "form")] +mod form; #[cfg(feature = "json")] mod json; #[cfg(feature = "headers")] @@ -434,5 +436,9 @@ pub use self::routing::Router; #[cfg(feature = "headers")] pub use self::typed_header::TypedHeader; +#[doc(inline)] +#[cfg(feature = "headers")] +pub use form::Form; + #[doc(inline)] pub use axum_core::{BoxError, Error}; diff --git a/axum/src/response/mod.rs b/axum/src/response/mod.rs index 50c2c458c4..695b931c22 100644 --- a/axum/src/response/mod.rs +++ b/axum/src/response/mod.rs @@ -15,6 +15,10 @@ pub use crate::Json; #[cfg(feature = "headers")] pub use crate::TypedHeader; +#[cfg(feature = "form")] +#[doc(no_inline)] +pub use crate::form::Form; + #[doc(no_inline)] pub use crate::Extension;