mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-30 00:00:32 +02:00
Only allow last extractor to mutate the request (#1272)
* Only allow last extractor to mutate the request * Change `FromRequest` and add `FromRequestParts` trait (#1275) * Add `Once`/`Mut` type parameter for `FromRequest` and `RequestParts` * 🪄 * split traits * `FromRequest` for tuples * Remove `BodyAlreadyExtracted` * don't need fully qualified path * don't export `Once` and `Mut` * remove temp tests * depend on axum again Co-authored-by: Jonas Platte <[email protected]> * Port `Handler` and most extractors (#1277) * Port `Handler` and most extractors * Put `M` inside `Handler` impls, not trait itself * comment out tuples for now * fix lints * Reorder arguments to `Handler` (#1281) I think `Request<B>, Arc<S>` is better since its consistent with `FromRequest` and `FromRequestParts`. * Port most things in axum-extra (#1282) * Port `#[derive(TypedPath)]` and `#[debug_handler]` (#1283) * port #[derive(TypedPath)] * wip: #[debug_handler] * fix #[debug_handler] * don't need itertools * also require `Send` * update expected error * support fully qualified `self` * Implement FromRequest[Parts] for tuples (#1286) * Port docs for axum and axum-core (#1285) * Port axum-extra (#1287) * Port axum-extra * Update axum-core/Cargo.toml Co-authored-by: Jonas Platte <[email protected]> * remove `impl FromRequest for Either*` Co-authored-by: Jonas Platte <[email protected]> * New FromRequest[Parts] trait cleanup (#1288) * Make private module truly private again * Simplify tuple FromRequest implementation * Port `#[derive(FromRequest)]` (#1289) * fix tests * fix docs * revert examples * fix docs link * fix intra docs links * Port examples (#1291) * Document wrapping other extractors (#1292) * axum-extra doesn't need to depend on axum-core (#1294) Missed this in https://github.com/tokio-rs/axum/pull/1287 * Add `FromRequest` changes to changelogs (#1293) * Update changelog * Remove default type for `S` in `Handler` * Clarify which types have default types for `S` * Apply suggestions from code review Co-authored-by: Jonas Platte <[email protected]> Co-authored-by: Jonas Platte <[email protected]> * remove unused import * Rename `Mut` and `Once` (#1296) * fix trybuild expected output Co-authored-by: Jonas Platte <[email protected]>
This commit is contained in:
co-authored by
Jonas Platte
parent
f1769e5134
commit
be624306f4
+87
-225
@@ -4,11 +4,10 @@
|
||||
//!
|
||||
//! [`axum::extract`]: https://docs.rs/axum/latest/axum/extract/index.html
|
||||
|
||||
use self::rejection::*;
|
||||
use crate::response::IntoResponse;
|
||||
use async_trait::async_trait;
|
||||
use http::{Extensions, HeaderMap, Method, Request, Uri, Version};
|
||||
use std::{convert::Infallible, sync::Arc};
|
||||
use http::{request::Parts, Request};
|
||||
use std::convert::Infallible;
|
||||
|
||||
pub mod rejection;
|
||||
|
||||
@@ -18,9 +17,44 @@ mod tuple;
|
||||
|
||||
pub use self::from_ref::FromRef;
|
||||
|
||||
mod private {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ViaParts {}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ViaRequest {}
|
||||
}
|
||||
|
||||
/// Types that can be created from request parts.
|
||||
///
|
||||
/// Extractors that implement `FromRequestParts` cannot consume the request body and can thus be
|
||||
/// run in any order for handlers.
|
||||
///
|
||||
/// If your extractor needs to consume the request body then you should implement [`FromRequest`]
|
||||
/// and not [`FromRequestParts`].
|
||||
///
|
||||
/// See [`axum::extract`] for more general docs about extraxtors.
|
||||
///
|
||||
/// [`axum::extract`]: https://docs.rs/axum/0.6/axum/extract/index.html
|
||||
#[async_trait]
|
||||
pub trait FromRequestParts<S>: 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_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection>;
|
||||
}
|
||||
|
||||
/// Types that can be created from requests.
|
||||
///
|
||||
/// See [`axum::extract`] for more details.
|
||||
/// Extractors that implement `FromRequest` can consume the request body and can thus only be run
|
||||
/// once for handlers.
|
||||
///
|
||||
/// If your extractor doesn't need to consume the request body then you should implement
|
||||
/// [`FromRequestParts`] and not [`FromRequest`].
|
||||
///
|
||||
/// See [`axum::extract`] for more general docs about extraxtors.
|
||||
///
|
||||
/// # What is the `B` type parameter?
|
||||
///
|
||||
@@ -39,7 +73,8 @@ pub use self::from_ref::FromRef;
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// async_trait,
|
||||
/// extract::{FromRequest, RequestParts},
|
||||
/// extract::FromRequest,
|
||||
/// http::Request,
|
||||
/// };
|
||||
///
|
||||
/// struct MyExtractor;
|
||||
@@ -48,12 +83,12 @@ pub use self::from_ref::FromRef;
|
||||
/// impl<S, B> FromRequest<S, B> for MyExtractor
|
||||
/// where
|
||||
/// // these bounds are required by `async_trait`
|
||||
/// B: Send,
|
||||
/// B: Send + 'static,
|
||||
/// S: Send + Sync,
|
||||
/// {
|
||||
/// type Rejection = http::StatusCode;
|
||||
///
|
||||
/// async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
|
||||
/// // ...
|
||||
/// # unimplemented!()
|
||||
/// }
|
||||
@@ -63,231 +98,45 @@ pub use self::from_ref::FromRef;
|
||||
/// This ensures your extractor is as flexible as possible.
|
||||
///
|
||||
/// [`http::Request<B>`]: http::Request
|
||||
/// [`axum::extract`]: https://docs.rs/axum/latest/axum/extract/index.html
|
||||
/// [`axum::extract`]: https://docs.rs/axum/0.6/axum/extract/index.html
|
||||
#[async_trait]
|
||||
pub trait FromRequest<S, B>: Sized {
|
||||
pub trait FromRequest<S, B, M = private::ViaRequest>: 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<S, B>) -> Result<Self, Self::Rejection>;
|
||||
async fn from_request(req: Request<B>, state: &S) -> 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<S, B> {
|
||||
pub(crate) state: Arc<S>,
|
||||
method: Method,
|
||||
uri: Uri,
|
||||
version: Version,
|
||||
headers: HeaderMap,
|
||||
extensions: Extensions,
|
||||
body: Option<B>,
|
||||
}
|
||||
#[async_trait]
|
||||
impl<S, B, T> FromRequest<S, B, private::ViaParts> for T
|
||||
where
|
||||
B: Send + 'static,
|
||||
S: Send + Sync,
|
||||
T: FromRequestParts<S>,
|
||||
{
|
||||
type Rejection = <Self as FromRequestParts<S>>::Rejection;
|
||||
|
||||
impl<B> RequestParts<(), B> {
|
||||
/// Create a new `RequestParts` without any state.
|
||||
///
|
||||
/// 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 {
|
||||
Self::with_state((), req)
|
||||
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let (mut parts, _) = req.into_parts();
|
||||
Self::from_request_parts(&mut parts, state).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> RequestParts<S, B> {
|
||||
/// Create a new `RequestParts` with the given state.
|
||||
///
|
||||
/// 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 with_state(state: S, req: Request<B>) -> Self {
|
||||
Self::with_state_arc(Arc::new(state), req)
|
||||
}
|
||||
#[async_trait]
|
||||
impl<S, T> FromRequestParts<S> for Option<T>
|
||||
where
|
||||
T: FromRequestParts<S>,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
/// Create a new `RequestParts` with the given [`Arc`]'ed state.
|
||||
///
|
||||
/// 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 with_state_arc(state: Arc<S>, req: Request<B>) -> Self {
|
||||
let (
|
||||
http::request::Parts {
|
||||
method,
|
||||
uri,
|
||||
version,
|
||||
headers,
|
||||
extensions,
|
||||
..
|
||||
},
|
||||
body,
|
||||
) = req.into_parts();
|
||||
|
||||
RequestParts {
|
||||
state,
|
||||
method,
|
||||
uri,
|
||||
version,
|
||||
headers,
|
||||
extensions,
|
||||
body: Some(body),
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply an extractor to this `RequestParts`.
|
||||
///
|
||||
/// `req.extract::<Extractor>()` is equivalent to `Extractor::from_request(req)`.
|
||||
/// This function simply exists as a convenience.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # struct MyExtractor {}
|
||||
///
|
||||
/// use std::convert::Infallible;
|
||||
///
|
||||
/// use async_trait::async_trait;
|
||||
/// use axum::extract::{FromRequest, RequestParts};
|
||||
/// use http::{Method, Uri};
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<S, B> FromRequest<S, B> for MyExtractor
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// S: Send + Sync,
|
||||
/// {
|
||||
/// type Rejection = Infallible;
|
||||
///
|
||||
/// async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Infallible> {
|
||||
/// let method = req.extract::<Method>().await?;
|
||||
/// let path = req.extract::<Uri>().await?.path().to_owned();
|
||||
///
|
||||
/// todo!()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn extract<E>(&mut self) -> Result<E, E::Rejection>
|
||||
where
|
||||
E: FromRequest<S, B>,
|
||||
{
|
||||
E::from_request(self).await
|
||||
}
|
||||
|
||||
/// Convert this `RequestParts` back into a [`Request`].
|
||||
///
|
||||
/// Fails if The request body has been extracted, that is [`take_body`] has
|
||||
/// been called.
|
||||
///
|
||||
/// [`take_body`]: RequestParts::take_body
|
||||
pub fn try_into_request(self) -> Result<Request<B>, BodyAlreadyExtracted> {
|
||||
let Self {
|
||||
state: _,
|
||||
method,
|
||||
uri,
|
||||
version,
|
||||
headers,
|
||||
extensions,
|
||||
mut body,
|
||||
} = self;
|
||||
|
||||
let mut req = if let Some(body) = body.take() {
|
||||
Request::new(body)
|
||||
} else {
|
||||
return Err(BodyAlreadyExtracted);
|
||||
};
|
||||
|
||||
*req.method_mut() = method;
|
||||
*req.uri_mut() = uri;
|
||||
*req.version_mut() = version;
|
||||
*req.headers_mut() = headers;
|
||||
*req.extensions_mut() = extensions;
|
||||
|
||||
Ok(req)
|
||||
}
|
||||
|
||||
/// Gets a reference to the request method.
|
||||
pub fn method(&self) -> &Method {
|
||||
&self.method
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the request method.
|
||||
pub fn method_mut(&mut self) -> &mut Method {
|
||||
&mut self.method
|
||||
}
|
||||
|
||||
/// Gets a reference to the request URI.
|
||||
pub fn uri(&self) -> &Uri {
|
||||
&self.uri
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the request URI.
|
||||
pub fn uri_mut(&mut self) -> &mut Uri {
|
||||
&mut self.uri
|
||||
}
|
||||
|
||||
/// Get the request HTTP version.
|
||||
pub fn version(&self) -> Version {
|
||||
self.version
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the request HTTP version.
|
||||
pub fn version_mut(&mut self) -> &mut Version {
|
||||
&mut self.version
|
||||
}
|
||||
|
||||
/// Gets a reference to the request headers.
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.headers
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the request headers.
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.headers
|
||||
}
|
||||
|
||||
/// Gets a reference to the request extensions.
|
||||
pub fn extensions(&self) -> &Extensions {
|
||||
&self.extensions
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to the request extensions.
|
||||
pub fn extensions_mut(&mut self) -> &mut Extensions {
|
||||
&mut self.extensions
|
||||
}
|
||||
|
||||
/// Gets a reference to the request body.
|
||||
///
|
||||
/// Returns `None` if the body has been taken by another extractor.
|
||||
pub fn body(&self) -> Option<&B> {
|
||||
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> {
|
||||
&mut self.body
|
||||
}
|
||||
|
||||
/// Takes the body out of the request, leaving a `None` in its place.
|
||||
pub fn take_body(&mut self) -> Option<B> {
|
||||
self.body.take()
|
||||
}
|
||||
|
||||
/// Get a reference to the state.
|
||||
pub fn state(&self) -> &S {
|
||||
&self.state
|
||||
async fn from_request_parts(
|
||||
parts: &mut Parts,
|
||||
state: &S,
|
||||
) -> Result<Option<T>, Self::Rejection> {
|
||||
Ok(T::from_request_parts(parts, state).await.ok())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,13 +144,26 @@ impl<S, B> RequestParts<S, B> {
|
||||
impl<S, T, B> FromRequest<S, B> for Option<T>
|
||||
where
|
||||
T: FromRequest<S, B>,
|
||||
B: Send,
|
||||
B: Send + 'static,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Option<T>, Self::Rejection> {
|
||||
Ok(T::from_request(req).await.ok())
|
||||
async fn from_request(req: Request<B>, state: &S) -> Result<Option<T>, Self::Rejection> {
|
||||
Ok(T::from_request(req, state).await.ok())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<S, T> FromRequestParts<S> for Result<T, T::Rejection>
|
||||
where
|
||||
T: FromRequestParts<S>,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
Ok(T::from_request_parts(parts, state).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,12 +171,12 @@ where
|
||||
impl<S, T, B> FromRequest<S, B> for Result<T, T::Rejection>
|
||||
where
|
||||
T: FromRequest<S, B>,
|
||||
B: Send,
|
||||
B: Send + 'static,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
Ok(T::from_request(req).await)
|
||||
async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
|
||||
Ok(T::from_request(req, state).await)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user