Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove request body type param #1154

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ members = [
# with `cargo +nightly update -Z minimal-versions`
"internal-minimal-versions",
]

[patch.crates-io]
hyper = { git = "https://github.com/hyperium/hyper", branch = "david/body-wrap-body" }
2 changes: 1 addition & 1 deletion axum-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ bytes = "1.0"
futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
http = "0.2.7"
http-body = "0.4.5"
hyper = "0.14"
mime = "0.3.16"

[dev-dependencies]
axum = { path = "../axum", version = "0.5" }
futures-util = "0.3"
hyper = "0.14"
tokio = { version = "1.0", features = ["macros"] }
7 changes: 5 additions & 2 deletions axum-core/src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
use crate::{BoxError, Error};
use bytes::Bytes;
use bytes::{Buf, BufMut};
use http_body::Body;
use http_body::Body as _;

#[doc(no_inline)]
pub use hyper::Body;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note this makes hyper a public dependency of axum-core. I think thats a little unfortunate but its required to make impl FromRequest for Request<hyper::Body> work. I think thats an okay trade-off.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also does mean that any crate that depends on axum-core now also depends on hyper. Thats pretty unfortunate 🤔 But yeah not sure there is a way around it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One solution would be to keep the type param on FromRequest and RequestParts but that would mean they get three type params in 0.6 😬

Copy link
Member Author

@davidpdrsn davidpdrsn Jul 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps its also worth considering adding our own body type to axum-core, to avoid the dependency on hyper. I believe hyper's Body will be moved into hyper-utils for 1.0 and we don't wanna have a public dependency on hyper-utils. So we'll probably need our own body type eventually.

Though we have to consider compatibility with things like reqwest. I probably should be possible to extract the body with axum for send it somewhere else with reqwest or a hyper::Client.


/// A boxed [`Body`] trait object.
///
Expand Down Expand Up @@ -55,7 +58,7 @@ where
// THE SOFTWARE.
pub(crate) async fn to_bytes<T>(body: T) -> Result<Bytes, T::Error>
where
T: Body,
T: http_body::Body,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: Wo can remove this method and just use the one in hyper.

