mirror of
https://github.com/tokio-rs/axum.git
synced 2024-12-27 23:10:20 +01:00
Add type safe state extractor (#1155)
* begin threading the state through * Pass state to extractors * make state extractor work * make sure nesting with different states work * impl Service for MethodRouter<()> * Fix some of axum-macro's tests * Implement more traits for `State` * Update examples to use `State` * consistent naming of request body param * swap type params * Default the state param to () * fix docs references * Docs and handler state refactoring * docs clean ups * more consistent naming * when does MethodRouter implement Service? * add missing docs * use `Router`'s default state type param * changelog * don't use default type param for FromRequest and RequestParts probably safer for library authors so you don't accidentally forget * fix examples * minor docs tweaks * clarify how to convert handlers into services * group methods in one impl block * make sure merged `MethodRouter`s can access state * fix docs link * test merge with same state type * Document how to access state from middleware * Port cookie extractors to use state to extract keys (#1250) * Updates ECOSYSTEM with a new sample project (#1252) * Avoid unhelpful compiler suggestion (#1251) * fix docs typo * document how library authors should access state * Add `RequestParts::with_state` * fix example * apply suggestions from review * add relevant changes to axum-extra and axum-core changelogs * Add `route_service_with_tsr` * fix trybuild expectations * make sure `SpaRouter` works with routers that have state * Change order of type params on FromRequest and RequestParts * reverse order of `RequestParts::with_state` args to match type params * Add `FromRef` trait (#1268) * Add `FromRef` trait * Remove unnecessary type params * format * fix docs link * format examples * Avoid unnecessary `MethodRouter` * apply suggestions from review Co-authored-by: Dani Pardo <dani.pardo@inmensys.com> Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
This commit is contained in:
parent
90dbd52ee4
commit
423308de3c
132 changed files with 2404 additions and 1126 deletions
|
@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
# Unreleased
|
||||
|
||||
- None.
|
||||
- **breaking:** `FromRequest` and `RequestParts` has a new `S` type param which
|
||||
represents the state ([#1155])
|
||||
|
||||
[#1155]: https://github.com/tokio-rs/axum/pull/1155
|
||||
|
||||
# 0.2.6 (18. June, 2022)
|
||||
|
||||
|
|
23
axum-core/src/extract/from_ref.rs
Normal file
23
axum-core/src/extract/from_ref.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
/// Used to do reference-to-value conversions thus not consuming the input value.
|
||||
///
|
||||
/// This is mainly used with [`State`] to extract "substates" from a reference to main application
|
||||
/// state.
|
||||
///
|
||||
/// See [`State`] for more details on how library authors should use this trait.
|
||||
///
|
||||
/// [`State`]: https://docs.rs/axum/0.6/axum/extract/struct.State.html
|
||||
// NOTE: This trait is defined in axum-core, even though it is mainly used with `State` which is
|
||||
// defined in axum. That allows crate authors to use it when implementing extractors.
|
||||
pub trait FromRef<T> {
|
||||
/// Converts to this type from a reference to the input type.
|
||||
fn from_ref(input: &T) -> Self;
|
||||
}
|
||||
|
||||
impl<T> FromRef<T> for T
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
fn from_ref(input: &T) -> Self {
|
||||
input.clone()
|
||||
}
|
||||
}
|
|
@ -12,9 +12,12 @@ use std::convert::Infallible;
|
|||
|
||||
pub mod rejection;
|
||||
|
||||
mod from_ref;
|
||||
mod request_parts;
|
||||
mod tuple;
|
||||
|
||||
pub use self::from_ref::FromRef;
|
||||
|
||||
/// Types that can be created from requests.
|
||||
///
|
||||
/// See [`axum::extract`] for more details.
|
||||
|
@ -42,13 +45,15 @@ mod tuple;
|
|||
/// struct MyExtractor;
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B> FromRequest<B> for MyExtractor
|
||||
/// impl<S, B> FromRequest<S, B> for MyExtractor
|
||||
/// where
|
||||
/// B: Send, // required by `async_trait`
|
||||
/// // these bounds are required by `async_trait`
|
||||
/// B: Send,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// type Rejection = http::StatusCode;
|
||||
///
|
||||
/// async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
/// async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// // ...
|
||||
/// # unimplemented!()
|
||||
/// }
|
||||
|
@ -60,20 +65,21 @@ 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<S, B>: 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<S, B>) -> 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<S, B> {
|
||||
state: S,
|
||||
method: Method,
|
||||
uri: Uri,
|
||||
version: Version,
|
||||
|
@ -82,8 +88,8 @@ pub struct RequestParts<B> {
|
|||
body: Option<B>,
|
||||
}
|
||||
|
||||
impl<B> RequestParts<B> {
|
||||
/// Create a new `RequestParts`.
|
||||
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
|
||||
|
@ -91,6 +97,19 @@ impl<B> RequestParts<B> {
|
|||
///
|
||||
/// [`tower::Service`]: https://docs.rs/tower/lastest/tower/trait.Service.html
|
||||
pub fn new(req: Request<B>) -> Self {
|
||||
Self::with_state((), req)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
let (
|
||||
http::request::Parts {
|
||||
method,
|
||||
|
@ -104,6 +123,7 @@ impl<B> RequestParts<B> {
|
|||
) = req.into_parts();
|
||||
|
||||
RequestParts {
|
||||
state,
|
||||
method,
|
||||
uri,
|
||||
version,
|
||||
|
@ -130,10 +150,14 @@ impl<B> RequestParts<B> {
|
|||
/// use http::{Method, Uri};
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B: Send> FromRequest<B> for MyExtractor {
|
||||
/// impl<S, B> FromRequest<S, B> for MyExtractor
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// type Rejection = Infallible;
|
||||
///
|
||||
/// async fn from_request(req: &mut RequestParts<B>) -> Result<Self, 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();
|
||||
///
|
||||
|
@ -141,7 +165,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<S, B>,
|
||||
{
|
||||
E::from_request(self).await
|
||||
}
|
||||
|
||||
|
@ -153,6 +180,7 @@ impl<B> RequestParts<B> {
|
|||
/// [`take_body`]: RequestParts::take_body
|
||||
pub fn try_into_request(self) -> Result<Request<B>, BodyAlreadyExtracted> {
|
||||
let Self {
|
||||
state: _,
|
||||
method,
|
||||
uri,
|
||||
version,
|
||||
|
@ -245,30 +273,37 @@ impl<B> RequestParts<B> {
|
|||
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_trait]
|
||||
impl<T, B> FromRequest<B> for Option<T>
|
||||
impl<S, T, B> FromRequest<S, B> for Option<T>
|
||||
where
|
||||
T: FromRequest<B>,
|
||||
T: FromRequest<S, B>,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Option<T>, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> 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<S, T, B> FromRequest<S, B> for Result<T, T::Rejection>
|
||||
where
|
||||
T: FromRequest<B>,
|
||||
T: FromRequest<S, B>,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
Ok(T::from_request(req).await)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,16 +6,18 @@ use http::{Extensions, HeaderMap, Method, Request, Uri, Version};
|
|||
use std::convert::Infallible;
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for Request<B>
|
||||
impl<S, B> FromRequest<S, B> for Request<B>
|
||||
where
|
||||
B: Send,
|
||||
S: Clone + Send,
|
||||
{
|
||||
type Rejection = BodyAlreadyExtracted;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let req = std::mem::replace(
|
||||
req,
|
||||
RequestParts {
|
||||
state: req.state().clone(),
|
||||
method: req.method.clone(),
|
||||
version: req.version,
|
||||
uri: req.uri.clone(),
|
||||
|
@ -30,37 +32,40 @@ where
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for Method
|
||||
impl<S, B> FromRequest<S, B> for Method
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
Ok(req.method().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for Uri
|
||||
impl<S, B> FromRequest<S, B> for Uri
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
Ok(req.uri().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for Version
|
||||
impl<S, B> FromRequest<S, B> for Version
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
Ok(req.version())
|
||||
}
|
||||
}
|
||||
|
@ -71,27 +76,29 @@ where
|
|||
///
|
||||
/// [`TypedHeader`]: https://docs.rs/axum/latest/axum/extract/struct.TypedHeader.html
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for HeaderMap
|
||||
impl<S, B> FromRequest<S, B> for HeaderMap
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
Ok(req.headers().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for Bytes
|
||||
impl<S, B> FromRequest<S, B> for Bytes
|
||||
where
|
||||
B: http_body::Body + Send,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = BytesRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let body = take_body(req)?;
|
||||
|
||||
let bytes = crate::body::to_bytes(body)
|
||||
|
@ -103,15 +110,16 @@ where
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for String
|
||||
impl<S, B> FromRequest<S, B> for String
|
||||
where
|
||||
B: http_body::Body + Send,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = StringRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let body = take_body(req)?;
|
||||
|
||||
let bytes = crate::body::to_bytes(body)
|
||||
|
@ -126,13 +134,14 @@ where
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for http::request::Parts
|
||||
impl<S, B> FromRequest<S, B> for http::request::Parts
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> 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);
|
||||
|
@ -159,6 +168,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<S, B>(req: &mut RequestParts<S, B>) -> Result<B, BodyAlreadyExtracted> {
|
||||
req.take_body().ok_or(BodyAlreadyExtracted)
|
||||
}
|
||||
|
|
|
@ -4,13 +4,14 @@ use async_trait::async_trait;
|
|||
use std::convert::Infallible;
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for ()
|
||||
impl<S, B> FromRequest<S, B> for ()
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(_: &mut RequestParts<B>) -> Result<(), Self::Rejection> {
|
||||
async fn from_request(_: &mut RequestParts<S, B>) -> Result<(), Self::Rejection> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -21,14 +22,15 @@ macro_rules! impl_from_request {
|
|||
( $($ty:ident),* $(,)? ) => {
|
||||
#[async_trait]
|
||||
#[allow(non_snake_case)]
|
||||
impl<B, $($ty,)*> FromRequest<B> for ($($ty,)*)
|
||||
impl<S, B, $($ty,)*> FromRequest<S, B> for ($($ty,)*)
|
||||
where
|
||||
$( $ty: FromRequest<B> + Send, )*
|
||||
$( $ty: FromRequest<S, B> + Send, )*
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Response;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
$( let $ty = $ty::from_request(req).await.map_err(|err| err.into_response())?; )*
|
||||
Ok(($($ty,)*))
|
||||
}
|
||||
|
|
|
@ -17,15 +17,21 @@ and this project adheres to [Semantic Versioning].
|
|||
literal `Response`
|
||||
- **added:** Support chaining handlers with `HandlerCallWithExtractors::or` ([#1170])
|
||||
- **change:** axum-extra's MSRV is now 1.60 ([#1239])
|
||||
- **breaking:** `SignedCookieJar` and `PrivateCookieJar` now extracts the keys
|
||||
from the router's state, rather than extensions
|
||||
- **added:** Add Protocol Buffer extractor and response ([#1239])
|
||||
- **added:** Add `Either*` types for combining extractors and responses into a
|
||||
single type ([#1263])
|
||||
- **added:** `WithRejection` extractor for customizing other extractors' rejections ([#1262])
|
||||
- **added:** Add sync constructors to `CookieJar`, `PrivateCookieJar`, and
|
||||
`SignedCookieJar` so they're easier to use in custom middleware
|
||||
- **breaking:** `Resource` has a new `S` type param which represents the state ([#1155])
|
||||
- **breaking:** `RouterExt::route_with_tsr` now only accepts `MethodRouter`s ([#1155])
|
||||
- **added:** `RouterExt::route_service_with_tsr` for routing to any `Service` ([#1155])
|
||||
|
||||
[#1086]: https://github.com/tokio-rs/axum/pull/1086
|
||||
[#1119]: https://github.com/tokio-rs/axum/pull/1119
|
||||
[#1155]: https://github.com/tokio-rs/axum/pull/1155
|
||||
[#1170]: https://github.com/tokio-rs/axum/pull/1170
|
||||
[#1214]: https://github.com/tokio-rs/axum/pull/1214
|
||||
[#1239]: https://github.com/tokio-rs/axum/pull/1239
|
||||
|
|
|
@ -190,15 +190,16 @@ macro_rules! impl_traits_for_either {
|
|||
$last:ident $(,)?
|
||||
) => {
|
||||
#[async_trait]
|
||||
impl<B, $($ident),*, $last> FromRequest<B> for $either<$($ident),*, $last>
|
||||
impl<S, B, $($ident),*, $last> FromRequest<S, B> for $either<$($ident),*, $last>
|
||||
where
|
||||
$($ident: FromRequest<B>),*,
|
||||
$last: FromRequest<B>,
|
||||
$($ident: FromRequest<S, B>),*,
|
||||
$last: FromRequest<S, B>,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = $last::Rejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
$(
|
||||
if let Ok(value) = req.extract().await {
|
||||
return Ok(Self::$ident(value));
|
||||
|
|
|
@ -30,13 +30,14 @@ use std::ops::{Deref, DerefMut};
|
|||
/// struct Session { /* ... */ }
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B> FromRequest<B> for Session
|
||||
/// impl<S, B> FromRequest<S, B> for Session
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// type Rejection = (StatusCode, String);
|
||||
///
|
||||
/// async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
/// async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// // load session...
|
||||
/// # unimplemented!()
|
||||
/// }
|
||||
|
@ -45,13 +46,14 @@ use std::ops::{Deref, DerefMut};
|
|||
/// struct CurrentUser { /* ... */ }
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B> FromRequest<B> for CurrentUser
|
||||
/// impl<S, B> FromRequest<S, B> for CurrentUser
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// type Rejection = Response;
|
||||
///
|
||||
/// async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
/// async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// // loading a `CurrentUser` requires first loading the `Session`
|
||||
/// //
|
||||
/// // by using `Cached<Session>` we avoid extracting the session more than
|
||||
|
@ -88,14 +90,15 @@ pub struct Cached<T>(pub T);
|
|||
struct CachedEntry<T>(T);
|
||||
|
||||
#[async_trait]
|
||||
impl<B, T> FromRequest<B> for Cached<T>
|
||||
impl<S, B, T> FromRequest<S, B> for Cached<T>
|
||||
where
|
||||
B: Send,
|
||||
T: FromRequest<B> + Clone + Send + Sync + 'static,
|
||||
S: Send,
|
||||
T: FromRequest<S, B> + Clone + Send + Sync + 'static,
|
||||
{
|
||||
type Rejection = T::Rejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
match Extension::<CachedEntry<T>>::from_request(req).await {
|
||||
Ok(Extension(CachedEntry(value))) => Ok(Self(value)),
|
||||
Err(_) => {
|
||||
|
@ -139,13 +142,14 @@ mod tests {
|
|||
struct Extractor(Instant);
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for Extractor
|
||||
impl<S, B> FromRequest<S, B> for Extractor
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(_req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(_req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
COUNTER.fetch_add(1, Ordering::SeqCst);
|
||||
Ok(Self(Instant::now()))
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ pub use cookie::Key;
|
|||
/// let app = Router::new()
|
||||
/// .route("/sessions", post(create_session))
|
||||
/// .route("/me", get(me));
|
||||
/// # let app: Router<axum::body::Body> = app;
|
||||
/// # let app: Router = app;
|
||||
/// ```
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CookieJar {
|
||||
|
@ -88,13 +88,14 @@ pub struct CookieJar {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for CookieJar
|
||||
impl<S, B> FromRequest<S, B> for CookieJar
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
Ok(Self::from_headers(req.headers()))
|
||||
}
|
||||
}
|
||||
|
@ -226,7 +227,7 @@ fn set_cookies(jar: cookie::CookieJar, headers: &mut HeaderMap) {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use axum::{body::Body, http::Request, routing::get, Extension, Router};
|
||||
use axum::{body::Body, extract::FromRef, http::Request, routing::get, Router};
|
||||
use tower::ServiceExt;
|
||||
|
||||
macro_rules! cookie_test {
|
||||
|
@ -245,12 +246,15 @@ mod tests {
|
|||
jar.remove(Cookie::named("key"))
|
||||
}
|
||||
|
||||
let app = Router::<Body>::new()
|
||||
let state = AppState {
|
||||
key: Key::generate(),
|
||||
custom_key: CustomKey(Key::generate()),
|
||||
};
|
||||
|
||||
let app = Router::<_, Body>::with_state(state)
|
||||
.route("/set", get(set_cookie))
|
||||
.route("/get", get(get_cookie))
|
||||
.route("/remove", get(remove_cookie))
|
||||
.layer(Extension(Key::generate()))
|
||||
.layer(Extension(CustomKey(Key::generate())));
|
||||
.route("/remove", get(remove_cookie));
|
||||
|
||||
let res = app
|
||||
.clone()
|
||||
|
@ -298,6 +302,24 @@ mod tests {
|
|||
cookie_test!(private_cookies, PrivateCookieJar);
|
||||
cookie_test!(private_cookies_with_custom_key, PrivateCookieJar<CustomKey>);
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
key: Key,
|
||||
custom_key: CustomKey,
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for Key {
|
||||
fn from_ref(state: &AppState) -> Key {
|
||||
state.key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for CustomKey {
|
||||
fn from_ref(state: &AppState) -> CustomKey {
|
||||
state.custom_key.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CustomKey(Key);
|
||||
|
||||
|
@ -313,9 +335,12 @@ mod tests {
|
|||
format!("{:?}", jar.get("key"))
|
||||
}
|
||||
|
||||
let app = Router::<Body>::new()
|
||||
.route("/get", get(get_cookie))
|
||||
.layer(Extension(Key::generate()));
|
||||
let state = AppState {
|
||||
key: Key::generate(),
|
||||
custom_key: CustomKey(Key::generate()),
|
||||
};
|
||||
|
||||
let app = Router::<_, Body>::with_state(state).route("/get", get(get_cookie));
|
||||
|
||||
let res = app
|
||||
.clone()
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use super::{cookies_from_request, set_cookies, Cookie, Key};
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{FromRequest, RequestParts},
|
||||
extract::{FromRef, FromRequest, RequestParts},
|
||||
response::{IntoResponse, IntoResponseParts, Response, ResponseParts},
|
||||
Extension,
|
||||
};
|
||||
use cookie::PrivateJar;
|
||||
use http::HeaderMap;
|
||||
|
@ -23,9 +22,8 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
|
|||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// Extension,
|
||||
/// routing::{post, get},
|
||||
/// extract::TypedHeader,
|
||||
/// extract::{TypedHeader, FromRef},
|
||||
/// response::{IntoResponse, Redirect},
|
||||
/// headers::authorization::{Authorization, Bearer},
|
||||
/// http::StatusCode,
|
||||
|
@ -45,22 +43,36 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
|
|||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Generate a secure key
|
||||
/// //
|
||||
/// // You probably don't wanna generate a new one each time the app starts though
|
||||
/// let key = Key::generate();
|
||||
/// // our application state
|
||||
/// #[derive(Clone)]
|
||||
/// struct AppState {
|
||||
/// // that holds the key used to sign cookies
|
||||
/// key: Key,
|
||||
/// }
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// // this impl tells `SignedCookieJar` how to access the key from our state
|
||||
/// impl FromRef<AppState> for Key {
|
||||
/// fn from_ref(state: &AppState) -> Self {
|
||||
/// state.key.clone()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let state = AppState {
|
||||
/// // Generate a secure key
|
||||
/// //
|
||||
/// // You probably don't wanna generate a new one each time the app starts though
|
||||
/// key: Key::generate(),
|
||||
/// };
|
||||
///
|
||||
/// let app = Router::with_state(state)
|
||||
/// .route("/set", post(set_secret))
|
||||
/// .route("/get", get(get_secret))
|
||||
/// // add extension with the key so `PrivateCookieJar` can access it
|
||||
/// .layer(Extension(key));
|
||||
/// # let app: Router<axum::body::Body> = app;
|
||||
/// .route("/get", get(get_secret));
|
||||
/// # let app: Router<_> = app;
|
||||
/// ```
|
||||
pub struct PrivateCookieJar<K = Key> {
|
||||
jar: cookie::CookieJar,
|
||||
key: Key,
|
||||
// The key used to extract the key extension. Allows users to use multiple keys for different
|
||||
// The key used to extract the key. Allows users to use multiple keys for different
|
||||
// jars. Maybe a library wants its own key.
|
||||
_marker: PhantomData<K>,
|
||||
}
|
||||
|
@ -75,15 +87,17 @@ impl<K> fmt::Debug for PrivateCookieJar<K> {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B, K> FromRequest<B> for PrivateCookieJar<K>
|
||||
impl<S, B, K> FromRequest<S, B> for PrivateCookieJar<K>
|
||||
where
|
||||
B: Send,
|
||||
K: Into<Key> + Clone + Send + Sync + 'static,
|
||||
S: Send,
|
||||
K: FromRef<S> + Into<Key>,
|
||||
{
|
||||
type Rejection = <axum::Extension<K> as FromRequest<B>>::Rejection;
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
let key = req.extract::<Extension<K>>().await?.0.into();
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let k = K::from_ref(req.state());
|
||||
let key = k.into();
|
||||
let PrivateCookieJar {
|
||||
jar,
|
||||
key,
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use super::{cookies_from_request, set_cookies};
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{FromRequest, RequestParts},
|
||||
extract::{FromRef, FromRequest, RequestParts},
|
||||
response::{IntoResponse, IntoResponseParts, Response, ResponseParts},
|
||||
Extension,
|
||||
};
|
||||
use cookie::SignedJar;
|
||||
use cookie::{Cookie, Key};
|
||||
|
@ -24,9 +23,8 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
|
|||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// Extension,
|
||||
/// routing::{post, get},
|
||||
/// extract::TypedHeader,
|
||||
/// extract::{TypedHeader, FromRef},
|
||||
/// response::{IntoResponse, Redirect},
|
||||
/// headers::authorization::{Authorization, Bearer},
|
||||
/// http::StatusCode,
|
||||
|
@ -63,22 +61,36 @@ use std::{convert::Infallible, fmt, marker::PhantomData};
|
|||
/// # todo!()
|
||||
/// }
|
||||
///
|
||||
/// // Generate a secure key
|
||||
/// //
|
||||
/// // You probably don't wanna generate a new one each time the app starts though
|
||||
/// let key = Key::generate();
|
||||
/// // our application state
|
||||
/// #[derive(Clone)]
|
||||
/// struct AppState {
|
||||
/// // that holds the key used to sign cookies
|
||||
/// key: Key,
|
||||
/// }
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// // this impl tells `SignedCookieJar` how to access the key from our state
|
||||
/// impl FromRef<AppState> for Key {
|
||||
/// fn from_ref(state: &AppState) -> Self {
|
||||
/// state.key.clone()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let state = AppState {
|
||||
/// // Generate a secure key
|
||||
/// //
|
||||
/// // You probably don't wanna generate a new one each time the app starts though
|
||||
/// key: Key::generate(),
|
||||
/// };
|
||||
///
|
||||
/// let app = Router::with_state(state)
|
||||
/// .route("/sessions", post(create_session))
|
||||
/// .route("/me", get(me))
|
||||
/// // add extension with the key so `SignedCookieJar` can access it
|
||||
/// .layer(Extension(key));
|
||||
/// # let app: Router<axum::body::Body> = app;
|
||||
/// .route("/me", get(me));
|
||||
/// # let app: Router<_> = app;
|
||||
/// ```
|
||||
pub struct SignedCookieJar<K = Key> {
|
||||
jar: cookie::CookieJar,
|
||||
key: Key,
|
||||
// The key used to extract the key extension. Allows users to use multiple keys for different
|
||||
// The key used to extract the key. Allows users to use multiple keys for different
|
||||
// jars. Maybe a library wants its own key.
|
||||
_marker: PhantomData<K>,
|
||||
}
|
||||
|
@ -93,15 +105,17 @@ impl<K> fmt::Debug for SignedCookieJar<K> {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B, K> FromRequest<B> for SignedCookieJar<K>
|
||||
impl<S, B, K> FromRequest<S, B> for SignedCookieJar<K>
|
||||
where
|
||||
B: Send,
|
||||
K: Into<Key> + Clone + Send + Sync + 'static,
|
||||
S: Send,
|
||||
K: FromRef<S> + Into<Key>,
|
||||
{
|
||||
type Rejection = <axum::Extension<K> as FromRequest<B>>::Rejection;
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
let key = req.extract::<Extension<K>>().await?.0.into();
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let k = K::from_ref(req.state());
|
||||
let key = k.into();
|
||||
let SignedCookieJar {
|
||||
jar,
|
||||
key,
|
||||
|
|
|
@ -55,16 +55,17 @@ impl<T> Deref for Form<T> {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B> FromRequest<B> for Form<T>
|
||||
impl<T, S, B> FromRequest<S, B> for Form<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
B: HttpBody + Send,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = FormRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
if req.method() == Method::GET {
|
||||
let query = req.uri().query().unwrap_or_default();
|
||||
let value = serde_html_form::from_str(query)
|
||||
|
@ -85,7 +86,7 @@ where
|
|||
}
|
||||
|
||||
// this is duplicated in `axum/src/extract/mod.rs`
|
||||
fn has_content_type<B>(req: &RequestParts<B>, expected_content_type: &mime::Mime) -> bool {
|
||||
fn has_content_type<S, B>(req: &RequestParts<S, B>, expected_content_type: &mime::Mime) -> bool {
|
||||
let content_type = if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) {
|
||||
content_type
|
||||
} else {
|
||||
|
|
|
@ -58,14 +58,15 @@ use std::ops::Deref;
|
|||
pub struct Query<T>(pub T);
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B> FromRequest<B> for Query<T>
|
||||
impl<T, S, B> FromRequest<S, B> for Query<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = QueryRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let query = req.uri().query().unwrap_or_default();
|
||||
let value = serde_html_form::from_str(query)
|
||||
.map_err(FailedToDeserializeQueryString::__private_new)?;
|
||||
|
|
|
@ -107,15 +107,16 @@ impl<E, R> DerefMut for WithRejection<E, R> {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B, E, R> FromRequest<B> for WithRejection<E, R>
|
||||
impl<B, E, R, S> FromRequest<S, B> for WithRejection<E, R>
|
||||
where
|
||||
B: Send,
|
||||
E: FromRequest<B>,
|
||||
S: Send,
|
||||
E: FromRequest<S, B>,
|
||||
R: From<E::Rejection> + IntoResponse,
|
||||
{
|
||||
type Rejection = R;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let extractor = req.extract::<E>().await?;
|
||||
Ok(WithRejection(extractor, PhantomData))
|
||||
}
|
||||
|
@ -134,10 +135,14 @@ mod tests {
|
|||
struct TestRejection;
|
||||
|
||||
#[async_trait]
|
||||
impl<B: Send> FromRequest<B> for TestExtractor {
|
||||
impl<S, B> FromRequest<S, B> for TestExtractor
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = ();
|
||||
|
||||
async fn from_request(_: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(_: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,15 +19,15 @@ pub use self::or::Or;
|
|||
///
|
||||
/// The drawbacks of this trait is that you cannot apply middleware to individual handlers like you
|
||||
/// can with [`Handler::layer`].
|
||||
pub trait HandlerCallWithExtractors<T, B>: Sized {
|
||||
pub trait HandlerCallWithExtractors<T, S, B>: Sized {
|
||||
/// The type of future calling this handler returns.
|
||||
type Future: Future<Output = Response> + Send + 'static;
|
||||
|
||||
/// Call the handler with the extracted inputs.
|
||||
fn call(self, extractors: T) -> <Self as HandlerCallWithExtractors<T, B>>::Future;
|
||||
fn call(self, state: S, extractors: T) -> <Self as HandlerCallWithExtractors<T, S, B>>::Future;
|
||||
|
||||
/// Conver this `HandlerCallWithExtractors` into [`Handler`].
|
||||
fn into_handler(self) -> IntoHandler<Self, T, B> {
|
||||
fn into_handler(self) -> IntoHandler<Self, T, S, B> {
|
||||
IntoHandler {
|
||||
handler: self,
|
||||
_marker: PhantomData,
|
||||
|
@ -67,10 +67,14 @@ pub trait HandlerCallWithExtractors<T, B>: Sized {
|
|||
/// struct AdminPermissions {}
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B: Send> FromRequest<B> for AdminPermissions {
|
||||
/// impl<S, B> FromRequest<S, B> for AdminPermissions
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// // check for admin permissions...
|
||||
/// # type Rejection = ();
|
||||
/// # async fn from_request(req: &mut axum::extract::RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
/// # async fn from_request(req: &mut axum::extract::RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// # todo!()
|
||||
/// # }
|
||||
/// }
|
||||
|
@ -78,10 +82,14 @@ pub trait HandlerCallWithExtractors<T, B>: Sized {
|
|||
/// struct User {}
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B: Send> FromRequest<B> for User {
|
||||
/// impl<S, B> FromRequest<S, B> for User
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// // check for a logged in user...
|
||||
/// # type Rejection = ();
|
||||
/// # async fn from_request(req: &mut axum::extract::RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
/// # async fn from_request(req: &mut axum::extract::RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// # todo!()
|
||||
/// # }
|
||||
/// }
|
||||
|
@ -96,9 +104,9 @@ pub trait HandlerCallWithExtractors<T, B>: Sized {
|
|||
/// );
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
fn or<R, Rt>(self, rhs: R) -> Or<Self, R, T, Rt, B>
|
||||
fn or<R, Rt>(self, rhs: R) -> Or<Self, R, T, Rt, S, B>
|
||||
where
|
||||
R: HandlerCallWithExtractors<Rt, B>,
|
||||
R: HandlerCallWithExtractors<Rt, S, B>,
|
||||
{
|
||||
Or {
|
||||
lhs: self,
|
||||
|
@ -111,7 +119,7 @@ pub trait HandlerCallWithExtractors<T, B>: Sized {
|
|||
macro_rules! impl_handler_call_with {
|
||||
( $($ty:ident),* $(,)? ) => {
|
||||
#[allow(non_snake_case)]
|
||||
impl<F, Fut, B, $($ty,)*> HandlerCallWithExtractors<($($ty,)*), B> for F
|
||||
impl<F, Fut, S, B, $($ty,)*> HandlerCallWithExtractors<($($ty,)*), S, B> for F
|
||||
where
|
||||
F: FnOnce($($ty,)*) -> Fut,
|
||||
Fut: Future + Send + 'static,
|
||||
|
@ -122,8 +130,9 @@ macro_rules! impl_handler_call_with {
|
|||
|
||||
fn call(
|
||||
self,
|
||||
_state: S,
|
||||
($($ty,)*): ($($ty,)*),
|
||||
) -> <Self as HandlerCallWithExtractors<($($ty,)*), B>>::Future {
|
||||
) -> <Self as HandlerCallWithExtractors<($($ty,)*), S, B>>::Future {
|
||||
self($($ty,)*).map(IntoResponse::into_response)
|
||||
}
|
||||
}
|
||||
|
@ -152,34 +161,35 @@ impl_handler_call_with!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,
|
|||
///
|
||||
/// Created with [`HandlerCallWithExtractors::into_handler`].
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct IntoHandler<H, T, B> {
|
||||
pub struct IntoHandler<H, T, S, B> {
|
||||
handler: H,
|
||||
_marker: PhantomData<fn() -> (T, B)>,
|
||||
_marker: PhantomData<fn() -> (T, S, B)>,
|
||||
}
|
||||
|
||||
impl<H, T, B> Handler<T, B> for IntoHandler<H, T, B>
|
||||
impl<H, T, S, B> Handler<T, S, B> for IntoHandler<H, T, S, B>
|
||||
where
|
||||
H: HandlerCallWithExtractors<T, B> + Clone + Send + 'static,
|
||||
T: FromRequest<B> + Send + 'static,
|
||||
H: HandlerCallWithExtractors<T, S, B> + Clone + Send + 'static,
|
||||
T: FromRequest<S, B> + Send + 'static,
|
||||
T::Rejection: Send,
|
||||
B: Send + 'static,
|
||||
S: Clone + Send + 'static,
|
||||
{
|
||||
type Future = BoxFuture<'static, Response>;
|
||||
|
||||
fn call(self, req: http::Request<B>) -> Self::Future {
|
||||
fn call(self, state: S, req: http::Request<B>) -> Self::Future {
|
||||
Box::pin(async move {
|
||||
let mut req = RequestParts::new(req);
|
||||
let mut req = RequestParts::with_state(state.clone(), req);
|
||||
match req.extract::<T>().await {
|
||||
Ok(t) => self.handler.call(t).await,
|
||||
Ok(t) => self.handler.call(state, t).await,
|
||||
Err(rejection) => rejection.into_response(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, B> Copy for IntoHandler<H, T, B> where H: Copy {}
|
||||
impl<H, T, S, B> Copy for IntoHandler<H, T, S, B> where H: Copy {}
|
||||
|
||||
impl<H, T, B> Clone for IntoHandler<H, T, B>
|
||||
impl<H, T, S, B> Clone for IntoHandler<H, T, S, B>
|
||||
where
|
||||
H: Clone,
|
||||
{
|
||||
|
|
|
@ -15,16 +15,16 @@ use std::{future::Future, marker::PhantomData};
|
|||
///
|
||||
/// Created with [`HandlerCallWithExtractors::or`](super::HandlerCallWithExtractors::or).
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Or<L, R, Lt, Rt, B> {
|
||||
pub struct Or<L, R, Lt, Rt, S, B> {
|
||||
pub(super) lhs: L,
|
||||
pub(super) rhs: R,
|
||||
pub(super) _marker: PhantomData<fn() -> (Lt, Rt, B)>,
|
||||
pub(super) _marker: PhantomData<fn() -> (Lt, Rt, S, B)>,
|
||||
}
|
||||
|
||||
impl<B, L, R, Lt, Rt> HandlerCallWithExtractors<Either<Lt, Rt>, B> for Or<L, R, Lt, Rt, B>
|
||||
impl<S, B, L, R, Lt, Rt> HandlerCallWithExtractors<Either<Lt, Rt>, S, B> for Or<L, R, Lt, Rt, S, B>
|
||||
where
|
||||
L: HandlerCallWithExtractors<Lt, B> + Send + 'static,
|
||||
R: HandlerCallWithExtractors<Rt, B> + Send + 'static,
|
||||
L: HandlerCallWithExtractors<Lt, S, B> + Send + 'static,
|
||||
R: HandlerCallWithExtractors<Rt, S, B> + Send + 'static,
|
||||
Rt: Send + 'static,
|
||||
Lt: Send + 'static,
|
||||
B: Send + 'static,
|
||||
|
@ -37,46 +37,48 @@ where
|
|||
|
||||
fn call(
|
||||
self,
|
||||
state: S,
|
||||
extractors: Either<Lt, Rt>,
|
||||
) -> <Self as HandlerCallWithExtractors<Either<Lt, Rt>, B>>::Future {
|
||||
) -> <Self as HandlerCallWithExtractors<Either<Lt, Rt>, S, B>>::Future {
|
||||
match extractors {
|
||||
Either::E1(lt) => self
|
||||
.lhs
|
||||
.call(lt)
|
||||
.call(state, lt)
|
||||
.map(IntoResponse::into_response as _)
|
||||
.left_future(),
|
||||
Either::E2(rt) => self
|
||||
.rhs
|
||||
.call(rt)
|
||||
.call(state, rt)
|
||||
.map(IntoResponse::into_response as _)
|
||||
.right_future(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, L, R, Lt, Rt> Handler<(Lt, Rt), B> for Or<L, R, Lt, Rt, B>
|
||||
impl<S, B, L, R, Lt, Rt> Handler<(Lt, Rt), S, B> for Or<L, R, Lt, Rt, S, B>
|
||||
where
|
||||
L: HandlerCallWithExtractors<Lt, B> + Clone + Send + 'static,
|
||||
R: HandlerCallWithExtractors<Rt, B> + Clone + Send + 'static,
|
||||
Lt: FromRequest<B> + Send + 'static,
|
||||
Rt: FromRequest<B> + Send + 'static,
|
||||
L: HandlerCallWithExtractors<Lt, S, B> + Clone + Send + 'static,
|
||||
R: HandlerCallWithExtractors<Rt, S, B> + Clone + Send + 'static,
|
||||
Lt: FromRequest<S, B> + Send + 'static,
|
||||
Rt: FromRequest<S, B> + Send + 'static,
|
||||
Lt::Rejection: Send,
|
||||
Rt::Rejection: Send,
|
||||
B: Send + 'static,
|
||||
S: Clone + Send + 'static,
|
||||
{
|
||||
// this puts `futures_util` in our public API but thats fine in axum-extra
|
||||
type Future = BoxFuture<'static, Response>;
|
||||
|
||||
fn call(self, req: Request<B>) -> Self::Future {
|
||||
fn call(self, state: S, req: Request<B>) -> Self::Future {
|
||||
Box::pin(async move {
|
||||
let mut req = RequestParts::new(req);
|
||||
let mut req = RequestParts::with_state(state.clone(), req);
|
||||
|
||||
if let Ok(lt) = req.extract::<Lt>().await {
|
||||
return self.lhs.call(lt).await;
|
||||
return self.lhs.call(state, lt).await;
|
||||
}
|
||||
|
||||
if let Ok(rt) = req.extract::<Rt>().await {
|
||||
return self.rhs.call(rt).await;
|
||||
return self.rhs.call(state, rt).await;
|
||||
}
|
||||
|
||||
StatusCode::NOT_FOUND.into_response()
|
||||
|
@ -84,14 +86,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<L, R, Lt, Rt, B> Copy for Or<L, R, Lt, Rt, B>
|
||||
impl<L, R, Lt, Rt, S, B> Copy for Or<L, R, Lt, Rt, S, B>
|
||||
where
|
||||
L: Copy,
|
||||
R: Copy,
|
||||
{
|
||||
}
|
||||
|
||||
impl<L, R, Lt, Rt, B> Clone for Or<L, R, Lt, Rt, B>
|
||||
impl<L, R, Lt, Rt, S, B> Clone for Or<L, R, Lt, Rt, S, B>
|
||||
where
|
||||
L: Clone,
|
||||
R: Clone,
|
||||
|
|
|
@ -98,16 +98,17 @@ impl<S> JsonLines<S, AsResponse> {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B, T> FromRequest<B> for JsonLines<T, AsExtractor>
|
||||
impl<S, B, T> FromRequest<S, B> for JsonLines<T, AsExtractor>
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
B::Data: Into<Bytes>,
|
||||
B::Error: Into<BoxError>,
|
||||
T: DeserializeOwned,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = BodyAlreadyExtracted;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
// `Stream::lines` isn't a thing so we have to convert it into an `AsyncRead`
|
||||
// so we can call `AsyncRead::lines` and then convert it back to a `Stream`
|
||||
|
||||
|
|
|
@ -97,16 +97,17 @@ use std::ops::{Deref, DerefMut};
|
|||
pub struct ProtoBuf<T>(pub T);
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B> FromRequest<B> for ProtoBuf<T>
|
||||
impl<T, S, B> FromRequest<S, B> for ProtoBuf<T>
|
||||
where
|
||||
T: Message + Default,
|
||||
B: HttpBody + Send,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = ProtoBufRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let mut bytes = Bytes::from_request(req).await?;
|
||||
|
||||
match T::decode(&mut bytes) {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
//! Additional types for defining routes.
|
||||
|
||||
use axum::{
|
||||
handler::Handler,
|
||||
handler::{Handler, HandlerWithoutStateExt},
|
||||
http::Request,
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::{any, MethodRouter},
|
||||
Router,
|
||||
};
|
||||
use std::{convert::Infallible, future::ready};
|
||||
use std::{convert::Infallible, future::ready, sync::Arc};
|
||||
use tower_service::Service;
|
||||
|
||||
mod resource;
|
||||
|
@ -29,7 +30,7 @@ pub use self::typed::{FirstElementIs, TypedPath};
|
|||
pub use self::spa::SpaRouter;
|
||||
|
||||
/// Extension trait that adds additional methods to [`Router`].
|
||||
pub trait RouterExt<B>: sealed::Sealed {
|
||||
pub trait RouterExt<S, B>: sealed::Sealed {
|
||||
/// Add a typed `GET` route to the router.
|
||||
///
|
||||
/// The path will be inferred from the first argument to the handler function which must
|
||||
|
@ -39,7 +40,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_get<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
|
@ -52,7 +53,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_delete<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
|
@ -65,7 +66,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_head<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
|
@ -78,7 +79,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_options<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
|
@ -91,7 +92,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_patch<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
|
@ -104,7 +105,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_post<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
|
@ -117,7 +118,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_put<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
|
@ -130,7 +131,7 @@ pub trait RouterExt<B>: sealed::Sealed {
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_trace<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath;
|
||||
|
||||
|
@ -159,7 +160,14 @@ pub trait RouterExt<B>: sealed::Sealed {
|
|||
/// .route_with_tsr("/bar/", get(|| async {}));
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
fn route_with_tsr<T>(self, path: &str, service: T) -> Self
|
||||
fn route_with_tsr(self, path: &str, method_router: MethodRouter<S, B>) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Add another route to the router with an additional "trailing slash redirect" route.
|
||||
///
|
||||
/// This works like [`RouterExt::route_with_tsr`] but accepts any [`Service`].
|
||||
fn route_service_with_tsr<T>(self, path: &str, service: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
|
||||
T::Response: IntoResponse,
|
||||
|
@ -167,14 +175,15 @@ pub trait RouterExt<B>: sealed::Sealed {
|
|||
Self: Sized;
|
||||
}
|
||||
|
||||
impl<B> RouterExt<B> for Router<B>
|
||||
impl<S, B> RouterExt<S, B> for Router<S, B>
|
||||
where
|
||||
B: axum::body::HttpBody + Send + 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_get<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
|
@ -184,7 +193,7 @@ where
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_delete<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
|
@ -194,7 +203,7 @@ where
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_head<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
|
@ -204,7 +213,7 @@ where
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_options<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
|
@ -214,7 +223,7 @@ where
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_patch<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
|
@ -224,7 +233,7 @@ where
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_post<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
|
@ -234,7 +243,7 @@ where
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_put<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
|
@ -244,41 +253,56 @@ where
|
|||
#[cfg(feature = "typed-routing")]
|
||||
fn typed_trace<H, T, P>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: FirstElementIs<P> + 'static,
|
||||
P: TypedPath,
|
||||
{
|
||||
self.route(P::PATH, axum::routing::trace(handler))
|
||||
}
|
||||
|
||||
fn route_with_tsr<T>(mut self, path: &str, service: T) -> Self
|
||||
fn route_with_tsr(mut self, path: &str, method_router: MethodRouter<S, B>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self = self.route(path, method_router);
|
||||
|
||||
let redirect_service = {
|
||||
let path: Arc<str> = path.into();
|
||||
(move || ready(Redirect::permanent(&path))).into_service()
|
||||
};
|
||||
|
||||
if let Some(path_without_trailing_slash) = path.strip_suffix('/') {
|
||||
self.route_service(path_without_trailing_slash, redirect_service)
|
||||
} else {
|
||||
self.route_service(&format!("{}/", path), redirect_service)
|
||||
}
|
||||
}
|
||||
|
||||
fn route_service_with_tsr<T>(mut self, path: &str, service: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
|
||||
T::Response: IntoResponse,
|
||||
T::Future: Send + 'static,
|
||||
Self: Sized,
|
||||
{
|
||||
self = self.route(path, service);
|
||||
self = self.route_service(path, service);
|
||||
|
||||
let redirect = Redirect::permanent(path);
|
||||
|
||||
if let Some(path_without_trailing_slash) = path.strip_suffix('/') {
|
||||
self.route(
|
||||
path_without_trailing_slash,
|
||||
(move || ready(redirect.clone())).into_service(),
|
||||
any(move || ready(redirect.clone())),
|
||||
)
|
||||
} else {
|
||||
self.route(
|
||||
&format!("{}/", path),
|
||||
(move || ready(redirect.clone())).into_service(),
|
||||
)
|
||||
self.route(&format!("{}/", path), any(move || ready(redirect.clone())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed {}
|
||||
impl<B> Sealed for axum::Router<B> {}
|
||||
impl<S, B> Sealed for axum::Router<S, B> {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
handler::Handler,
|
||||
http::Request,
|
||||
response::IntoResponse,
|
||||
routing::{delete, get, on, post, MethodFilter},
|
||||
routing::{delete, get, on, post, MethodFilter, MethodRouter},
|
||||
Router,
|
||||
};
|
||||
use std::{convert::Infallible, fmt};
|
||||
use tower_service::Service;
|
||||
|
||||
/// A resource which defines a set of conventional CRUD routes.
|
||||
///
|
||||
|
@ -34,14 +30,15 @@ use tower_service::Service;
|
|||
/// .destroy(|Path(user_id): Path<u64>| async {});
|
||||
///
|
||||
/// let app = Router::new().merge(users);
|
||||
/// # let _: Router<axum::body::Body> = app;
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
pub struct Resource<B = Body> {
|
||||
#[derive(Debug)]
|
||||
pub struct Resource<S = (), B = Body> {
|
||||
pub(crate) name: String,
|
||||
pub(crate) router: Router<B>,
|
||||
pub(crate) router: Router<S, B>,
|
||||
}
|
||||
|
||||
impl<B> Resource<B>
|
||||
impl<B> Resource<(), B>
|
||||
where
|
||||
B: axum::body::HttpBody + Send + 'static,
|
||||
{
|
||||
|
@ -49,16 +46,29 @@ where
|
|||
///
|
||||
/// All routes will be nested at `/{resource_name}`.
|
||||
pub fn named(resource_name: &str) -> Self {
|
||||
Self::named_with((), resource_name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> Resource<S, B>
|
||||
where
|
||||
B: axum::body::HttpBody + Send + 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
/// Create a `Resource` with the given name and state.
|
||||
///
|
||||
/// All routes will be nested at `/{resource_name}`.
|
||||
pub fn named_with(state: S, resource_name: &str) -> Self {
|
||||
Self {
|
||||
name: resource_name.to_owned(),
|
||||
router: Default::default(),
|
||||
router: Router::with_state(state),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a handler at `GET /{resource_name}`.
|
||||
pub fn index<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = self.index_create_path();
|
||||
|
@ -68,7 +78,7 @@ where
|
|||
/// Add a handler at `POST /{resource_name}`.
|
||||
pub fn create<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = self.index_create_path();
|
||||
|
@ -78,7 +88,7 @@ where
|
|||
/// Add a handler at `GET /{resource_name}/new`.
|
||||
pub fn new<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = format!("/{}/new", self.name);
|
||||
|
@ -88,7 +98,7 @@ where
|
|||
/// Add a handler at `GET /{resource_name}/:{resource_name}_id`.
|
||||
pub fn show<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = self.show_update_destroy_path();
|
||||
|
@ -98,7 +108,7 @@ where
|
|||
/// Add a handler at `GET /{resource_name}/:{resource_name}_id/edit`.
|
||||
pub fn edit<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = format!("/{0}/:{0}_id/edit", self.name);
|
||||
|
@ -108,7 +118,7 @@ where
|
|||
/// Add a handler at `PUT or PATCH /resource_name/:{resource_name}_id`.
|
||||
pub fn update<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = self.show_update_destroy_path();
|
||||
|
@ -118,7 +128,7 @@ where
|
|||
/// Add a handler at `DELETE /{resource_name}/:{resource_name}_id`.
|
||||
pub fn destroy<H, T>(self, handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, B>,
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
let path = self.show_update_destroy_path();
|
||||
|
@ -133,13 +143,8 @@ where
|
|||
format!("/{0}/:{0}_id", self.name)
|
||||
}
|
||||
|
||||
fn route<T>(mut self, path: &str, svc: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
|
||||
T::Response: IntoResponse,
|
||||
T::Future: Send + 'static,
|
||||
{
|
||||
self.router = self.router.route(path, svc);
|
||||
fn route(mut self, path: &str, method_router: MethodRouter<S, B>) -> Self {
|
||||
self.router = self.router.route(path, method_router);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -150,21 +155,13 @@ impl<B> From<Resource<B>> for Router<B> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<B> fmt::Debug for Resource<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Resource")
|
||||
.field("name", &self.name)
|
||||
.field("router", &self.router)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
use axum::{extract::Path, http::Method, Router};
|
||||
use tower::ServiceExt;
|
||||
use http::Request;
|
||||
use tower::{Service, ServiceExt};
|
||||
|
||||
#[tokio::test]
|
||||
async fn works() {
|
||||
|
@ -220,7 +217,7 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
async fn call_route(app: &mut Router, method: Method, uri: &str) -> String {
|
||||
async fn call_route(app: &mut Router<()>, method: Method, uri: &str) -> String {
|
||||
let res = app
|
||||
.ready()
|
||||
.await
|
||||
|
|
|
@ -36,7 +36,7 @@ use tower_service::Service;
|
|||
/// .merge(spa)
|
||||
/// // we can still add other routes
|
||||
/// .route("/api/foo", get(api_foo));
|
||||
/// # let _: Router<axum::body::Body> = app;
|
||||
/// # let _: Router = app;
|
||||
///
|
||||
/// async fn api_foo() {}
|
||||
/// ```
|
||||
|
@ -101,7 +101,7 @@ impl<B, T, F> SpaRouter<B, T, F> {
|
|||
/// .index_file("another_file.html");
|
||||
///
|
||||
/// let app = Router::new().merge(spa);
|
||||
/// # let _: Router<axum::body::Body> = app;
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
pub fn index_file<P>(mut self, path: P) -> Self
|
||||
where
|
||||
|
@ -136,7 +136,7 @@ impl<B, T, F> SpaRouter<B, T, F> {
|
|||
/// }
|
||||
///
|
||||
/// let app = Router::new().merge(spa);
|
||||
/// # let _: Router<axum::body::Body> = app;
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
pub fn handle_error<T2, F2>(self, f: F2) -> SpaRouter<B, T2, F2> {
|
||||
SpaRouter {
|
||||
|
@ -147,7 +147,7 @@ impl<B, T, F> SpaRouter<B, T, F> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<B, F, T> From<SpaRouter<B, T, F>> for Router<B>
|
||||
impl<B, F, T> From<SpaRouter<B, T, F>> for Router<(), B>
|
||||
where
|
||||
F: Clone + Send + 'static,
|
||||
HandleError<Route<B, io::Error>, F, T>: Service<Request<B>, Error = Infallible>,
|
||||
|
@ -162,7 +162,7 @@ where
|
|||
|
||||
Router::new()
|
||||
.nest(&spa.paths.assets_path, assets_service)
|
||||
.fallback(
|
||||
.fallback_service(
|
||||
get_service(ServeFile::new(&spa.paths.index_file)).handle_error(spa.handle_error),
|
||||
)
|
||||
}
|
||||
|
@ -264,6 +264,13 @@ mod tests {
|
|||
|
||||
let spa = SpaRouter::new("/assets", "test_files").handle_error(handle_error);
|
||||
|
||||
Router::<Body>::new().merge(spa);
|
||||
Router::<_, Body>::new().merge(spa);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn works_with_router_with_state() {
|
||||
let _: Router<String> = Router::with_state(String::new())
|
||||
.merge(SpaRouter::new("/assets", "test_files"))
|
||||
.route("/", get(|_: axum::extract::State<String>| async {}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ use http::Uri;
|
|||
/// async fn users_destroy(_: UsersCollection) { /* ... */ }
|
||||
///
|
||||
/// #
|
||||
/// # let app: Router<axum::body::Body> = app;
|
||||
/// # let app: Router = app;
|
||||
/// ```
|
||||
///
|
||||
/// # Using `#[derive(TypedPath)]`
|
||||
|
|
|
@ -203,7 +203,7 @@ fn check_inputs_impls_from_request(item_fn: &ItemFn, body_ty: &Type) -> TokenStr
|
|||
#[allow(warnings)]
|
||||
fn #name()
|
||||
where
|
||||
#ty: ::axum::extract::FromRequest<#body_ty> + Send,
|
||||
#ty: ::axum::extract::FromRequest<(), #body_ty> + Send,
|
||||
{}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -218,16 +218,17 @@ fn impl_struct_by_extracting_each_field(
|
|||
Ok(quote! {
|
||||
#[::axum::async_trait]
|
||||
#[automatically_derived]
|
||||
impl<B> ::axum::extract::FromRequest<B> for #ident
|
||||
impl<S, B> ::axum::extract::FromRequest<S, B> for #ident
|
||||
where
|
||||
B: ::axum::body::HttpBody + ::std::marker::Send + 'static,
|
||||
B::Data: ::std::marker::Send,
|
||||
B::Error: ::std::convert::Into<::axum::BoxError>,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = #rejection_ident;
|
||||
|
||||
async fn from_request(
|
||||
req: &mut ::axum::extract::RequestParts<B>,
|
||||
req: &mut ::axum::extract::RequestParts<S, B>,
|
||||
) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
::std::result::Result::Ok(Self {
|
||||
#(#extract_fields)*
|
||||
|
@ -422,7 +423,7 @@ fn extract_each_field_rejection(
|
|||
|
||||
Ok(quote_spanned! {ty_span=>
|
||||
#[allow(non_camel_case_types)]
|
||||
#variant_name(<#extractor_ty as ::axum::extract::FromRequest<::axum::body::Body>>::Rejection),
|
||||
#variant_name(<#extractor_ty as ::axum::extract::FromRequest<(), ::axum::body::Body>>::Rejection),
|
||||
})
|
||||
})
|
||||
.collect::<syn::Result<Vec<_>>>()?;
|
||||
|
@ -609,26 +610,26 @@ fn impl_struct_by_extracting_all_at_once(
|
|||
quote! { #rejection }
|
||||
} else {
|
||||
quote! {
|
||||
<#path<Self> as ::axum::extract::FromRequest<B>>::Rejection
|
||||
<#path<Self> as ::axum::extract::FromRequest<S, B>>::Rejection
|
||||
}
|
||||
};
|
||||
|
||||
let rejection_bound = rejection.as_ref().map(|rejection| {
|
||||
if generic_ident.is_some() {
|
||||
quote! {
|
||||
#rejection: ::std::convert::From<<#path<T> as ::axum::extract::FromRequest<B>>::Rejection>,
|
||||
#rejection: ::std::convert::From<<#path<T> as ::axum::extract::FromRequest<S, B>>::Rejection>,
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#rejection: ::std::convert::From<<#path<Self> as ::axum::extract::FromRequest<B>>::Rejection>,
|
||||
#rejection: ::std::convert::From<<#path<Self> as ::axum::extract::FromRequest<S, B>>::Rejection>,
|
||||
}
|
||||
}
|
||||
}).unwrap_or_default();
|
||||
|
||||
let impl_generics = if generic_ident.is_some() {
|
||||
quote! { B, T }
|
||||
quote! { S, B, T }
|
||||
} else {
|
||||
quote! { B }
|
||||
quote! { S, B }
|
||||
};
|
||||
|
||||
let type_generics = generic_ident
|
||||
|
@ -653,18 +654,19 @@ fn impl_struct_by_extracting_all_at_once(
|
|||
Ok(quote_spanned! {path_span=>
|
||||
#[::axum::async_trait]
|
||||
#[automatically_derived]
|
||||
impl<#impl_generics> ::axum::extract::FromRequest<B> for #ident #type_generics
|
||||
impl<#impl_generics> ::axum::extract::FromRequest<S, B> for #ident #type_generics
|
||||
where
|
||||
#path<#via_type_generics>: ::axum::extract::FromRequest<B>,
|
||||
#path<#via_type_generics>: ::axum::extract::FromRequest<S, B>,
|
||||
#rejection_bound
|
||||
B: ::std::marker::Send,
|
||||
S: ::std::marker::Send,
|
||||
{
|
||||
type Rejection = #associated_rejection_type;
|
||||
|
||||
async fn from_request(
|
||||
req: &mut ::axum::extract::RequestParts<B>,
|
||||
req: &mut ::axum::extract::RequestParts<S, B>,
|
||||
) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
::axum::extract::FromRequest::<B>::from_request(req)
|
||||
::axum::extract::FromRequest::<S, B>::from_request(req)
|
||||
.await
|
||||
.map(|#path(value)| #value_to_self)
|
||||
.map_err(::std::convert::From::from)
|
||||
|
@ -709,7 +711,7 @@ fn impl_enum_by_extracting_all_at_once(
|
|||
quote! { #rejection }
|
||||
} else {
|
||||
quote! {
|
||||
<#path<Self> as ::axum::extract::FromRequest<B>>::Rejection
|
||||
<#path<Self> as ::axum::extract::FromRequest<S, B>>::Rejection
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -718,18 +720,19 @@ fn impl_enum_by_extracting_all_at_once(
|
|||
Ok(quote_spanned! {path_span=>
|
||||
#[::axum::async_trait]
|
||||
#[automatically_derived]
|
||||
impl<B> ::axum::extract::FromRequest<B> for #ident
|
||||
impl<S, B> ::axum::extract::FromRequest<S, B> for #ident
|
||||
where
|
||||
B: ::axum::body::HttpBody + ::std::marker::Send + 'static,
|
||||
B::Data: ::std::marker::Send,
|
||||
B::Error: ::std::convert::Into<::axum::BoxError>,
|
||||
S: ::std::marker::Send,
|
||||
{
|
||||
type Rejection = #associated_rejection_type;
|
||||
|
||||
async fn from_request(
|
||||
req: &mut ::axum::extract::RequestParts<B>,
|
||||
req: &mut ::axum::extract::RequestParts<S, B>,
|
||||
) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
::axum::extract::FromRequest::<B>::from_request(req)
|
||||
::axum::extract::FromRequest::<S, B>::from_request(req)
|
||||
.await
|
||||
.map(|#path(inner)| inner)
|
||||
.map_err(::std::convert::From::from)
|
||||
|
|
|
@ -125,7 +125,7 @@ mod typed_path;
|
|||
/// ```
|
||||
/// pub struct ViaExtractor<T>(pub T);
|
||||
///
|
||||
/// // impl<T, B> FromRequest<B> for ViaExtractor<T> { ... }
|
||||
/// // impl<T, S, B> FromRequest<S, B> for ViaExtractor<T> { ... }
|
||||
/// ```
|
||||
///
|
||||
/// More complex via extractors are not supported and require writing a manual implementation.
|
||||
|
@ -223,14 +223,15 @@ mod typed_path;
|
|||
/// struct OtherExtractor;
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B> FromRequest<B> for OtherExtractor
|
||||
/// impl<S, B> FromRequest<S, B> for OtherExtractor
|
||||
/// where
|
||||
/// B: Send + 'static,
|
||||
/// B: Send,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// // this rejection doesn't implement `Display` and `Error`
|
||||
/// type Rejection = (StatusCode, String);
|
||||
///
|
||||
/// async fn from_request(_req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
/// async fn from_request(_req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// // ...
|
||||
/// # unimplemented!()
|
||||
/// }
|
||||
|
|
|
@ -127,13 +127,14 @@ fn expand_named_fields(
|
|||
let from_request_impl = quote! {
|
||||
#[::axum::async_trait]
|
||||
#[automatically_derived]
|
||||
impl<B> ::axum::extract::FromRequest<B> for #ident
|
||||
impl<S, B> ::axum::extract::FromRequest<S, B> for #ident
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = #rejection_assoc_type;
|
||||
|
||||
async fn from_request(req: &mut ::axum::extract::RequestParts<B>) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut ::axum::extract::RequestParts<S, B>) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
::axum::extract::Path::from_request(req)
|
||||
.await
|
||||
.map(|path| path.0)
|
||||
|
@ -229,13 +230,14 @@ fn expand_unnamed_fields(
|
|||
let from_request_impl = quote! {
|
||||
#[::axum::async_trait]
|
||||
#[automatically_derived]
|
||||
impl<B> ::axum::extract::FromRequest<B> for #ident
|
||||
impl<S, B> ::axum::extract::FromRequest<S, B> for #ident
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = #rejection_assoc_type;
|
||||
|
||||
async fn from_request(req: &mut ::axum::extract::RequestParts<B>) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut ::axum::extract::RequestParts<S, B>) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
::axum::extract::Path::from_request(req)
|
||||
.await
|
||||
.map(|path| path.0)
|
||||
|
@ -310,13 +312,14 @@ fn expand_unit_fields(
|
|||
let from_request_impl = quote! {
|
||||
#[::axum::async_trait]
|
||||
#[automatically_derived]
|
||||
impl<B> ::axum::extract::FromRequest<B> for #ident
|
||||
impl<S, B> ::axum::extract::FromRequest<S, B> for #ident
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = #rejection_assoc_type;
|
||||
|
||||
async fn from_request(req: &mut ::axum::extract::RequestParts<B>) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut ::axum::extract::RequestParts<S, B>) -> ::std::result::Result<Self, Self::Rejection> {
|
||||
if req.uri().path() == <Self as ::axum_extra::routing::TypedPath>::PATH {
|
||||
Ok(Self)
|
||||
} else {
|
||||
|
@ -387,7 +390,7 @@ enum Segment {
|
|||
|
||||
fn path_rejection() -> TokenStream {
|
||||
quote! {
|
||||
<::axum::extract::Path<Self> as ::axum::extract::FromRequest<B>>::Rejection
|
||||
<::axum::extract::Path<Self> as ::axum::extract::FromRequest<S, B>>::Rejection
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
error[E0277]: the trait bound `bool: FromRequest<Body>` is not satisfied
|
||||
error[E0277]: the trait bound `bool: FromRequest<(), Body>` is not satisfied
|
||||
--> tests/debug_handler/fail/argument_not_extractor.rs:4:23
|
||||
|
|
||||
4 | async fn handler(foo: bool) {}
|
||||
| ^^^^ the trait `FromRequest<Body>` is not implemented for `bool`
|
||||
| ^^^^ the trait `FromRequest<(), Body>` is not implemented for `bool`
|
||||
|
|
||||
= help: the following other types implement trait `FromRequest<B>`:
|
||||
()
|
||||
(T1, T2)
|
||||
(T1, T2, T3)
|
||||
(T1, T2, T3, T4)
|
||||
(T1, T2, T3, T4, T5)
|
||||
(T1, T2, T3, T4, T5, T6)
|
||||
(T1, T2, T3, T4, T5, T6, T7)
|
||||
(T1, T2, T3, T4, T5, T6, T7, T8)
|
||||
and 33 others
|
||||
= help: the following other types implement trait `FromRequest<S, B>`:
|
||||
<() as FromRequest<S, B>>
|
||||
<(T1, T2) as FromRequest<S, B>>
|
||||
<(T1, T2, T3) as FromRequest<S, B>>
|
||||
<(T1, T2, T3, T4) as FromRequest<S, B>>
|
||||
<(T1, T2, T3, T4, T5) as FromRequest<S, B>>
|
||||
<(T1, T2, T3, T4, T5, T6) as FromRequest<S, B>>
|
||||
<(T1, T2, T3, T4, T5, T6, T7) as FromRequest<S, B>>
|
||||
<(T1, T2, T3, T4, T5, T6, T7, T8) as FromRequest<S, B>>
|
||||
and 34 others
|
||||
= help: see issue #48214
|
||||
|
|
|
@ -7,13 +7,14 @@ use axum_macros::debug_handler;
|
|||
struct A;
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for A
|
||||
impl<S, B> FromRequest<S, B> for A
|
||||
where
|
||||
B: Send + 'static,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = ();
|
||||
|
||||
async fn from_request(_req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(_req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: Handlers must only take owned values
|
||||
--> tests/debug_handler/fail/extract_self_mut.rs:23:22
|
||||
--> tests/debug_handler/fail/extract_self_mut.rs:24:22
|
||||
|
|
||||
23 | async fn handler(&mut self) {}
|
||||
24 | async fn handler(&mut self) {}
|
||||
| ^^^^^^^^^
|
||||
|
|
|
@ -7,13 +7,14 @@ use axum_macros::debug_handler;
|
|||
struct A;
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for A
|
||||
impl<S, B> FromRequest<S, B> for A
|
||||
where
|
||||
B: Send + 'static,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = ();
|
||||
|
||||
async fn from_request(_req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(_req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: Handlers must only take owned values
|
||||
--> tests/debug_handler/fail/extract_self_ref.rs:23:22
|
||||
--> tests/debug_handler/fail/extract_self_ref.rs:24:22
|
||||
|
|
||||
23 | async fn handler(&self) {}
|
||||
24 | async fn handler(&self) {}
|
||||
| ^^^^^
|
||||
|
|
|
@ -120,13 +120,14 @@ impl A {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for A
|
||||
impl<S, B> FromRequest<S, B> for A
|
||||
where
|
||||
B: Send + 'static,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = ();
|
||||
|
||||
async fn from_request(_req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(_req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,14 @@ use axum_macros::debug_handler;
|
|||
struct A;
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for A
|
||||
impl<S, B> FromRequest<S, B> for A
|
||||
where
|
||||
B: Send + 'static,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = ();
|
||||
|
||||
async fn from_request(_req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(_req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use axum_macros::FromRequest;
|
||||
use axum::extract::Extension;
|
||||
|
||||
#[derive(FromRequest)]
|
||||
struct Extractor(#[from_request(via(Extension), via(Extension))] State);
|
||||
struct Extractor(#[from_request(via(axum::Extension), via(axum::Extension))] State);
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State;
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
error: `via` specified more than once
|
||||
--> tests/from_request/fail/double_via_attr.rs:5:49
|
||||
--> tests/from_request/fail/double_via_attr.rs:4:55
|
||||
|
|
||||
5 | struct Extractor(#[from_request(via(Extension), via(Extension))] State);
|
||||
| ^^^
|
||||
|
||||
warning: unused import: `axum::extract::Extension`
|
||||
--> tests/from_request/fail/double_via_attr.rs:2:5
|
||||
|
|
||||
2 | use axum::extract::Extension;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
4 | struct Extractor(#[from_request(via(axum::Extension), via(axum::Extension))] State);
|
||||
| ^^^
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use axum::{body::Body, routing::get, Extension, Router};
|
||||
use axum::{body::Body, routing::get, Router};
|
||||
use axum_macros::FromRequest;
|
||||
|
||||
#[derive(FromRequest, Clone)]
|
||||
|
@ -7,5 +7,5 @@ struct Extractor<T>(T);
|
|||
async fn foo(_: Extractor<()>) {}
|
||||
|
||||
fn main() {
|
||||
Router::<Body>::new().route("/", get(foo));
|
||||
Router::<(), Body>::new().route("/", get(foo));
|
||||
}
|
||||
|
|
|
@ -4,23 +4,15 @@ error: #[derive(FromRequest)] only supports generics when used with #[from_reque
|
|||
5 | struct Extractor<T>(T);
|
||||
| ^
|
||||
|
||||
warning: unused import: `Extension`
|
||||
--> tests/from_request/fail/generic_without_via.rs:1:38
|
||||
|
|
||||
1 | use axum::{body::Body, routing::get, Extension, Router};
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
|
||||
error[E0277]: the trait bound `fn(Extractor<()>) -> impl Future<Output = ()> {foo}: Handler<_, _>` is not satisfied
|
||||
--> tests/from_request/fail/generic_without_via.rs:10:42
|
||||
error[E0277]: the trait bound `fn(Extractor<()>) -> impl Future<Output = ()> {foo}: Handler<_, _, _>` is not satisfied
|
||||
--> tests/from_request/fail/generic_without_via.rs:10:46
|
||||
|
|
||||
10 | Router::<Body>::new().route("/", get(foo));
|
||||
| --- ^^^ the trait `Handler<_, _>` is not implemented for `fn(Extractor<()>) -> impl Future<Output = ()> {foo}`
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
10 | Router::<(), Body>::new().route("/", get(foo));
|
||||
| --- ^^^ the trait `Handler<_, _, _>` is not implemented for `fn(Extractor<()>) -> impl Future<Output = ()> {foo}`
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
|
|
||||
= help: the trait `Handler<T, ReqBody>` is implemented for `Layered<S, T>`
|
||||
= help: the trait `Handler<T, S, B>` is implemented for `Layered<L, H, T, S, B>`
|
||||
note: required by a bound in `axum::routing::get`
|
||||
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
||||
|
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use axum::{body::Body, routing::get, Extension, Router};
|
||||
use axum::{body::Body, routing::get, Router};
|
||||
use axum_macros::FromRequest;
|
||||
|
||||
#[derive(FromRequest, Clone)]
|
||||
|
@ -8,5 +8,5 @@ struct Extractor<T>(T);
|
|||
async fn foo(_: Extractor<()>) {}
|
||||
|
||||
fn main() {
|
||||
Router::<Body>::new().route("/", get(foo));
|
||||
Router::<(), Body>::new().route("/", get(foo));
|
||||
}
|
||||
|
|
|
@ -4,23 +4,15 @@ error: #[derive(FromRequest)] only supports generics when used with #[from_reque
|
|||
6 | struct Extractor<T>(T);
|
||||
| ^
|
||||
|
||||
warning: unused import: `Extension`
|
||||
--> tests/from_request/fail/generic_without_via_rejection.rs:1:38
|
||||
|
|
||||
1 | use axum::{body::Body, routing::get, Extension, Router};
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
|
||||
error[E0277]: the trait bound `fn(Extractor<()>) -> impl Future<Output = ()> {foo}: Handler<_, _>` is not satisfied
|
||||
--> tests/from_request/fail/generic_without_via_rejection.rs:11:42
|
||||
error[E0277]: the trait bound `fn(Extractor<()>) -> impl Future<Output = ()> {foo}: Handler<_, _, _>` is not satisfied
|
||||
--> tests/from_request/fail/generic_without_via_rejection.rs:11:46
|
||||
|
|
||||
11 | Router::<Body>::new().route("/", get(foo));
|
||||
| --- ^^^ the trait `Handler<_, _>` is not implemented for `fn(Extractor<()>) -> impl Future<Output = ()> {foo}`
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
11 | Router::<(), Body>::new().route("/", get(foo));
|
||||
| --- ^^^ the trait `Handler<_, _, _>` is not implemented for `fn(Extractor<()>) -> impl Future<Output = ()> {foo}`
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
|
|
||||
= help: the trait `Handler<T, ReqBody>` is implemented for `Layered<S, T>`
|
||||
= help: the trait `Handler<T, S, B>` is implemented for `Layered<L, H, T, S, B>`
|
||||
note: required by a bound in `axum::routing::get`
|
||||
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
||||
|
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use axum::{body::Body, routing::get, Extension, Router};
|
||||
use axum::{body::Body, routing::get, Router};
|
||||
use axum_macros::FromRequest;
|
||||
|
||||
#[derive(FromRequest, Clone)]
|
||||
|
@ -8,5 +8,5 @@ struct Extractor<T>(T);
|
|||
async fn foo(_: Extractor<()>) {}
|
||||
|
||||
fn main() {
|
||||
Router::<Body>::new().route("/", get(foo));
|
||||
Router::<(), Body>::new().route("/", get(foo));
|
||||
}
|
||||
|
|
|
@ -4,23 +4,15 @@ error: #[derive(FromRequest)] only supports generics when used with #[from_reque
|
|||
6 | struct Extractor<T>(T);
|
||||
| ^
|
||||
|
||||
warning: unused import: `Extension`
|
||||
--> tests/from_request/fail/generic_without_via_rejection_derive.rs:1:38
|
||||
|
|
||||
1 | use axum::{body::Body, routing::get, Extension, Router};
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
|
||||
error[E0277]: the trait bound `fn(Extractor<()>) -> impl Future<Output = ()> {foo}: Handler<_, _>` is not satisfied
|
||||
--> tests/from_request/fail/generic_without_via_rejection_derive.rs:11:42
|
||||
error[E0277]: the trait bound `fn(Extractor<()>) -> impl Future<Output = ()> {foo}: Handler<_, _, _>` is not satisfied
|
||||
--> tests/from_request/fail/generic_without_via_rejection_derive.rs:11:46
|
||||
|
|
||||
11 | Router::<Body>::new().route("/", get(foo));
|
||||
| --- ^^^ the trait `Handler<_, _>` is not implemented for `fn(Extractor<()>) -> impl Future<Output = ()> {foo}`
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
11 | Router::<(), Body>::new().route("/", get(foo));
|
||||
| --- ^^^ the trait `Handler<_, _, _>` is not implemented for `fn(Extractor<()>) -> impl Future<Output = ()> {foo}`
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
|
|
||||
= help: the trait `Handler<T, ReqBody>` is implemented for `Layered<S, T>`
|
||||
= help: the trait `Handler<T, S, B>` is implemented for `Layered<L, H, T, S, B>`
|
||||
note: required by a bound in `axum::routing::get`
|
||||
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
||||
|
|
||||
|
|
|
@ -4,15 +4,15 @@ error: cannot use `rejection` without `via`
|
|||
18 | #[from_request(rejection(MyRejection))]
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
error[E0277]: the trait bound `fn(MyExtractor) -> impl Future<Output = ()> {handler}: Handler<_, _>` is not satisfied
|
||||
error[E0277]: the trait bound `fn(MyExtractor) -> impl Future<Output = ()> {handler}: Handler<_, _, _>` is not satisfied
|
||||
--> tests/from_request/fail/override_rejection_on_enum_without_via.rs:10:50
|
||||
|
|
||||
10 | let _: Router = Router::new().route("/", get(handler).post(handler_result));
|
||||
| --- ^^^^^^^ the trait `Handler<_, _>` is not implemented for `fn(MyExtractor) -> impl Future<Output = ()> {handler}`
|
||||
| --- ^^^^^^^ the trait `Handler<_, _, _>` is not implemented for `fn(MyExtractor) -> impl Future<Output = ()> {handler}`
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
|
|
||||
= help: the trait `Handler<T, ReqBody>` is implemented for `Layered<S, T>`
|
||||
= help: the trait `Handler<T, S, B>` is implemented for `Layered<L, H, T, S, B>`
|
||||
note: required by a bound in `axum::routing::get`
|
||||
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
||||
|
|
||||
|
@ -20,18 +20,18 @@ note: required by a bound in `axum::routing::get`
|
|||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `axum::routing::get`
|
||||
= note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `fn(Result<MyExtractor, MyRejection>) -> impl Future<Output = ()> {handler_result}: Handler<_, _>` is not satisfied
|
||||
error[E0277]: the trait bound `fn(Result<MyExtractor, MyRejection>) -> impl Future<Output = ()> {handler_result}: Handler<_, _, _>` is not satisfied
|
||||
--> tests/from_request/fail/override_rejection_on_enum_without_via.rs:10:64
|
||||
|
|
||||
10 | let _: Router = Router::new().route("/", get(handler).post(handler_result));
|
||||
| ---- ^^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for `fn(Result<MyExtractor, MyRejection>) -> impl Future<Output = ()> {handler_result}`
|
||||
| ---- ^^^^^^^^^^^^^^ the trait `Handler<_, _, _>` is not implemented for `fn(Result<MyExtractor, MyRejection>) -> impl Future<Output = ()> {handler_result}`
|
||||
| |
|
||||
| required by a bound introduced by this call
|
||||
|
|
||||
= help: the trait `Handler<T, ReqBody>` is implemented for `Layered<S, T>`
|
||||
note: required by a bound in `MethodRouter::<B>::post`
|
||||
= help: the trait `Handler<T, S, B>` is implemented for `Layered<L, H, T, S, B>`
|
||||
note: required by a bound in `MethodRouter::<S, B>::post`
|
||||
--> $WORKSPACE/axum/src/routing/method_routing.rs
|
||||
|
|
||||
| chained_handler_fn!(post, POST);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MethodRouter::<B>::post`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `MethodRouter::<S, B>::post`
|
||||
= note: this error originates in the macro `chained_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use axum_macros::FromRequest;
|
||||
use axum::extract::Extension;
|
||||
|
||||
#[derive(FromRequest, Clone)]
|
||||
#[from_request(rejection_derive(!Error), via(Extension))]
|
||||
#[from_request(rejection_derive(!Error), via(axum::Extension))]
|
||||
struct Extractor {
|
||||
config: String,
|
||||
}
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
error: cannot use both `rejection_derive` and `via`
|
||||
--> tests/from_request/fail/rejection_derive_and_via.rs:5:42
|
||||
--> tests/from_request/fail/rejection_derive_and_via.rs:4:42
|
||||
|
|
||||
5 | #[from_request(rejection_derive(!Error), via(Extension))]
|
||||
4 | #[from_request(rejection_derive(!Error), via(axum::Extension))]
|
||||
| ^^^
|
||||
|
||||
warning: unused import: `axum::extract::Extension`
|
||||
--> tests/from_request/fail/rejection_derive_and_via.rs:2:5
|
||||
|
|
||||
2 | use axum::extract::Extension;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use axum_macros::FromRequest;
|
||||
use axum::extract::Extension;
|
||||
|
||||
#[derive(FromRequest, Clone)]
|
||||
#[from_request(via(Extension), rejection_derive(!Error))]
|
||||
#[from_request(via(axum::Extension), rejection_derive(!Error))]
|
||||
struct Extractor {
|
||||
config: String,
|
||||
}
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
error: cannot use both `via` and `rejection_derive`
|
||||
--> tests/from_request/fail/via_and_rejection_derive.rs:5:32
|
||||
--> tests/from_request/fail/via_and_rejection_derive.rs:4:38
|
||||
|
|
||||
5 | #[from_request(via(Extension), rejection_derive(!Error))]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: unused import: `axum::extract::Extension`
|
||||
--> tests/from_request/fail/via_and_rejection_derive.rs:2:5
|
||||
|
|
||||
2 | use axum::extract::Extension;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
4 | #[from_request(via(axum::Extension), rejection_derive(!Error))]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use axum_macros::FromRequest;
|
||||
use axum::extract::Extension;
|
||||
|
||||
#[derive(FromRequest)]
|
||||
#[from_request(via(Extension))]
|
||||
struct Extractor(#[from_request(via(Extension))] State);
|
||||
#[from_request(via(axum::Extension))]
|
||||
struct Extractor(#[from_request(via(axum::Extension))] State);
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State;
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
error: `#[from_request(via(...))]` on a field cannot be used together with `#[from_request(...)]` on the container
|
||||
--> tests/from_request/fail/via_on_container_and_field.rs:6:33
|
||||
--> tests/from_request/fail/via_on_container_and_field.rs:5:33
|
||||
|
|
||||
6 | struct Extractor(#[from_request(via(Extension))] State);
|
||||
5 | struct Extractor(#[from_request(via(axum::Extension))] State);
|
||||
| ^^^
|
||||
|
||||
warning: unused import: `axum::extract::Extension`
|
||||
--> tests/from_request/fail/via_on_container_and_field.rs:2:5
|
||||
|
|
||||
2 | use axum::extract::Extension;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` on by default
|
||||
|
|
|
@ -15,7 +15,7 @@ struct Extractor {
|
|||
|
||||
fn assert_from_request()
|
||||
where
|
||||
Extractor: FromRequest<Body, Rejection = JsonRejection>,
|
||||
Extractor: FromRequest<(), Body, Rejection = JsonRejection>,
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -14,13 +14,14 @@ struct Extractor {
|
|||
struct OtherExtractor;
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for OtherExtractor
|
||||
impl<S, B> FromRequest<S, B> for OtherExtractor
|
||||
where
|
||||
B: Send + 'static,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = OtherExtractorRejection;
|
||||
|
||||
async fn from_request(_req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(_req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ struct Extractor {}
|
|||
|
||||
fn assert_from_request()
|
||||
where
|
||||
Extractor: axum::extract::FromRequest<axum::body::Body, Rejection = std::convert::Infallible>,
|
||||
Extractor: axum::extract::FromRequest<(), axum::body::Body, Rejection = std::convert::Infallible>,
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ struct Extractor();
|
|||
|
||||
fn assert_from_request()
|
||||
where
|
||||
Extractor: axum::extract::FromRequest<axum::body::Body, Rejection = std::convert::Infallible>,
|
||||
Extractor: axum::extract::FromRequest<(), axum::body::Body, Rejection = std::convert::Infallible>,
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -8,5 +8,5 @@ enum Extractor {}
|
|||
async fn foo(_: Extractor) {}
|
||||
|
||||
fn main() {
|
||||
Router::<Body>::new().route("/", get(foo));
|
||||
Router::<(), Body>::new().route("/", get(foo));
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ struct Extractor {
|
|||
|
||||
fn assert_from_request()
|
||||
where
|
||||
Extractor: FromRequest<Body, Rejection = ExtractorRejection>,
|
||||
Extractor: FromRequest<(), Body, Rejection = ExtractorRejection>,
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ struct Extractor {
|
|||
|
||||
fn assert_from_request()
|
||||
where
|
||||
Extractor: FromRequest<Body, Rejection = ExtractorRejection>,
|
||||
Extractor: FromRequest<(), Body, Rejection = ExtractorRejection>,
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -28,14 +28,15 @@ struct MyExtractor {
|
|||
struct OtherExtractor;
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for OtherExtractor
|
||||
impl<S, B> FromRequest<S, B> for OtherExtractor
|
||||
where
|
||||
B: Send + 'static,
|
||||
S: Send,
|
||||
{
|
||||
// this rejection doesn't implement `Display` and `Error`
|
||||
type Rejection = (StatusCode, String);
|
||||
|
||||
async fn from_request(_req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(_req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ struct Extractor(axum::http::HeaderMap, String);
|
|||
|
||||
fn assert_from_request()
|
||||
where
|
||||
Extractor: axum::extract::FromRequest<axum::body::Body>,
|
||||
Extractor: axum::extract::FromRequest<(), axum::body::Body>,
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ struct Payload {}
|
|||
|
||||
fn assert_from_request()
|
||||
where
|
||||
Extractor: axum::extract::FromRequest<axum::body::Body>,
|
||||
Extractor: axum::extract::FromRequest<(), axum::body::Body>,
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ struct Payload {}
|
|||
|
||||
fn assert_from_request()
|
||||
where
|
||||
Extractor: axum::extract::FromRequest<axum::body::Body>,
|
||||
Extractor: axum::extract::FromRequest<(), axum::body::Body>,
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use axum::Extension;
|
||||
use axum_macros::FromRequest;
|
||||
use axum::extract::Extension;
|
||||
|
||||
#[derive(FromRequest)]
|
||||
struct Extractor(#[from_request(via(Extension))] State);
|
||||
|
@ -9,7 +9,7 @@ struct State;
|
|||
|
||||
fn assert_from_request()
|
||||
where
|
||||
Extractor: axum::extract::FromRequest<axum::body::Body>,
|
||||
Extractor: axum::extract::FromRequest<(), axum::body::Body>,
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ struct Extractor;
|
|||
|
||||
fn assert_from_request()
|
||||
where
|
||||
Extractor: axum::extract::FromRequest<axum::body::Body, Rejection = std::convert::Infallible>,
|
||||
Extractor: axum::extract::FromRequest<(), axum::body::Body, Rejection = std::convert::Infallible>,
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -15,5 +15,5 @@ error[E0277]: the trait bound `for<'de> MyPath: serde::de::Deserialize<'de>` is
|
|||
(T0, T1, T2, T3)
|
||||
and 138 others
|
||||
= note: required because of the requirements on the impl of `serde::de::DeserializeOwned` for `MyPath`
|
||||
= note: required because of the requirements on the impl of `FromRequest<B>` for `axum::extract::Path<MyPath>`
|
||||
= note: required because of the requirements on the impl of `FromRequest<S, B>` for `axum::extract::Path<MyPath>`
|
||||
= note: this error originates in the derive macro `TypedPath` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
|
|
@ -40,7 +40,7 @@ impl Default for MyRejection {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
axum::Router::<axum::body::Body>::new()
|
||||
axum::Router::<(), axum::body::Body>::new()
|
||||
.typed_get(|_: Result<MyPathNamed, MyRejection>| async {})
|
||||
.typed_post(|_: Result<MyPathUnnamed, MyRejection>| async {})
|
||||
.typed_put(|_: Result<MyPathUnit, MyRejection>| async {});
|
||||
|
|
|
@ -9,7 +9,7 @@ struct MyPath {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
axum::Router::<axum::body::Body>::new().route("/", axum::routing::get(|_: MyPath| async {}));
|
||||
axum::Router::<(), axum::body::Body>::new().route("/", axum::routing::get(|_: MyPath| async {}));
|
||||
|
||||
assert_eq!(MyPath::PATH, "/users/:user_id/teams/:team_id");
|
||||
assert_eq!(
|
||||
|
|
|
@ -20,7 +20,7 @@ struct UsersIndex;
|
|||
async fn result_handler_unit_struct(_: Result<UsersIndex, StatusCode>) {}
|
||||
|
||||
fn main() {
|
||||
axum::Router::<axum::body::Body>::new()
|
||||
axum::Router::<(), axum::body::Body>::new()
|
||||
.typed_get(option_handler)
|
||||
.typed_post(result_handler)
|
||||
.typed_post(result_handler_unit_struct);
|
||||
|
|
|
@ -8,7 +8,7 @@ pub type Result<T> = std::result::Result<T, ()>;
|
|||
struct MyPath(u32, u32);
|
||||
|
||||
fn main() {
|
||||
axum::Router::<axum::body::Body>::new().route("/", axum::routing::get(|_: MyPath| async {}));
|
||||
axum::Router::<(), axum::body::Body>::new().route("/", axum::routing::get(|_: MyPath| async {}));
|
||||
|
||||
assert_eq!(MyPath::PATH, "/users/:user_id/teams/:team_id");
|
||||
assert_eq!(format!("{}", MyPath(1, 2)), "/users/1/teams/2");
|
||||
|
|
|
@ -5,7 +5,7 @@ use axum_extra::routing::TypedPath;
|
|||
struct MyPath;
|
||||
|
||||
fn main() {
|
||||
axum::Router::<axum::body::Body>::new()
|
||||
axum::Router::<(), axum::body::Body>::new()
|
||||
.route("/", axum::routing::get(|_: MyPath| async {}));
|
||||
|
||||
assert_eq!(MyPath::PATH, "/users");
|
||||
|
|
|
@ -8,5 +8,5 @@ struct MyPath {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
axum::Router::<axum::body::Body>::new().typed_get(|_: MyPath| async {});
|
||||
axum::Router::<(), axum::body::Body>::new().typed_get(|_: MyPath| async {});
|
||||
}
|
||||
|
|
|
@ -35,6 +35,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- **added:** Added `debug_handler` which is an attribute macro that improves
|
||||
type errors when applied to handler function. It is re-exported from
|
||||
`axum-macros`
|
||||
- **added:** Added new type safe `State` extractor. This can be used with
|
||||
`Router::with_state` and gives compile errors for missing states, whereas
|
||||
`Extension` would result in runtime errors ([#1155])
|
||||
- **breaking:** The following types or traits have a new `S` type param
|
||||
(`()` by default) which represents the state ([#1155]):
|
||||
- `FromRequest`
|
||||
- `RequestParts`
|
||||
- `Router`
|
||||
- `MethodRouter`
|
||||
- `Handler`
|
||||
- **breaking:** `Router::route` now only accepts `MethodRouter`s created with
|
||||
`get`, `post`, etc ([#1155])
|
||||
- **added:** `Router::route_service` for routing to arbitrary `Service`s ([#1155])
|
||||
- **added:** Support any middleware response that implements `IntoResponse` ([#1152])
|
||||
- **breaking:** Require middleware added with `Handler::layer` to have
|
||||
`Infallible` as the error type ([#1152])
|
||||
|
@ -54,6 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
[#1130]: https://github.com/tokio-rs/axum/pull/1130
|
||||
[#1135]: https://github.com/tokio-rs/axum/pull/1135
|
||||
[#1152]: https://github.com/tokio-rs/axum/pull/1152
|
||||
[#1155]: https://github.com/tokio-rs/axum/pull/1155
|
||||
[#1171]: https://github.com/tokio-rs/axum/pull/1171
|
||||
[#1239]: https://github.com/tokio-rs/axum/pull/1239
|
||||
[#1248]: https://github.com/tokio-rs/axum/pull/1248
|
||||
|
|
|
@ -69,7 +69,7 @@ let some_fallible_service = tower::service_fn(|_req| async {
|
|||
Ok::<_, anyhow::Error>(Response::new(Body::empty()))
|
||||
});
|
||||
|
||||
let app = Router::new().route(
|
||||
let app = Router::new().route_service(
|
||||
"/",
|
||||
// we cannot route to `some_fallible_service` directly since it might fail.
|
||||
// we have to use `handle_error` which converts its errors into responses
|
||||
|
|
|
@ -421,13 +421,14 @@ use http::{StatusCode, header::{HeaderValue, USER_AGENT}};
|
|||
struct ExtractUserAgent(HeaderValue);
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for ExtractUserAgent
|
||||
impl<S, B> FromRequest<S, B> for ExtractUserAgent
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = (StatusCode, &'static str);
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
if let Some(user_agent) = req.headers().get(USER_AGENT) {
|
||||
Ok(ExtractUserAgent(user_agent.clone()))
|
||||
} else {
|
||||
|
@ -472,13 +473,14 @@ struct AuthenticatedUser {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for AuthenticatedUser
|
||||
impl<S, B> FromRequest<S, B> for AuthenticatedUser
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Response;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let TypedHeader(Authorization(token)) =
|
||||
TypedHeader::<Authorization<Bearer>>::from_request(req)
|
||||
.await
|
||||
|
@ -633,7 +635,7 @@ fn token_is_valid(token: &str) -> bool {
|
|||
}
|
||||
|
||||
let app = Router::new().layer(middleware::from_fn(auth_middleware));
|
||||
# let _: Router = app;
|
||||
# let _: Router<()> = app;
|
||||
```
|
||||
|
||||
[`body::Body`]: crate::body::Body
|
||||
|
|
|
@ -11,7 +11,7 @@ use axum::{
|
|||
http::{StatusCode, Method, Uri},
|
||||
};
|
||||
|
||||
let handler = get(|| async {}).fallback(fallback.into_service());
|
||||
let handler = get(|| async {}).fallback(fallback);
|
||||
|
||||
let app = Router::new().route("/", handler);
|
||||
|
||||
|
@ -36,11 +36,9 @@ use axum::{
|
|||
http::{StatusCode, Uri},
|
||||
};
|
||||
|
||||
let one = get(|| async {})
|
||||
.fallback(fallback_one.into_service());
|
||||
let one = get(|| async {}).fallback(fallback_one);
|
||||
|
||||
let two = post(|| async {})
|
||||
.fallback(fallback_two.into_service());
|
||||
let two = post(|| async {}).fallback(fallback_two);
|
||||
|
||||
let method_route = one.merge(two);
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
- [Ordering](#ordering)
|
||||
- [Writing middleware](#writing-middleware)
|
||||
- [Routing to services/middleware and backpressure](#routing-to-servicesmiddleware-and-backpressure)
|
||||
- [Sharing state between handlers and middleware](#sharing-state-between-handlers-and-middleware)
|
||||
- [Accessing state in middleware](#accessing-state-in-middleware)
|
||||
- [Passing state from middleware to handlers](#passing-state-from-middleware-to-handlers)
|
||||
|
||||
# Intro
|
||||
|
||||
|
@ -95,7 +96,7 @@ let app = Router::new()
|
|||
.layer(layer_one)
|
||||
.layer(layer_two)
|
||||
.layer(layer_three);
|
||||
# let app: Router<axum::body::Body> = app;
|
||||
# let _: Router<(), axum::body::Body> = app;
|
||||
```
|
||||
|
||||
Think of the middleware as being layered like an onion where each new layer
|
||||
|
@ -154,7 +155,7 @@ let app = Router::new()
|
|||
.layer(layer_two)
|
||||
.layer(layer_three),
|
||||
);
|
||||
# let app: Router<axum::body::Body> = app;
|
||||
# let _: Router<(), axum::body::Body> = app;
|
||||
```
|
||||
|
||||
`ServiceBuilder` works by composing all layers into one such that they run top
|
||||
|
@ -386,9 +387,119 @@ Also note that handlers created from async functions don't care about
|
|||
backpressure and are always ready. So if you're not using any Tower
|
||||
middleware you don't have to worry about any of this.
|
||||
|
||||
# Sharing state between handlers and middleware
|
||||
# Accessing state in middleware
|
||||
|
||||
State can be shared between middleware and handlers using [request extensions]:
|
||||
Handlers can access state using the [`State`] extractor but this isn't available
|
||||
to middleware. Instead you have to pass the state directly to middleware using
|
||||
either closure captures (for [`axum::middleware::from_fn`]) or regular struct
|
||||
fields (if you're implementing a [`tower::Layer`])
|
||||
|
||||
## Accessing state in `axum::middleware::from_fn`
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
Router,
|
||||
routing::get,
|
||||
middleware::{self, Next},
|
||||
response::Response,
|
||||
extract::State,
|
||||
http::Request,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {}
|
||||
|
||||
async fn my_middleware<B>(
|
||||
state: AppState,
|
||||
req: Request<B>,
|
||||
next: Next<B>,
|
||||
) -> Response {
|
||||
next.run(req).await
|
||||
}
|
||||
|
||||
async fn handler(_: State<AppState>) {}
|
||||
|
||||
let state = AppState {};
|
||||
|
||||
let app = Router::with_state(state.clone())
|
||||
.route("/", get(handler))
|
||||
.layer(middleware::from_fn(move |req, next| {
|
||||
my_middleware(state.clone(), req, next)
|
||||
}));
|
||||
# let _: Router<_> = app;
|
||||
```
|
||||
|
||||
## Accessing state in custom `tower::Layer`s
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
Router,
|
||||
routing::get,
|
||||
middleware::{self, Next},
|
||||
response::Response,
|
||||
extract::State,
|
||||
http::Request,
|
||||
};
|
||||
use tower::{Layer, Service};
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MyLayer {
|
||||
state: AppState,
|
||||
}
|
||||
|
||||
impl<S> Layer<S> for MyLayer {
|
||||
type Service = MyService<S>;
|
||||
|
||||
fn layer(&self, inner: S) -> Self::Service {
|
||||
MyService {
|
||||
inner,
|
||||
state: self.state.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct MyService<S> {
|
||||
inner: S,
|
||||
state: AppState,
|
||||
}
|
||||
|
||||
impl<S, B> Service<Request<B>> for MyService<S>
|
||||
where
|
||||
S: Service<Request<B>>,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||
// do something with `self.state`
|
||||
|
||||
self.inner.call(req)
|
||||
}
|
||||
}
|
||||
|
||||
async fn handler(_: State<AppState>) {}
|
||||
|
||||
let state = AppState {};
|
||||
|
||||
let app = Router::with_state(state.clone())
|
||||
.route("/", get(handler))
|
||||
.layer(MyLayer { state });
|
||||
# let _: Router<_> = app;
|
||||
```
|
||||
|
||||
# Passing state from middleware to handlers
|
||||
|
||||
State can be passed from middleware to handlers using [request extensions]:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
|
@ -415,6 +526,8 @@ async fn auth<B>(mut req: Request<B>, next: Next<B>) -> Result<Response, StatusC
|
|||
};
|
||||
|
||||
if let Some(current_user) = authorize_current_user(auth_header).await {
|
||||
// insert the current user into a request extension so the handler can
|
||||
// extract it
|
||||
req.extensions_mut().insert(current_user);
|
||||
Ok(next.run(req).await)
|
||||
} else {
|
||||
|
@ -437,7 +550,7 @@ async fn handler(
|
|||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.route_layer(middleware::from_fn(auth));
|
||||
# let app: Router = app;
|
||||
# let _: Router<()> = app;
|
||||
```
|
||||
|
||||
[Response extensions] can also be used but note that request extensions are not
|
||||
|
@ -462,3 +575,4 @@ extensions you need.
|
|||
[`MethodRouter::route_layer`]: crate::routing::MethodRouter::route_layer
|
||||
[request extensions]: https://docs.rs/http/latest/http/request/struct.Request.html#method.extensions
|
||||
[Response extensions]: https://docs.rs/http/latest/http/response/struct.Response.html#method.extensions
|
||||
[`State`]: crate::extract::State
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Add a fallback service to the router.
|
||||
Add a fallback [`Handler`] to the router.
|
||||
|
||||
This service will be called if no routes matches the incoming request.
|
||||
|
||||
|
@ -13,7 +13,7 @@ use axum::{
|
|||
|
||||
let app = Router::new()
|
||||
.route("/foo", get(|| async { /* ... */ }))
|
||||
.fallback(fallback.into_service());
|
||||
.fallback(fallback);
|
||||
|
||||
async fn fallback(uri: Uri) -> (StatusCode, String) {
|
||||
(StatusCode::NOT_FOUND, format!("No route for {}", uri))
|
||||
|
|
|
@ -104,7 +104,7 @@ let api_routes = Router::new().nest("/users", get(|| async {}));
|
|||
|
||||
let app = Router::new()
|
||||
.nest("/api", api_routes)
|
||||
.fallback(fallback.into_service());
|
||||
.fallback(fallback);
|
||||
# let _: Router = app;
|
||||
```
|
||||
|
||||
|
@ -132,11 +132,11 @@ async fn api_fallback() -> (StatusCode, Json<Value>) {
|
|||
let api_routes = Router::new()
|
||||
.nest("/users", get(|| async {}))
|
||||
// add dedicated fallback for requests starting with `/api`
|
||||
.fallback(api_fallback.into_service());
|
||||
.fallback(api_fallback);
|
||||
|
||||
let app = Router::new()
|
||||
.nest("/api", api_routes)
|
||||
.fallback(fallback.into_service());
|
||||
.fallback(fallback);
|
||||
# let _: Router = app;
|
||||
```
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ Add another route to the router.
|
|||
`path` is a string of path segments separated by `/`. Each segment
|
||||
can be either static, a capture, or a wildcard.
|
||||
|
||||
`service` is the [`Service`] that should receive the request if the path matches
|
||||
`path`. `service` will commonly be a handler wrapped in a method router like
|
||||
[`get`](crate::routing::get). See [`handler`](crate::handler) for more details
|
||||
on handlers.
|
||||
`method_router` is the [`MethodRouter`] that should receive the request if the
|
||||
path matches `path`. `method_router` will commonly be a handler wrapped in a method
|
||||
router like [`get`](crate::routing::get). See [`handler`](crate::handler) for
|
||||
more details on handlers.
|
||||
|
||||
# Static paths
|
||||
|
||||
|
@ -105,69 +105,6 @@ async fn serve_asset(Path(path): Path<String>) {}
|
|||
# };
|
||||
```
|
||||
|
||||
# Routing to any [`Service`]
|
||||
|
||||
axum also supports routing to general [`Service`]s:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
Router,
|
||||
body::Body,
|
||||
routing::{any_service, get_service},
|
||||
http::{Request, StatusCode},
|
||||
error_handling::HandleErrorLayer,
|
||||
};
|
||||
use tower_http::services::ServeFile;
|
||||
use http::Response;
|
||||
use std::{convert::Infallible, io};
|
||||
use tower::service_fn;
|
||||
|
||||
let app = Router::new()
|
||||
.route(
|
||||
// Any request to `/` goes to a service
|
||||
"/",
|
||||
// Services whose response body is not `axum::body::BoxBody`
|
||||
// can be wrapped in `axum::routing::any_service` (or one of the other routing filters)
|
||||
// to have the response body mapped
|
||||
any_service(service_fn(|_: Request<Body>| async {
|
||||
let res = Response::new(Body::from("Hi from `GET /`"));
|
||||
Ok::<_, Infallible>(res)
|
||||
}))
|
||||
)
|
||||
.route(
|
||||
"/foo",
|
||||
// This service's response body is `axum::body::BoxBody` so
|
||||
// it can be routed to directly.
|
||||
service_fn(|req: Request<Body>| async move {
|
||||
let body = Body::from(format!("Hi from `{} /foo`", req.method()));
|
||||
let body = axum::body::boxed(body);
|
||||
let res = Response::new(body);
|
||||
Ok::<_, Infallible>(res)
|
||||
})
|
||||
)
|
||||
.route(
|
||||
// GET `/static/Cargo.toml` goes to a service from tower-http
|
||||
"/static/Cargo.toml",
|
||||
get_service(ServeFile::new("Cargo.toml"))
|
||||
// though we must handle any potential errors
|
||||
.handle_error(|error: io::Error| async move {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Unhandled internal error: {}", error),
|
||||
)
|
||||
})
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Routing to arbitrary services in this way has complications for backpressure
|
||||
([`Service::poll_ready`]). See the [Routing to services and backpressure] module
|
||||
for more details.
|
||||
|
||||
[Routing to services and backpressure]: middleware/index.html#routing-to-servicesmiddleware-and-backpressure
|
||||
|
||||
# Panics
|
||||
|
||||
Panics if the route overlaps with another route:
|
||||
|
@ -187,21 +124,3 @@ The static route `/foo` and the dynamic route `/:key` are not considered to
|
|||
overlap and `/foo` will take precedence.
|
||||
|
||||
Also panics if `path` is empty.
|
||||
|
||||
## Nesting
|
||||
|
||||
`route` cannot be used to nest `Router`s. Instead use [`Router::nest`].
|
||||
|
||||
Attempting to will result in a panic:
|
||||
|
||||
```rust,should_panic
|
||||
use axum::{routing::get, Router};
|
||||
|
||||
let app = Router::new().route(
|
||||
"/",
|
||||
Router::new().route("/foo", get(|| async {})),
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
|
81
axum/src/docs/routing/route_service.md
Normal file
81
axum/src/docs/routing/route_service.md
Normal file
|
@ -0,0 +1,81 @@
|
|||
Add another route to the router that calls a [`Service`].
|
||||
|
||||
# Example
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
Router,
|
||||
body::Body,
|
||||
routing::{any_service, get_service},
|
||||
http::{Request, StatusCode},
|
||||
error_handling::HandleErrorLayer,
|
||||
};
|
||||
use tower_http::services::ServeFile;
|
||||
use http::Response;
|
||||
use std::{convert::Infallible, io};
|
||||
use tower::service_fn;
|
||||
|
||||
let app = Router::new()
|
||||
.route(
|
||||
// Any request to `/` goes to a service
|
||||
"/",
|
||||
// Services whose response body is not `axum::body::BoxBody`
|
||||
// can be wrapped in `axum::routing::any_service` (or one of the other routing filters)
|
||||
// to have the response body mapped
|
||||
any_service(service_fn(|_: Request<Body>| async {
|
||||
let res = Response::new(Body::from("Hi from `GET /`"));
|
||||
Ok::<_, Infallible>(res)
|
||||
}))
|
||||
)
|
||||
.route_service(
|
||||
"/foo",
|
||||
// This service's response body is `axum::body::BoxBody` so
|
||||
// it can be routed to directly.
|
||||
service_fn(|req: Request<Body>| async move {
|
||||
let body = Body::from(format!("Hi from `{} /foo`", req.method()));
|
||||
let body = axum::body::boxed(body);
|
||||
let res = Response::new(body);
|
||||
Ok::<_, Infallible>(res)
|
||||
})
|
||||
)
|
||||
.route(
|
||||
// GET `/static/Cargo.toml` goes to a service from tower-http
|
||||
"/static/Cargo.toml",
|
||||
get_service(ServeFile::new("Cargo.toml"))
|
||||
// though we must handle any potential errors
|
||||
.handle_error(|error: io::Error| async move {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Unhandled internal error: {}", error),
|
||||
)
|
||||
})
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Routing to arbitrary services in this way has complications for backpressure
|
||||
([`Service::poll_ready`]). See the [Routing to services and backpressure] module
|
||||
for more details.
|
||||
|
||||
# Panics
|
||||
|
||||
Panics for the same reasons as [`Router::route`] or if you attempt to route to a
|
||||
`Router`:
|
||||
|
||||
```rust,should_panic
|
||||
use axum::{routing::get, Router};
|
||||
|
||||
let app = Router::new().route_service(
|
||||
"/",
|
||||
Router::new().route("/foo", get(|| async {})),
|
||||
);
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
```
|
||||
|
||||
Use [`Router::nest`] instead.
|
||||
|
||||
[Routing to services and backpressure]: middleware/index.html#routing-to-servicesmiddleware-and-backpressure
|
|
@ -1,7 +1,6 @@
|
|||
#![doc = include_str!("../docs/error_handling.md")]
|
||||
|
||||
use crate::{
|
||||
body::boxed,
|
||||
extract::{FromRequest, RequestParts},
|
||||
http::{Request, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
|
@ -113,16 +112,16 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<S, F, ReqBody, Fut, Res> Service<Request<ReqBody>> for HandleError<S, F, ()>
|
||||
impl<S, F, B, Fut, Res> Service<Request<B>> for HandleError<S, F, ()>
|
||||
where
|
||||
S: Service<Request<ReqBody>> + Clone + Send + 'static,
|
||||
S: Service<Request<B>> + Clone + Send + 'static,
|
||||
S::Response: IntoResponse + Send,
|
||||
S::Error: Send,
|
||||
S::Future: Send,
|
||||
F: FnOnce(S::Error) -> Fut + Clone + Send + 'static,
|
||||
Fut: Future<Output = Res> + Send,
|
||||
Res: IntoResponse,
|
||||
ReqBody: Send + 'static,
|
||||
B: Send + 'static,
|
||||
{
|
||||
type Response = Response;
|
||||
type Error = Infallible;
|
||||
|
@ -132,7 +131,7 @@ where
|
|||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
|
||||
fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||
let f = self.f.clone();
|
||||
|
||||
let clone = self.inner.clone();
|
||||
|
@ -152,18 +151,18 @@ where
|
|||
#[allow(unused_macros)]
|
||||
macro_rules! impl_service {
|
||||
( $($ty:ident),* $(,)? ) => {
|
||||
impl<S, F, ReqBody, Res, Fut, $($ty,)*> Service<Request<ReqBody>>
|
||||
impl<S, F, B, Res, Fut, $($ty,)*> Service<Request<B>>
|
||||
for HandleError<S, F, ($($ty,)*)>
|
||||
where
|
||||
S: Service<Request<ReqBody>> + Clone + Send + 'static,
|
||||
S: Service<Request<B>> + Clone + Send + 'static,
|
||||
S::Response: IntoResponse + Send,
|
||||
S::Error: Send,
|
||||
S::Future: Send,
|
||||
F: FnOnce($($ty),*, S::Error) -> Fut + Clone + Send + 'static,
|
||||
Fut: Future<Output = Res> + Send,
|
||||
Res: IntoResponse,
|
||||
$( $ty: FromRequest<ReqBody> + Send,)*
|
||||
ReqBody: Send + 'static,
|
||||
$( $ty: FromRequest<(), B> + Send,)*
|
||||
B: Send + 'static,
|
||||
{
|
||||
type Response = Response;
|
||||
type Error = Infallible;
|
||||
|
@ -175,7 +174,7 @@ macro_rules! impl_service {
|
|||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
|
||||
fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||
let f = self.f.clone();
|
||||
|
||||
let clone = self.inner.clone();
|
||||
|
@ -187,7 +186,7 @@ macro_rules! impl_service {
|
|||
$(
|
||||
let $ty = match $ty::from_request(&mut req).await {
|
||||
Ok(value) => value,
|
||||
Err(rejection) => return Ok(rejection.into_response().map(boxed)),
|
||||
Err(rejection) => return Ok(rejection.into_response()),
|
||||
};
|
||||
)*
|
||||
|
||||
|
@ -200,7 +199,7 @@ macro_rules! impl_service {
|
|||
|
||||
match inner.oneshot(req).await {
|
||||
Ok(res) => Ok(res.into_response()),
|
||||
Err(err) => Ok(f($($ty),*, err).await.into_response().map(boxed)),
|
||||
Err(err) => Ok(f($($ty),*, err).await.into_response()),
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -73,14 +73,15 @@ use tower_service::Service;
|
|||
pub struct Extension<T>(pub T);
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B> FromRequest<B> for Extension<T>
|
||||
impl<T, S, B> FromRequest<S, B> for Extension<T>
|
||||
where
|
||||
T: Clone + Send + Sync + 'static,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = ExtensionRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let value = req
|
||||
.extensions()
|
||||
.get::<T>()
|
||||
|
|
|
@ -128,14 +128,15 @@ opaque_future! {
|
|||
pub struct ConnectInfo<T>(pub T);
|
||||
|
||||
#[async_trait]
|
||||
impl<B, T> FromRequest<B> for ConnectInfo<T>
|
||||
impl<S, B, T> FromRequest<S, B> for ConnectInfo<T>
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
T: Clone + Send + Sync + 'static,
|
||||
{
|
||||
type Rejection = <Extension<Self> as FromRequest<B>>::Rejection;
|
||||
type Rejection = <Extension<Self> as FromRequest<S, B>>::Rejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let Extension(connect_info) = Extension::<Self>::from_request(req).await?;
|
||||
Ok(connect_info)
|
||||
}
|
||||
|
|
|
@ -36,15 +36,16 @@ use std::ops::Deref;
|
|||
pub struct ContentLengthLimit<T, const N: u64>(pub T);
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B, const N: u64> FromRequest<B> for ContentLengthLimit<T, N>
|
||||
impl<T, S, B, const N: u64> FromRequest<S, B> for ContentLengthLimit<T, N>
|
||||
where
|
||||
T: FromRequest<B>,
|
||||
T: FromRequest<S, B>,
|
||||
T::Rejection: IntoResponse,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = ContentLengthLimitRejection<T::Rejection>;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let content_length = req
|
||||
.headers()
|
||||
.get(http::header::CONTENT_LENGTH)
|
||||
|
|
|
@ -21,13 +21,14 @@ const X_FORWARDED_HOST_HEADER_KEY: &str = "X-Forwarded-Host";
|
|||
pub struct Host(pub String);
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for Host
|
||||
impl<S, B> FromRequest<S, B> for Host
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = HostRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
if let Some(host) = parse_forwarded(req.headers()) {
|
||||
return Ok(Host(host.to_owned()));
|
||||
}
|
||||
|
|
|
@ -64,13 +64,14 @@ impl MatchedPath {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for MatchedPath
|
||||
impl<S, B> FromRequest<S, B> for MatchedPath
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = MatchedPathRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let matched_path = req
|
||||
.extensions()
|
||||
.get::<Self>()
|
||||
|
@ -84,7 +85,9 @@ where
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{extract::Extension, handler::Handler, routing::get, test_helpers::*, Router};
|
||||
use crate::{
|
||||
extract::Extension, handler::HandlerWithoutStateExt, routing::get, test_helpers::*, Router,
|
||||
};
|
||||
use http::{Request, StatusCode};
|
||||
use std::task::{Context, Poll};
|
||||
use tower::layer::layer_fn;
|
||||
|
@ -93,7 +96,7 @@ mod tests {
|
|||
#[derive(Clone)]
|
||||
struct SetMatchedPathExtension<S>(S);
|
||||
|
||||
impl<B, S> Service<Request<B>> for SetMatchedPathExtension<S>
|
||||
impl<S, B> Service<Request<B>> for SetMatchedPathExtension<S>
|
||||
where
|
||||
S: Service<Request<B>>,
|
||||
{
|
||||
|
|
|
@ -14,9 +14,10 @@ mod content_length_limit;
|
|||
mod host;
|
||||
mod raw_query;
|
||||
mod request_parts;
|
||||
mod state;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use axum_core::extract::{FromRequest, RequestParts};
|
||||
pub use axum_core::extract::{FromRef, FromRequest, RequestParts};
|
||||
|
||||
#[doc(inline)]
|
||||
#[allow(deprecated)]
|
||||
|
@ -27,6 +28,7 @@ pub use self::{
|
|||
path::Path,
|
||||
raw_query::RawQuery,
|
||||
request_parts::{BodyStream, RawBody},
|
||||
state::State,
|
||||
};
|
||||
|
||||
#[doc(no_inline)]
|
||||
|
@ -73,13 +75,13 @@ pub use self::ws::WebSocketUpgrade;
|
|||
#[doc(no_inline)]
|
||||
pub use crate::TypedHeader;
|
||||
|
||||
pub(crate) fn take_body<B>(req: &mut RequestParts<B>) -> Result<B, BodyAlreadyExtracted> {
|
||||
pub(crate) fn take_body<S, B>(req: &mut RequestParts<S, B>) -> Result<B, BodyAlreadyExtracted> {
|
||||
req.take_body().ok_or_else(BodyAlreadyExtracted::default)
|
||||
}
|
||||
|
||||
// this is duplicated in `axum-extra/src/extract/form.rs`
|
||||
pub(super) fn has_content_type<B>(
|
||||
req: &RequestParts<B>,
|
||||
pub(super) fn has_content_type<S, B>(
|
||||
req: &RequestParts<S, B>,
|
||||
expected_content_type: &mime::Mime,
|
||||
) -> bool {
|
||||
let content_type = if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) {
|
||||
|
|
|
@ -50,14 +50,15 @@ pub struct Multipart {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for Multipart
|
||||
impl<S, B> FromRequest<S, B> for Multipart
|
||||
where
|
||||
B: HttpBody<Data = Bytes> + Default + Unpin + Send + 'static,
|
||||
B::Error: Into<BoxError>,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = MultipartRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let stream = BodyStream::from_request(req).await?;
|
||||
let headers = req.headers();
|
||||
let boundary = parse_boundary(headers).ok_or(InvalidBoundary)?;
|
||||
|
@ -179,7 +180,7 @@ impl<'a> Field<'a> {
|
|||
/// }
|
||||
///
|
||||
/// let app = Router::new().route("/upload", post(upload));
|
||||
/// # let _: Router<axum::body::Body> = app;
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
pub async fn chunk(&mut self) -> Result<Option<Bytes>, MultipartError> {
|
||||
self.inner
|
||||
|
|
|
@ -163,14 +163,15 @@ impl<T> DerefMut for Path<T> {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B> FromRequest<B> for Path<T>
|
||||
impl<T, S, B> FromRequest<S, B> for Path<T>
|
||||
where
|
||||
T: DeserializeOwned + Send,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = PathRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let params = match req.extensions_mut().get::<UrlParams>() {
|
||||
Some(UrlParams::Params(params)) => params,
|
||||
Some(UrlParams::InvalidUtf8InPathParam { key }) => {
|
||||
|
|
|
@ -49,14 +49,15 @@ use std::ops::Deref;
|
|||
pub struct Query<T>(pub T);
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B> FromRequest<B> for Query<T>
|
||||
impl<T, S, B> FromRequest<S, B> for Query<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = QueryRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let query = req.uri().query().unwrap_or_default();
|
||||
let value = serde_urlencoded::from_str(query)
|
||||
.map_err(FailedToDeserializeQueryString::__private_new)?;
|
||||
|
@ -81,7 +82,8 @@ mod tests {
|
|||
use std::fmt::Debug;
|
||||
|
||||
async fn check<T: DeserializeOwned + PartialEq + Debug>(uri: impl AsRef<str>, value: T) {
|
||||
let mut req = RequestParts::new(Request::builder().uri(uri.as_ref()).body(()).unwrap());
|
||||
let req = Request::builder().uri(uri.as_ref()).body(()).unwrap();
|
||||
let mut req = RequestParts::new(req);
|
||||
assert_eq!(Query::<T>::from_request(&mut req).await.unwrap().0, value);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,13 +27,14 @@ use std::convert::Infallible;
|
|||
pub struct RawQuery(pub Option<String>);
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for RawQuery
|
||||
impl<S, B> FromRequest<S, B> for RawQuery
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let query = req.uri().query().map(|query| query.to_owned());
|
||||
Ok(Self(query))
|
||||
}
|
||||
|
|
|
@ -86,13 +86,14 @@ pub struct OriginalUri(pub Uri);
|
|||
|
||||
#[cfg(feature = "original-uri")]
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for OriginalUri
|
||||
impl<S, B> FromRequest<S, B> for OriginalUri
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let uri = Extension::<Self>::from_request(req)
|
||||
.await
|
||||
.unwrap_or_else(|_| Extension(OriginalUri(req.uri().clone())))
|
||||
|
@ -140,15 +141,16 @@ impl Stream for BodyStream {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for BodyStream
|
||||
impl<S, B> FromRequest<S, B> for BodyStream
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
B::Data: Into<Bytes>,
|
||||
B::Error: Into<BoxError>,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = BodyAlreadyExtracted;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let body = take_body(req)?
|
||||
.map_data(Into::into)
|
||||
.map_err(|err| Error::new(err.into()));
|
||||
|
@ -196,13 +198,14 @@ fn body_stream_traits() {
|
|||
pub struct RawBody<B = Body>(pub B);
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for RawBody<B>
|
||||
impl<S, B> FromRequest<S, B> for RawBody<B>
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = BodyAlreadyExtracted;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
let body = take_body(req)?;
|
||||
Ok(Self(body))
|
||||
}
|
||||
|
|
207
axum/src/extract/state.rs
Normal file
207
axum/src/extract/state.rs
Normal file
|
@ -0,0 +1,207 @@
|
|||
use async_trait::async_trait;
|
||||
use axum_core::extract::{FromRef, FromRequest, RequestParts};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
/// Extractor for state.
|
||||
///
|
||||
/// Note this extractor is not available to middleware. See ["Accessing state in
|
||||
/// middleware"][state-from-middleware] for how to access state in middleware.
|
||||
///
|
||||
/// [state-from-middleware]: ../middleware/index.html#accessing-state-in-middleware
|
||||
///
|
||||
/// # With `Router`
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{Router, routing::get, extract::State};
|
||||
///
|
||||
/// // the application state
|
||||
/// //
|
||||
/// // here you can put configuration, database connection pools, or whatever
|
||||
/// // state you need
|
||||
/// #[derive(Clone)]
|
||||
/// struct AppState {}
|
||||
///
|
||||
/// let state = AppState {};
|
||||
///
|
||||
/// // create a `Router` that holds our state
|
||||
/// let app = Router::with_state(state).route("/", get(handler));
|
||||
///
|
||||
/// async fn handler(
|
||||
/// // access the state via the `State` extractor
|
||||
/// // extracting a state of the wrong type results in a compile error
|
||||
/// State(state): State<AppState>,
|
||||
/// ) {
|
||||
/// // use `state`...
|
||||
/// }
|
||||
/// # let _: Router<AppState> = app;
|
||||
/// ```
|
||||
///
|
||||
/// # With `MethodRouter`
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{routing::get, extract::State};
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// struct AppState {}
|
||||
///
|
||||
/// let state = AppState {};
|
||||
///
|
||||
/// let method_router_with_state = get(handler)
|
||||
/// // provide the state so the handler can access it
|
||||
/// .with_state(state);
|
||||
///
|
||||
/// async fn handler(State(state): State<AppState>) {
|
||||
/// // use `state`...
|
||||
/// }
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(method_router_with_state.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// # With `Handler`
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{routing::get, handler::Handler, extract::State};
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// struct AppState {}
|
||||
///
|
||||
/// let state = AppState {};
|
||||
///
|
||||
/// async fn handler(State(state): State<AppState>) {
|
||||
/// // use `state`...
|
||||
/// }
|
||||
///
|
||||
/// // provide the state so the handler can access it
|
||||
/// let handler_with_state = handler.with_state(state);
|
||||
///
|
||||
/// # async {
|
||||
/// axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
/// .serve(handler_with_state.into_make_service())
|
||||
/// .await
|
||||
/// .expect("server failed");
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// # Substates
|
||||
///
|
||||
/// [`State`] only allows a single state type but you can use [`From`] to extract "substates":
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{Router, routing::get, extract::{State, FromRef}};
|
||||
///
|
||||
/// // the application state
|
||||
/// #[derive(Clone)]
|
||||
/// struct AppState {
|
||||
/// // that holds some api specific state
|
||||
/// api_state: ApiState,
|
||||
/// }
|
||||
///
|
||||
/// // the api specific state
|
||||
/// #[derive(Clone)]
|
||||
/// struct ApiState {}
|
||||
///
|
||||
/// // support converting an `AppState` in an `ApiState`
|
||||
/// impl FromRef<AppState> for ApiState {
|
||||
/// fn from_ref(app_state: &AppState) -> ApiState {
|
||||
/// app_state.api_state.clone()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let state = AppState {
|
||||
/// api_state: ApiState {},
|
||||
/// };
|
||||
///
|
||||
/// let app = Router::with_state(state)
|
||||
/// .route("/", get(handler))
|
||||
/// .route("/api/users", get(api_users));
|
||||
///
|
||||
/// async fn api_users(
|
||||
/// // access the api specific state
|
||||
/// State(api_state): State<ApiState>,
|
||||
/// ) {
|
||||
/// }
|
||||
///
|
||||
/// async fn handler(
|
||||
/// // we can still access to top level state
|
||||
/// State(state): State<AppState>,
|
||||
/// ) {
|
||||
/// }
|
||||
/// # let _: Router<AppState> = app;
|
||||
/// ```
|
||||
///
|
||||
/// # For library authors
|
||||
///
|
||||
/// If you're writing a library that has an extractor that needs state, this is the recommended way
|
||||
/// to do it:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum_core::extract::{FromRequest, RequestParts, FromRef};
|
||||
/// use async_trait::async_trait;
|
||||
/// use std::convert::Infallible;
|
||||
///
|
||||
/// // the extractor your library provides
|
||||
/// struct MyLibraryExtractor;
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<S, B> FromRequest<S, B> for MyLibraryExtractor
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// // keep `S` generic but require that it can produce a `MyLibraryState`
|
||||
/// // this means users will have to implement `FromRef<UserState> for MyLibraryState`
|
||||
/// MyLibraryState: FromRef<S>,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// type Rejection = Infallible;
|
||||
///
|
||||
/// async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// // get a `MyLibraryState` from a reference to the state
|
||||
/// let state = MyLibraryState::from_ref(req.state());
|
||||
///
|
||||
/// // ...
|
||||
/// # todo!()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // the state your library needs
|
||||
/// struct MyLibraryState {
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Note that you don't need to use the `State` extractor since you can access the state directly
|
||||
/// from [`RequestParts`].
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct State<S>(pub S);
|
||||
|
||||
#[async_trait]
|
||||
impl<B, OuterState, InnerState> FromRequest<OuterState, B> for State<InnerState>
|
||||
where
|
||||
B: Send,
|
||||
InnerState: FromRef<OuterState>,
|
||||
OuterState: Send,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<OuterState, B>) -> Result<Self, Self::Rejection> {
|
||||
let inner_state = InnerState::from_ref(req.state());
|
||||
Ok(Self(inner_state))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Deref for State<S> {
|
||||
type Target = S;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DerefMut for State<S> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
|
@ -275,13 +275,14 @@ impl WebSocketUpgrade {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<B> FromRequest<B> for WebSocketUpgrade
|
||||
impl<S, B> FromRequest<S, B> for WebSocketUpgrade
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = WebSocketUpgradeRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
if req.method() != Method::GET {
|
||||
return Err(MethodNotGet.into());
|
||||
}
|
||||
|
@ -320,7 +321,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn header_eq<B>(req: &RequestParts<B>, key: HeaderName, value: &'static str) -> bool {
|
||||
fn header_eq<S, B>(req: &RequestParts<S, B>, key: HeaderName, value: &'static str) -> bool {
|
||||
if let Some(header) = req.headers().get(&key) {
|
||||
header.as_bytes().eq_ignore_ascii_case(value.as_bytes())
|
||||
} else {
|
||||
|
@ -328,7 +329,7 @@ fn header_eq<B>(req: &RequestParts<B>, key: HeaderName, value: &'static str) ->
|
|||
}
|
||||
}
|
||||
|
||||
fn header_contains<B>(req: &RequestParts<B>, key: HeaderName, value: &'static str) -> bool {
|
||||
fn header_contains<S, B>(req: &RequestParts<S, B>, key: HeaderName, value: &'static str) -> bool {
|
||||
let header = if let Some(header) = req.headers().get(&key) {
|
||||
header
|
||||
} else {
|
||||
|
|
|
@ -56,16 +56,17 @@ use std::ops::Deref;
|
|||
pub struct Form<T>(pub T);
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B> FromRequest<B> for Form<T>
|
||||
impl<T, S, B> FromRequest<S, B> for Form<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
B: HttpBody + Send,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = FormRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
if req.method() == Method::GET {
|
||||
let query = req.uri().query().unwrap_or_default();
|
||||
let value = serde_urlencoded::from_str(query)
|
||||
|
@ -125,29 +126,27 @@ mod tests {
|
|||
}
|
||||
|
||||
async fn check_query<T: DeserializeOwned + PartialEq + Debug>(uri: impl AsRef<str>, value: T) {
|
||||
let mut req = RequestParts::new(
|
||||
Request::builder()
|
||||
.uri(uri.as_ref())
|
||||
.body(Empty::<Bytes>::new())
|
||||
.unwrap(),
|
||||
);
|
||||
let req = Request::builder()
|
||||
.uri(uri.as_ref())
|
||||
.body(Empty::<Bytes>::new())
|
||||
.unwrap();
|
||||
let mut req = RequestParts::new(req);
|
||||
assert_eq!(Form::<T>::from_request(&mut req).await.unwrap().0, value);
|
||||
}
|
||||
|
||||
async fn check_body<T: Serialize + DeserializeOwned + PartialEq + Debug>(value: T) {
|
||||
let mut req = RequestParts::new(
|
||||
Request::builder()
|
||||
.uri("http://example.com/test")
|
||||
.method(Method::POST)
|
||||
.header(
|
||||
http::header::CONTENT_TYPE,
|
||||
mime::APPLICATION_WWW_FORM_URLENCODED.as_ref(),
|
||||
)
|
||||
.body(Full::<Bytes>::new(
|
||||
serde_urlencoded::to_string(&value).unwrap().into(),
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
let req = Request::builder()
|
||||
.uri("http://example.com/test")
|
||||
.method(Method::POST)
|
||||
.header(
|
||||
http::header::CONTENT_TYPE,
|
||||
mime::APPLICATION_WWW_FORM_URLENCODED.as_ref(),
|
||||
)
|
||||
.body(Full::<Bytes>::new(
|
||||
serde_urlencoded::to_string(&value).unwrap().into(),
|
||||
))
|
||||
.unwrap();
|
||||
let mut req = RequestParts::new(req);
|
||||
assert_eq!(Form::<T>::from_request(&mut req).await.unwrap().0, value);
|
||||
}
|
||||
|
||||
|
@ -204,21 +203,20 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn test_incorrect_content_type() {
|
||||
let mut req = RequestParts::new(
|
||||
Request::builder()
|
||||
.uri("http://example.com/test")
|
||||
.method(Method::POST)
|
||||
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
|
||||
.body(Full::<Bytes>::new(
|
||||
serde_urlencoded::to_string(&Pagination {
|
||||
size: Some(10),
|
||||
page: None,
|
||||
})
|
||||
.unwrap()
|
||||
.into(),
|
||||
))
|
||||
.unwrap(),
|
||||
);
|
||||
let req = Request::builder()
|
||||
.uri("http://example.com/test")
|
||||
.method(Method::POST)
|
||||
.header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
|
||||
.body(Full::<Bytes>::new(
|
||||
serde_urlencoded::to_string(&Pagination {
|
||||
size: Some(10),
|
||||
page: None,
|
||||
})
|
||||
.unwrap()
|
||||
.into(),
|
||||
))
|
||||
.unwrap();
|
||||
let mut req = RequestParts::new(req);
|
||||
assert!(matches!(
|
||||
Form::<Pagination>::from_request(&mut req)
|
||||
.await
|
||||
|
|
|
@ -19,29 +19,29 @@ opaque_future! {
|
|||
|
||||
pin_project! {
|
||||
/// The response future for [`Layered`](super::Layered).
|
||||
pub struct LayeredFuture<S, ReqBody>
|
||||
pub struct LayeredFuture<B, S>
|
||||
where
|
||||
S: Service<Request<ReqBody>>,
|
||||
S: Service<Request<B>>,
|
||||
{
|
||||
#[pin]
|
||||
inner: Map<Oneshot<S, Request<ReqBody>>, fn(Result<S::Response, S::Error>) -> Response>,
|
||||
inner: Map<Oneshot<S, Request<B>>, fn(Result<S::Response, S::Error>) -> Response>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, ReqBody> LayeredFuture<S, ReqBody>
|
||||
impl<B, S> LayeredFuture<B, S>
|
||||
where
|
||||
S: Service<Request<ReqBody>>,
|
||||
S: Service<Request<B>>,
|
||||
{
|
||||
pub(super) fn new(
|
||||
inner: Map<Oneshot<S, Request<ReqBody>>, fn(Result<S::Response, S::Error>) -> Response>,
|
||||
inner: Map<Oneshot<S, Request<B>>, fn(Result<S::Response, S::Error>) -> Response>,
|
||||
) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, ReqBody> Future for LayeredFuture<S, ReqBody>
|
||||
impl<B, S> Future for LayeredFuture<B, S>
|
||||
where
|
||||
S: Service<Request<ReqBody>>,
|
||||
S: Service<Request<B>>,
|
||||
{
|
||||
type Output = Response;
|
||||
|
||||
|
|
|
@ -11,29 +11,40 @@ use tower_service::Service;
|
|||
|
||||
/// An adapter that makes a [`Handler`] into a [`Service`].
|
||||
///
|
||||
/// Created with [`Handler::into_service`].
|
||||
pub struct IntoService<H, T, B> {
|
||||
/// Created with [`HandlerWithoutStateExt::into_service`].
|
||||
///
|
||||
/// [`HandlerWithoutStateExt::into_service`]: super::HandlerWithoutStateExt::into_service
|
||||
pub struct IntoService<H, T, S, B> {
|
||||
handler: H,
|
||||
state: S,
|
||||
_marker: PhantomData<fn() -> (T, B)>,
|
||||
}
|
||||
|
||||
impl<H, T, S, B> IntoService<H, T, S, B> {
|
||||
/// Get a reference to the state.
|
||||
pub fn state(&self) -> &S {
|
||||
&self.state
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn traits() {
|
||||
use crate::test_helpers::*;
|
||||
assert_send::<IntoService<(), NotSendSync, NotSendSync>>();
|
||||
assert_sync::<IntoService<(), NotSendSync, NotSendSync>>();
|
||||
assert_send::<IntoService<(), NotSendSync, (), NotSendSync>>();
|
||||
assert_sync::<IntoService<(), NotSendSync, (), NotSendSync>>();
|
||||
}
|
||||
|
||||
impl<H, T, B> IntoService<H, T, B> {
|
||||
pub(super) fn new(handler: H) -> Self {
|
||||
impl<H, T, S, B> IntoService<H, T, S, B> {
|
||||
pub(super) fn new(handler: H, state: S) -> Self {
|
||||
Self {
|
||||
handler,
|
||||
state,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, B> fmt::Debug for IntoService<H, T, B> {
|
||||
impl<H, T, S, B> fmt::Debug for IntoService<H, T, S, B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("IntoService")
|
||||
.field(&format_args!("..."))
|
||||
|
@ -41,22 +52,25 @@ impl<H, T, B> fmt::Debug for IntoService<H, T, B> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<H, T, B> Clone for IntoService<H, T, B>
|
||||
impl<H, T, S, B> Clone for IntoService<H, T, S, B>
|
||||
where
|
||||
H: Clone,
|
||||
S: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
handler: self.handler.clone(),
|
||||
state: self.state.clone(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, B> Service<Request<B>> for IntoService<H, T, B>
|
||||
impl<H, T, S, B> Service<Request<B>> for IntoService<H, T, S, B>
|
||||
where
|
||||
H: Handler<T, B> + Clone + Send + 'static,
|
||||
H: Handler<T, S, B> + Clone + Send + 'static,
|
||||
B: Send + 'static,
|
||||
S: Clone,
|
||||
{
|
||||
type Response = Response;
|
||||
type Error = Infallible;
|
||||
|
@ -74,7 +88,7 @@ where
|
|||
use futures_util::future::FutureExt;
|
||||
|
||||
let handler = self.handler.clone();
|
||||
let future = Handler::call(handler, req);
|
||||
let future = Handler::call(handler, self.state.clone(), req);
|
||||
let future = future.map(Ok as _);
|
||||
|
||||
super::future::IntoServiceFuture::new(future)
|
||||
|
|
85
axum/src/handler/into_service_state_in_extension.rs
Normal file
85
axum/src/handler/into_service_state_in_extension.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use super::Handler;
|
||||
use crate::response::Response;
|
||||
use http::Request;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
fmt,
|
||||
marker::PhantomData,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower_service::Service;
|
||||
|
||||
pub(crate) struct IntoServiceStateInExtension<H, T, S, B> {
|
||||
handler: H,
|
||||
_marker: PhantomData<fn() -> (T, S, B)>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn traits() {
|
||||
use crate::test_helpers::*;
|
||||
assert_send::<IntoServiceStateInExtension<(), NotSendSync, (), NotSendSync>>();
|
||||
assert_sync::<IntoServiceStateInExtension<(), NotSendSync, (), NotSendSync>>();
|
||||
}
|
||||
|
||||
impl<H, T, S, B> IntoServiceStateInExtension<H, T, S, B> {
|
||||
pub(crate) fn new(handler: H) -> Self {
|
||||
Self {
|
||||
handler,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, S, B> fmt::Debug for IntoServiceStateInExtension<H, T, S, B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("IntoServiceStateInExtension")
|
||||
.field(&format_args!("..."))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, S, B> Clone for IntoServiceStateInExtension<H, T, S, B>
|
||||
where
|
||||
H: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
handler: self.handler.clone(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, S, B> Service<Request<B>> for IntoServiceStateInExtension<H, T, S, B>
|
||||
where
|
||||
H: Handler<T, S, B> + Clone + Send + 'static,
|
||||
B: Send + 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
type Response = Response;
|
||||
type Error = Infallible;
|
||||
type Future = super::future::IntoServiceFuture<H::Future>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
// `IntoServiceStateInExtension` can only be constructed from async functions which are always ready, or
|
||||
// from `Layered` which bufferes in `<Layered as Handler>::call` and is therefore
|
||||
// also always ready.
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: Request<B>) -> Self::Future {
|
||||
use futures_util::future::FutureExt;
|
||||
|
||||
let state = req
|
||||
.extensions_mut()
|
||||
.remove::<S>()
|
||||
.expect("state extension missing. This is a bug in axum, please file an issue");
|
||||
|
||||
let handler = self.handler.clone();
|
||||
let future = Handler::call(handler, state, req);
|
||||
let future = future.map(Ok as _);
|
||||
|
||||
super::future::IntoServiceFuture::new(future)
|
||||
}
|
||||
}
|
|
@ -49,8 +49,11 @@ use tower_service::Service;
|
|||
|
||||
pub mod future;
|
||||
mod into_service;
|
||||
mod into_service_state_in_extension;
|
||||
mod with_state;
|
||||
|
||||
pub use self::into_service::IntoService;
|
||||
pub(crate) use self::into_service_state_in_extension::IntoServiceStateInExtension;
|
||||
pub use self::{into_service::IntoService, with_state::WithState};
|
||||
|
||||
/// Trait for async functions that can be used to handle requests.
|
||||
///
|
||||
|
@ -59,13 +62,45 @@ pub use self::into_service::IntoService;
|
|||
///
|
||||
/// See the [module docs](crate::handler) for more details.
|
||||
///
|
||||
/// # Converting `Handler`s into [`Service`]s
|
||||
///
|
||||
/// To convert `Handler`s into [`Service`]s you have to call either
|
||||
/// [`HandlerWithoutStateExt::into_service`] or [`Handler::with_state`]:
|
||||
///
|
||||
/// ```
|
||||
/// use tower::Service;
|
||||
/// use axum::{
|
||||
/// extract::State,
|
||||
/// body::Body,
|
||||
/// http::Request,
|
||||
/// handler::{HandlerWithoutStateExt, Handler},
|
||||
/// };
|
||||
///
|
||||
/// // this handler doesn't require any state
|
||||
/// async fn one() {}
|
||||
/// // so it can be converted to a service with `HandlerWithoutStateExt::into_service`
|
||||
/// assert_service(one.into_service());
|
||||
///
|
||||
/// // this handler requires state
|
||||
/// async fn two(_: State<String>) {}
|
||||
/// // so we have to provide it
|
||||
/// let handler_with_state = two.with_state(String::new());
|
||||
/// // which gives us a `Service`
|
||||
/// assert_service(handler_with_state);
|
||||
///
|
||||
/// // helper to check that a value implements `Service`
|
||||
/// fn assert_service<S>(service: S)
|
||||
/// where
|
||||
/// S: Service<Request<Body>>,
|
||||
/// {}
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/debugging_handler_type_errors.md")]
|
||||
pub trait Handler<T, B = Body>: Clone + Send + Sized + 'static {
|
||||
pub trait Handler<T, S = (), B = Body>: Clone + Send + Sized + 'static {
|
||||
/// The type of future calling this handler returns.
|
||||
type Future: Future<Output = Response> + Send + 'static;
|
||||
|
||||
/// Call the handler with the given request.
|
||||
fn call(self, req: Request<B>) -> Self::Future;
|
||||
fn call(self, state: S, req: Request<B>) -> Self::Future;
|
||||
|
||||
/// Apply a [`tower::Layer`] to the handler.
|
||||
///
|
||||
|
@ -103,112 +138,26 @@ pub trait Handler<T, B = Body>: Clone + Send + Sized + 'static {
|
|||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
fn layer<L>(self, layer: L) -> Layered<L::Service, T>
|
||||
fn layer<L>(self, layer: L) -> Layered<L, Self, T, S, B>
|
||||
where
|
||||
L: Layer<IntoService<Self, T, B>>,
|
||||
L: Layer<WithState<Self, T, S, B>>,
|
||||
{
|
||||
Layered::new(layer.layer(self.into_service()))
|
||||
Layered {
|
||||
layer,
|
||||
handler: self,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the handler into a [`Service`].
|
||||
///
|
||||
/// This is commonly used together with [`Router::fallback`]:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Server,
|
||||
/// handler::Handler,
|
||||
/// http::{Uri, Method, StatusCode},
|
||||
/// response::IntoResponse,
|
||||
/// routing::{get, Router},
|
||||
/// };
|
||||
/// use tower::make::Shared;
|
||||
/// use std::net::SocketAddr;
|
||||
///
|
||||
/// async fn handler(method: Method, uri: Uri) -> (StatusCode, String) {
|
||||
/// (StatusCode::NOT_FOUND, format!("Nothing to see at {} {}", method, uri))
|
||||
/// }
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/", get(|| async {}))
|
||||
/// .fallback(handler.into_service());
|
||||
///
|
||||
/// # async {
|
||||
/// Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000)))
|
||||
/// .serve(app.into_make_service())
|
||||
/// .await?;
|
||||
/// # Ok::<_, hyper::Error>(())
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// [`Router::fallback`]: crate::routing::Router::fallback
|
||||
fn into_service(self) -> IntoService<Self, T, B> {
|
||||
IntoService::new(self)
|
||||
}
|
||||
|
||||
/// Convert the handler into a [`MakeService`].
|
||||
///
|
||||
/// This allows you to serve a single handler if you don't need any routing:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Server, handler::Handler, http::{Uri, Method}, response::IntoResponse,
|
||||
/// };
|
||||
/// use std::net::SocketAddr;
|
||||
///
|
||||
/// async fn handler(method: Method, uri: Uri, body: String) -> String {
|
||||
/// format!("received `{} {}` with body `{:?}`", method, uri, body)
|
||||
/// }
|
||||
///
|
||||
/// # async {
|
||||
/// Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000)))
|
||||
/// .serve(handler.into_make_service())
|
||||
/// .await?;
|
||||
/// # Ok::<_, hyper::Error>(())
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
fn into_make_service(self) -> IntoMakeService<IntoService<Self, T, B>> {
|
||||
IntoMakeService::new(self.into_service())
|
||||
}
|
||||
|
||||
/// Convert the handler into a [`MakeService`] which stores information
|
||||
/// about the incoming connection.
|
||||
///
|
||||
/// See [`Router::into_make_service_with_connect_info`] for more details.
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Server,
|
||||
/// handler::Handler,
|
||||
/// response::IntoResponse,
|
||||
/// extract::ConnectInfo,
|
||||
/// };
|
||||
/// use std::net::SocketAddr;
|
||||
///
|
||||
/// async fn handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> String {
|
||||
/// format!("Hello {}", addr)
|
||||
/// }
|
||||
///
|
||||
/// # async {
|
||||
/// Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000)))
|
||||
/// .serve(handler.into_make_service_with_connect_info::<SocketAddr>())
|
||||
/// .await?;
|
||||
/// # Ok::<_, hyper::Error>(())
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
|
||||
fn into_make_service_with_connect_info<C>(
|
||||
self,
|
||||
) -> IntoMakeServiceWithConnectInfo<IntoService<Self, T, B>, C> {
|
||||
IntoMakeServiceWithConnectInfo::new(self.into_service())
|
||||
/// Convert the handler into a [`Service`] by providing the state
|
||||
fn with_state(self, state: S) -> WithState<Self, T, S, B> {
|
||||
WithState {
|
||||
service: IntoService::new(self, state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, Fut, Res, B> Handler<(), B> for F
|
||||
impl<F, Fut, Res, S, B> Handler<(), S, B> for F
|
||||
where
|
||||
F: FnOnce() -> Fut + Clone + Send + 'static,
|
||||
Fut: Future<Output = Res> + Send,
|
||||
|
@ -217,7 +166,7 @@ where
|
|||
{
|
||||
type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||
|
||||
fn call(self, _req: Request<B>) -> Self::Future {
|
||||
fn call(self, _state: S, _req: Request<B>) -> Self::Future {
|
||||
Box::pin(async move { self().await.into_response() })
|
||||
}
|
||||
}
|
||||
|
@ -225,19 +174,20 @@ where
|
|||
macro_rules! impl_handler {
|
||||
( $($ty:ident),* $(,)? ) => {
|
||||
#[allow(non_snake_case)]
|
||||
impl<F, Fut, B, Res, $($ty,)*> Handler<($($ty,)*), B> for F
|
||||
impl<F, Fut, S, B, Res, $($ty,)*> Handler<($($ty,)*), S, B> for F
|
||||
where
|
||||
F: FnOnce($($ty,)*) -> Fut + Clone + Send + 'static,
|
||||
Fut: Future<Output = Res> + Send,
|
||||
B: Send + 'static,
|
||||
S: Send + 'static,
|
||||
Res: IntoResponse,
|
||||
$( $ty: FromRequest<B> + Send,)*
|
||||
$( $ty: FromRequest<S, B> + Send,)*
|
||||
{
|
||||
type Future = Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||
|
||||
fn call(self, req: Request<B>) -> Self::Future {
|
||||
fn call(self, state: S, req: Request<B>) -> Self::Future {
|
||||
Box::pin(async move {
|
||||
let mut req = RequestParts::new(req);
|
||||
let mut req = RequestParts::with_state(state, req);
|
||||
|
||||
$(
|
||||
let $ty = match $ty::from_request(&mut req).await {
|
||||
|
@ -260,58 +210,116 @@ all_the_tuples!(impl_handler);
|
|||
/// A [`Service`] created from a [`Handler`] by applying a Tower middleware.
|
||||
///
|
||||
/// Created with [`Handler::layer`]. See that method for more details.
|
||||
pub struct Layered<S, T> {
|
||||
svc: S,
|
||||
_input: PhantomData<fn() -> T>,
|
||||
pub struct Layered<L, H, T, S, B> {
|
||||
layer: L,
|
||||
handler: H,
|
||||
_marker: PhantomData<fn() -> (T, S, B)>,
|
||||
}
|
||||
|
||||
impl<S, T> fmt::Debug for Layered<S, T>
|
||||
impl<L, H, T, S, B> fmt::Debug for Layered<L, H, T, S, B>
|
||||
where
|
||||
S: fmt::Debug,
|
||||
L: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Layered").field("svc", &self.svc).finish()
|
||||
f.debug_struct("Layered")
|
||||
.field("layer", &self.layer)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> Clone for Layered<S, T>
|
||||
impl<L, H, T, S, B> Clone for Layered<L, H, T, S, B>
|
||||
where
|
||||
S: Clone,
|
||||
L: Clone,
|
||||
H: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self::new(self.svc.clone())
|
||||
Self {
|
||||
layer: self.layer.clone(),
|
||||
handler: self.handler.clone(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T, ReqBody> Handler<T, ReqBody> for Layered<S, T>
|
||||
impl<H, S, T, B, L> Handler<T, S, B> for Layered<L, H, T, S, B>
|
||||
where
|
||||
S: Service<Request<ReqBody>, Error = Infallible> + Clone + Send + 'static,
|
||||
S::Response: IntoResponse,
|
||||
S::Future: Send,
|
||||
L: Layer<WithState<H, T, S, B>> + Clone + Send + 'static,
|
||||
H: Handler<T, S, B>,
|
||||
L::Service: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
|
||||
<L::Service as Service<Request<B>>>::Response: IntoResponse,
|
||||
<L::Service as Service<Request<B>>>::Future: Send,
|
||||
T: 'static,
|
||||
ReqBody: Send + 'static,
|
||||
S: 'static,
|
||||
B: Send + 'static,
|
||||
{
|
||||
type Future = future::LayeredFuture<S, ReqBody>;
|
||||
type Future = future::LayeredFuture<B, L::Service>;
|
||||
|
||||
fn call(self, req: Request<ReqBody>) -> Self::Future {
|
||||
fn call(self, state: S, req: Request<B>) -> Self::Future {
|
||||
use futures_util::future::{FutureExt, Map};
|
||||
|
||||
let future: Map<_, fn(Result<S::Response, S::Error>) -> _> =
|
||||
self.svc.oneshot(req).map(|result| match result {
|
||||
Ok(res) => res.into_response(),
|
||||
Err(err) => match err {},
|
||||
});
|
||||
let svc = self.handler.with_state(state);
|
||||
let svc = self.layer.layer(svc);
|
||||
|
||||
let future: Map<
|
||||
_,
|
||||
fn(
|
||||
Result<
|
||||
<L::Service as Service<Request<B>>>::Response,
|
||||
<L::Service as Service<Request<B>>>::Error,
|
||||
>,
|
||||
) -> _,
|
||||
> = svc.oneshot(req).map(|result| match result {
|
||||
Ok(res) => res.into_response(),
|
||||
Err(err) => match err {},
|
||||
});
|
||||
|
||||
future::LayeredFuture::new(future)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> Layered<S, T> {
|
||||
pub(crate) fn new(svc: S) -> Self {
|
||||
Self {
|
||||
svc,
|
||||
_input: PhantomData,
|
||||
}
|
||||
/// Extension trait for [`Handler`]s that don't have state.
|
||||
///
|
||||
/// This provides convenience methods to convert the [`Handler`] into a [`Service`] or [`MakeService`].
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
pub trait HandlerWithoutStateExt<T, B>: Handler<T, (), B> {
|
||||
/// Convert the handler into a [`Service`] and no state.
|
||||
fn into_service(self) -> WithState<Self, T, (), B>;
|
||||
|
||||
/// Convert the handler into a [`MakeService`] and no state.
|
||||
///
|
||||
/// See [`WithState::into_make_service`] for more details.
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
fn into_make_service(self) -> IntoMakeService<IntoService<Self, T, (), B>>;
|
||||
|
||||
/// Convert the handler into a [`MakeService`] which stores information
|
||||
/// about the incoming connection and has no state.
|
||||
///
|
||||
/// See [`WithState::into_make_service_with_connect_info`] for more details.
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
fn into_make_service_with_connect_info<C>(
|
||||
self,
|
||||
) -> IntoMakeServiceWithConnectInfo<IntoService<Self, T, (), B>, C>;
|
||||
}
|
||||
|
||||
impl<H, T, B> HandlerWithoutStateExt<T, B> for H
|
||||
where
|
||||
H: Handler<T, (), B>,
|
||||
{
|
||||
fn into_service(self) -> WithState<Self, T, (), B> {
|
||||
self.with_state(())
|
||||
}
|
||||
|
||||
fn into_make_service(self) -> IntoMakeService<IntoService<Self, T, (), B>> {
|
||||
self.with_state(()).into_make_service()
|
||||
}
|
||||
|
||||
fn into_make_service_with_connect_info<C>(
|
||||
self,
|
||||
) -> IntoMakeServiceWithConnectInfo<IntoService<Self, T, (), B>, C> {
|
||||
self.with_state(()).into_make_service_with_connect_info()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
144
axum/src/handler/with_state.rs
Normal file
144
axum/src/handler/with_state.rs
Normal file
|
@ -0,0 +1,144 @@
|
|||
use super::{Handler, IntoService};
|
||||
use crate::{extract::connect_info::IntoMakeServiceWithConnectInfo, routing::IntoMakeService};
|
||||
use http::Request;
|
||||
use std::task::{Context, Poll};
|
||||
use tower_service::Service;
|
||||
|
||||
/// A [`Handler`] which has access to some state.
|
||||
///
|
||||
/// Implements [`Service`].
|
||||
///
|
||||
/// The state can be extracted with [`State`](crate::extract::State).
|
||||
///
|
||||
/// Created with [`Handler::with_state`].
|
||||
pub struct WithState<H, T, S, B> {
|
||||
pub(super) service: IntoService<H, T, S, B>,
|
||||
}
|
||||
|
||||
impl<H, T, S, B> WithState<H, T, S, B> {
|
||||
/// Get a reference to the state.
|
||||
pub fn state(&self) -> &S {
|
||||
self.service.state()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, S, B> WithState<H, T, S, B> {
|
||||
/// Convert the handler into a [`MakeService`].
|
||||
///
|
||||
/// This allows you to serve a single handler if you don't need any routing:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Server,
|
||||
/// handler::Handler,
|
||||
/// extract::State,
|
||||
/// http::{Uri, Method},
|
||||
/// response::IntoResponse,
|
||||
/// };
|
||||
/// use std::net::SocketAddr;
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// struct AppState {}
|
||||
///
|
||||
/// async fn handler(State(state): State<AppState>) {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// let app = handler.with_state(AppState {});
|
||||
///
|
||||
/// # async {
|
||||
/// Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000)))
|
||||
/// .serve(app.into_make_service())
|
||||
/// .await?;
|
||||
/// # Ok::<_, hyper::Error>(())
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
pub fn into_make_service(self) -> IntoMakeService<IntoService<H, T, S, B>> {
|
||||
IntoMakeService::new(self.service)
|
||||
}
|
||||
|
||||
/// Convert the handler into a [`MakeService`] which stores information
|
||||
/// about the incoming connection.
|
||||
///
|
||||
/// See [`Router::into_make_service_with_connect_info`] for more details.
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Server,
|
||||
/// handler::Handler,
|
||||
/// response::IntoResponse,
|
||||
/// extract::{ConnectInfo, State},
|
||||
/// };
|
||||
/// use std::net::SocketAddr;
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// struct AppState {};
|
||||
///
|
||||
/// async fn handler(
|
||||
/// ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
/// State(state): State<AppState>,
|
||||
/// ) -> String {
|
||||
/// format!("Hello {}", addr)
|
||||
/// }
|
||||
///
|
||||
/// let app = handler.with_state(AppState {});
|
||||
///
|
||||
/// # async {
|
||||
/// Server::bind(&SocketAddr::from(([127, 0, 0, 1], 3000)))
|
||||
/// .serve(app.into_make_service_with_connect_info::<SocketAddr>())
|
||||
/// .await?;
|
||||
/// # Ok::<_, hyper::Error>(())
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
|
||||
pub fn into_make_service_with_connect_info<C>(
|
||||
self,
|
||||
) -> IntoMakeServiceWithConnectInfo<IntoService<H, T, S, B>, C> {
|
||||
IntoMakeServiceWithConnectInfo::new(self.service)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, S, B> Service<Request<B>> for WithState<H, T, S, B>
|
||||
where
|
||||
H: Handler<T, S, B> + Clone + Send + 'static,
|
||||
B: Send + 'static,
|
||||
S: Clone,
|
||||
{
|
||||
type Response = <IntoService<H, T, S, B> as Service<Request<B>>>::Response;
|
||||
type Error = <IntoService<H, T, S, B> as Service<Request<B>>>::Error;
|
||||
type Future = <IntoService<H, T, S, B> as Service<Request<B>>>::Future;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(cx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||
self.service.call(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, S, B> std::fmt::Debug for WithState<H, T, S, B> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("WithState")
|
||||
.field("service", &self.service)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, S, B> Clone for WithState<H, T, S, B>
|
||||
where
|
||||
H: Clone,
|
||||
S: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
service: self.service.clone(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -94,16 +94,17 @@ use std::ops::{Deref, DerefMut};
|
|||
pub struct Json<T>(pub T);
|
||||
|
||||
#[async_trait]
|
||||
impl<T, B> FromRequest<B> for Json<T>
|
||||
impl<T, S, B> FromRequest<S, B> for Json<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
B: HttpBody + Send,
|
||||
B::Data: Send,
|
||||
B::Error: Into<BoxError>,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = JsonRejection;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
if json_content_type(req) {
|
||||
let bytes = Bytes::from_request(req).await?;
|
||||
|
||||
|
@ -136,7 +137,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn json_content_type<B>(req: &RequestParts<B>) -> bool {
|
||||
fn json_content_type<S, B>(req: &RequestParts<S, B>) -> bool {
|
||||
let content_type = if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) {
|
||||
content_type
|
||||
} else {
|
||||
|
|
|
@ -168,13 +168,48 @@
|
|||
//! pool of database connections or clients to other services.
|
||||
//!
|
||||
//! The two most common ways of doing that are:
|
||||
//! - Using the [`State`] extractor.
|
||||
//! - Using request extensions
|
||||
//! - Using closure captures
|
||||
//!
|
||||
//! ## Using the [`State`] extractor
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use axum::{
|
||||
//! extract::State,
|
||||
//! routing::get,
|
||||
//! Router,
|
||||
//! };
|
||||
//! use std::sync::Arc;
|
||||
//!
|
||||
//! struct AppState {
|
||||
//! // ...
|
||||
//! }
|
||||
//!
|
||||
//! let shared_state = Arc::new(AppState { /* ... */ });
|
||||
//!
|
||||
//! let app = Router::with_state(shared_state)
|
||||
//! .route("/", get(handler));
|
||||
//!
|
||||
//! async fn handler(
|
||||
//! State(state): State<Arc<AppState>>,
|
||||
//! ) {
|
||||
//! // ...
|
||||
//! }
|
||||
//! # async {
|
||||
//! # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
//! # };
|
||||
//! ```
|
||||
//!
|
||||
//! You should prefer using [`State`] if possible since it's more type safe. The downside is that
|
||||
//! its less dynamic than request extensions.
|
||||
//!
|
||||
//! See [`State`] for more details about accessing state.
|
||||
//!
|
||||
//! ## Using request extensions
|
||||
//!
|
||||
//! The easiest way to extract state in handlers is using [`Extension`](crate::extract::Extension)
|
||||
//! as layer and extractor:
|
||||
//! Another way to extract state in handlers is using [`Extension`](crate::extract::Extension) as
|
||||
//! layer and extractor:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use axum::{
|
||||
|
@ -184,18 +219,18 @@
|
|||
//! };
|
||||
//! use std::sync::Arc;
|
||||
//!
|
||||
//! struct State {
|
||||
//! struct AppState {
|
||||
//! // ...
|
||||
//! }
|
||||
//!
|
||||
//! let shared_state = Arc::new(State { /* ... */ });
|
||||
//! let shared_state = Arc::new(AppState { /* ... */ });
|
||||
//!
|
||||
//! let app = Router::new()
|
||||
//! .route("/", get(handler))
|
||||
//! .layer(Extension(shared_state));
|
||||
//!
|
||||
//! async fn handler(
|
||||
//! Extension(state): Extension<Arc<State>>,
|
||||
//! Extension(state): Extension<Arc<AppState>>,
|
||||
//! ) {
|
||||
//! // ...
|
||||
//! }
|
||||
|
@ -223,11 +258,11 @@
|
|||
//! use std::sync::Arc;
|
||||
//! use serde::Deserialize;
|
||||
//!
|
||||
//! struct State {
|
||||
//! struct AppState {
|
||||
//! // ...
|
||||
//! }
|
||||
//!
|
||||
//! let shared_state = Arc::new(State { /* ... */ });
|
||||
//! let shared_state = Arc::new(AppState { /* ... */ });
|
||||
//!
|
||||
//! let app = Router::new()
|
||||
//! .route(
|
||||
|
@ -245,11 +280,11 @@
|
|||
//! }),
|
||||
//! );
|
||||
//!
|
||||
//! async fn get_user(Path(user_id): Path<String>, state: Arc<State>) {
|
||||
//! async fn get_user(Path(user_id): Path<String>, state: Arc<AppState>) {
|
||||
//! // ...
|
||||
//! }
|
||||
//!
|
||||
//! async fn create_user(Json(payload): Json<CreateUserPayload>, state: Arc<State>) {
|
||||
//! async fn create_user(Json(payload): Json<CreateUserPayload>, state: Arc<AppState>) {
|
||||
//! // ...
|
||||
//! }
|
||||
//!
|
||||
|
@ -263,7 +298,7 @@
|
|||
//! ```
|
||||
//!
|
||||
//! The downside to this approach is that it's a little more verbose than using
|
||||
//! extensions.
|
||||
//! [`State`] or extensions.
|
||||
//!
|
||||
//! # Building integrations for axum
|
||||
//!
|
||||
|
@ -350,6 +385,7 @@
|
|||
//! [`Infallible`]: std::convert::Infallible
|
||||
//! [load shed]: tower::load_shed
|
||||
//! [`axum-core`]: http://crates.io/crates/axum-core
|
||||
//! [`State`]: crate::extract::State
|
||||
|
||||
#![warn(
|
||||
clippy::all,
|
||||
|
|
|
@ -45,13 +45,14 @@ use tower_service::Service;
|
|||
/// struct RequireAuth;
|
||||
///
|
||||
/// #[async_trait]
|
||||
/// impl<B> FromRequest<B> for RequireAuth
|
||||
/// impl<S, B> FromRequest<S, B> for RequireAuth
|
||||
/// where
|
||||
/// B: Send,
|
||||
/// S: Send,
|
||||
/// {
|
||||
/// type Rejection = StatusCode;
|
||||
///
|
||||
/// async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
/// async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
/// let auth_header = req
|
||||
/// .headers()
|
||||
/// .get(header::AUTHORIZATION)
|
||||
|
@ -166,23 +167,23 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<S, E, ReqBody> Service<Request<ReqBody>> for FromExtractor<S, E>
|
||||
impl<S, E, B> Service<Request<B>> for FromExtractor<S, E>
|
||||
where
|
||||
E: FromRequest<ReqBody> + 'static,
|
||||
ReqBody: Default + Send + 'static,
|
||||
S: Service<Request<ReqBody>> + Clone,
|
||||
E: FromRequest<(), B> + 'static,
|
||||
B: Default + Send + 'static,
|
||||
S: Service<Request<B>> + Clone,
|
||||
S::Response: IntoResponse,
|
||||
{
|
||||
type Response = Response;
|
||||
type Error = S::Error;
|
||||
type Future = ResponseFuture<ReqBody, S, E>;
|
||||
type Future = ResponseFuture<B, S, E>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
|
||||
fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||
let extract_future = Box::pin(async move {
|
||||
let mut req = RequestParts::new(req);
|
||||
let extracted = E::from_request(&mut req).await;
|
||||
|
@ -201,35 +202,37 @@ where
|
|||
pin_project! {
|
||||
/// Response future for [`FromExtractor`].
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct ResponseFuture<ReqBody, S, E>
|
||||
pub struct ResponseFuture<B, S, E>
|
||||
where
|
||||
E: FromRequest<ReqBody>,
|
||||
S: Service<Request<ReqBody>>,
|
||||
E: FromRequest<(), B>,
|
||||
S: Service<Request<B>>,
|
||||
{
|
||||
#[pin]
|
||||
state: State<ReqBody, S, E>,
|
||||
state: State<B, S, E>,
|
||||
svc: Option<S>,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
enum State<ReqBody, S, E>
|
||||
enum State<B, S, E>
|
||||
where
|
||||
E: FromRequest<ReqBody>,
|
||||
S: Service<Request<ReqBody>>,
|
||||
E: FromRequest<(), B>,
|
||||
S: Service<Request<B>>,
|
||||
{
|
||||
Extracting { future: BoxFuture<'static, (RequestParts<ReqBody>, Result<E, E::Rejection>)> },
|
||||
Extracting {
|
||||
future: BoxFuture<'static, (RequestParts<(), B>, Result<E, E::Rejection>)>,
|
||||
},
|
||||
Call { #[pin] future: S::Future },
|
||||
}
|
||||
}
|
||||
|
||||
impl<ReqBody, S, E> Future for ResponseFuture<ReqBody, S, E>
|
||||
impl<B, S, E> Future for ResponseFuture<B, S, E>
|
||||
where
|
||||
E: FromRequest<ReqBody>,
|
||||
S: Service<Request<ReqBody>>,
|
||||
E: FromRequest<(), B>,
|
||||
S: Service<Request<B>>,
|
||||
S::Response: IntoResponse,
|
||||
ReqBody: Default,
|
||||
B: Default,
|
||||
{
|
||||
type Output = Result<Response, S::Error>;
|
||||
|
||||
|
@ -277,13 +280,14 @@ mod tests {
|
|||
struct RequireAuth;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<B> FromRequest<B> for RequireAuth
|
||||
impl<S, B> FromRequest<S, B> for RequireAuth
|
||||
where
|
||||
B: Send,
|
||||
S: Send,
|
||||
{
|
||||
type Rejection = StatusCode;
|
||||
|
||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||
async fn from_request(req: &mut RequestParts<S, B>) -> Result<Self, Self::Rejection> {
|
||||
if let Some(auth) = req
|
||||
.headers()
|
||||
.get(header::AUTHORIZATION)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue