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 <[email protected]>
Co-authored-by: Jonas Platte <[email protected]>
This commit is contained in:
David Pedersen
2022-08-17 15:13:31 +00:00
committed by GitHub
co-authored by Dani Pardo Jonas Platte
parent 90dbd52ee4
commit 423308de3c
132 changed files with 2404 additions and 1126 deletions
+31 -21
View File
@@ -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,
{
+21 -19
View File
@@ -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,