{
futures_util::pin_mut!(body);

Expand Down
48 changes: 27 additions & 21 deletions axum-core/src/extract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
//! [`axum::extract`]: https://docs.rs/axum/latest/axum/extract/index.html

use self::rejection::*;
use crate::response::IntoResponse;
use crate::{body::Body, response::IntoResponse, BoxError};
use async_trait::async_trait;
use bytes::Bytes;
use http::{Extensions, HeaderMap, Method, Request, Uri, Version};
use std::convert::Infallible;

Expand Down Expand Up @@ -60,37 +61,41 @@ mod tuple;
/// [`http::Request<B>`]: http::Request
/// [`axum::extract`]: https://docs.rs/axum/latest/axum/extract/index.html
#[async_trait]
pub trait FromRequest<B>: Sized {
pub trait FromRequest: Sized {
/// If the extractor fails it'll use this "rejection" type. A rejection is
/// a kind of error that can be converted into a response.
type Rejection: IntoResponse;

/// Perform the extraction.
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection>;
async fn from_request(req: &mut RequestParts) -> Result<Self, Self::Rejection>;
}

/// The type used with [`FromRequest`] to extract data from requests.
///
/// Has several convenience methods for getting owned parts of the request.
#[derive(Debug)]
pub struct RequestParts<B> {
pub struct RequestParts {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err, why is there a request_parts module, but RequestParts is defined elsewhere?

method: Method,
uri: Uri,
version: Version,
headers: HeaderMap,
extensions: Extensions,
body: Option<B>,
body: Option<Body>,
}

impl<B> RequestParts<B> {
impl RequestParts {
/// Create a new `RequestParts`.
///
/// You generally shouldn't need to construct this type yourself, unless
/// using extractors outside of axum for example to implement a
/// [`tower::Service`].
///
/// [`tower::Service`]: https://docs.rs/tower/lastest/tower/trait.Service.html
pub fn new(req: Request<B>) -> Self {
pub fn new<B>(req: Request<B>) -> Self
where
B: http_body::Body<Data = Bytes> + Send + 'static,
B::Error: Into<BoxError>,
{
let (
http::request::Parts {
method,
Expand All @@ -109,7 +114,7 @@ impl<B> RequestParts<B> {
version,
headers,
extensions,
body: Some(body),
body: Some(Body::wrap_body(body)),
}
}

Expand Down Expand Up @@ -141,7 +146,10 @@ impl<B> RequestParts<B> {
/// }
/// }
/// ```
pub async fn extract<E: FromRequest<B>>(&mut self) -> Result<E, E::Rejection> {
pub async fn extract<E>(&mut self) -> Result<E, E::Rejection>
where
E: FromRequest,
{
E::from_request(self).await
}

Expand All @@ -151,7 +159,7 @@ impl<B> RequestParts<B> {
/// been called.
///
/// [`take_body`]: RequestParts::take_body
pub fn try_into_request(self) -> Result<Request<B>, BodyAlreadyExtracted> {
pub fn try_into_request(self) -> Result<Request<Body>, BodyAlreadyExtracted> {
let Self {
method,
uri,
Expand Down Expand Up @@ -229,46 +237,44 @@ impl<B> RequestParts<B> {
/// Gets a reference to the request body.
///
/// Returns `None` if the body has been taken by another extractor.
pub fn body(&self) -> Option<&B> {
pub fn body(&self) -> Option<&Body> {
self.body.as_ref()
}

/// Gets a mutable reference to the request body.
///
/// Returns `None` if the body has been taken by another extractor.
// this returns `&mut Option<B>` rather than `Option<&mut B>` such that users can use it to set the body.
pub fn body_mut(&mut self) -> &mut Option<B> {
pub fn body_mut(&mut self) -> &mut Option<Body> {
&mut self.body
}

/// Takes the body out of the request, leaving a `None` in its place.
pub fn take_body(&mut self) -> Option<B> {
pub fn take_body(&mut self) -> Option<Body> {
self.body.take()
}
}

#[async_trait]
impl<T, B> FromRequest<B> for Option<T>
impl<T> FromRequest for Option<T>
where
T: FromRequest<B>,
B: Send,
T: FromRequest,
{
type Rejection = Infallible;

async fn from_request(req: &mut RequestParts<B>) -> Result<Option<T>, Self::Rejection> {
async fn from_request(req: &mut RequestParts) -> Result<Option<T>, Self::Rejection> {
Ok(T::from_request(req).await.ok())
}
}

#[async_trait]
impl<T, B> FromRequest<B> for Result<T, T::Rejection>
impl<T> FromRequest for Result<T, T::Rejection>
where
T: FromRequest<B>,
B: Send,
T: FromRequest,
{
type Rejection = Infallible;

async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
async fn from_request(req: &mut RequestParts) -> Result<Self, Self::Rejection> {
Ok(T::from_request(req).await)
}
}
11 changes: 8 additions & 3 deletions axum-core/src/extract/rejection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,14 @@ impl FailedToBufferBody {
where
E: Into<BoxError>,
{
match err.into().downcast::<http_body::LengthLimitError>() {
Ok(err) => Self::LengthLimitError(LengthLimitError::from_err(err)),
Err(err) => Self::UnknownBodyError(UnknownBodyError::from_err(err)),
let err = err.into();
if err
.source()
.map_or(false, |source| source.is::<http_body::LengthLimitError>())
{
Self::LengthLimitError(LengthLimitError::from_err(err))
} else {
Self::UnknownBodyError(UnknownBodyError::from_err(err))
}
}
}
Expand Down
73 changes: 27 additions & 46 deletions axum-core/src/extract/request_parts.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
use super::{rejection::*, FromRequest, RequestParts};
use crate::BoxError;
use crate::body::Body;
use async_trait::async_trait;
use bytes::Bytes;
use http::{Extensions, HeaderMap, Method, Request, Uri, Version};
use std::convert::Infallible;

#[async_trait]
impl<B> FromRequest<B> for Request<B>
where
B: Send,
{
impl FromRequest for Request<Body> {
type Rejection = BodyAlreadyExtracted;

async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
async fn from_request(req: &mut RequestParts) -> Result<Self, Self::Rejection> {
let req = std::mem::replace(
req,
RequestParts {
Expand All @@ -30,37 +27,28 @@ where
}

#[async_trait]
impl<B> FromRequest<B> for Method
where
B: Send,
{
impl FromRequest for Method {
type Rejection = Infallible;

async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
async fn from_request(req: &mut RequestParts) -> Result<Self, Self::Rejection> {
Ok(req.method().clone())
}
}

#[async_trait]
impl<B> FromRequest<B> for Uri
where
B: Send,
{
impl FromRequest for Uri {
type Rejection = Infallible;

async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
async fn from_request(req: &mut RequestParts) -> Result<Self, Self::Rejection> {
Ok(req.uri().clone())
}
}

#[async_trait]
impl<B> FromRequest<B> for Version
where
B: Send,
{
impl FromRequest for Version {
type Rejection = Infallible;

async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
async fn from_request(req: &mut RequestParts) -> Result<Self, Self::Rejection> {
Ok(req.version())
}
}
Expand All @@ -71,27 +59,28 @@ where
///
/// [`TypedHeader`]: https://docs.rs/axum/latest/axum/extract/struct.TypedHeader.html
#[async_trait]
impl<B> FromRequest<B> for HeaderMap
where
B: Send,
{
impl FromRequest for HeaderMap {
type Rejection = Infallible;

async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
async fn from_request(req: &mut RequestParts) -> Result<Self, Self::Rejection> {
Ok(req.headers().clone())
}
}

#[async_trait]
impl<B> FromRequest<B> for Bytes
where
B: http_body::Body + Send,
B::Data: Send,
B::Error: Into<BoxError>,
{
impl FromRequest for Body {
type Rejection = BodyAlreadyExtracted;

async fn from_request(req: &mut RequestParts) -> Result<Self, Self::Rejection> {
Ok(take_body(req)?)
}
}

#[async_trait]
impl FromRequest for Bytes {
type Rejection = BytesRejection;

async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
async fn from_request(req: &mut RequestParts) -> Result<Self, Self::Rejection> {
let body = take_body(req)?;

let bytes = crate::body::to_bytes(body)
Expand All @@ -103,15 +92,10 @@ where
}

#[async_trait]
impl<B> FromRequest<B> for String
where
B: http_body::Body + Send,
B::Data: Send,
B::Error: Into<BoxError>,
{
impl FromRequest for String {
type Rejection = StringRejection;

async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
async fn from_request(req: &mut RequestParts) -> Result<Self, Self::Rejection> {
let body = take_body(req)?;

let bytes = crate::body::to_bytes(body)
Expand All @@ -126,13 +110,10 @@ where
}

#[async_trait]
impl<B> FromRequest<B> for http::request::Parts
where
B: Send,
{
impl FromRequest for http::request::Parts {
type Rejection = Infallible;

async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
async fn from_request(req: &mut RequestParts) -> Result<Self, Self::Rejection> {
let method = unwrap_infallible(Method::from_request(req).await);
let uri = unwrap_infallible(Uri::from_request(req).await);
let version = unwrap_infallible(Version::from_request(req).await);
Expand All @@ -159,6 +140,6 @@ fn unwrap_infallible<T>(result: Result<T, Infallible>) -> T {
}
}

pub(crate) fn take_body<B>(req: &mut RequestParts<B>) -> Result<B, BodyAlreadyExtracted> {
pub(crate) fn take_body(req: &mut RequestParts) -> Result<Body, BodyAlreadyExtracted> {
req.take_body().ok_or(BodyAlreadyExtracted)
}
16 changes: 5 additions & 11 deletions axum-core/src/extract/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,25 @@ use async_trait::async_trait;
use std::convert::Infallible;

#[async_trait]
impl<B> FromRequest<B> for ()
where
B: Send,
{
impl FromRequest for () {
type Rejection = Infallible;

async fn from_request(_: &mut RequestParts<B>) -> Result<(), Self::Rejection> {
async fn from_request(_: &mut RequestParts) -> Result<(), Self::Rejection> {
Ok(())
}
}

macro_rules! impl_from_request {
() => {};

( $($ty:ident),* $(,)? ) => {
#[async_trait]
#[allow(non_snake_case)]
impl<B, $($ty,)*> FromRequest<B> for ($($ty,)*)
impl<$($ty,)*> FromRequest for ($($ty,)*)
where
$( $ty: FromRequest<B> + Send, )*
B: Send,
$( $ty: FromRequest + Send, )*
{
type Rejection = Response;

async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
async fn from_request(req: &mut RequestParts) -> Result<Self, Self::Rejection> {
$( let $ty = $ty::from_request(req).await.map_err(|err| err.into_response())?; )*
Ok(($($ty,)*))
}
Expand Down
Loading