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
+53 -29
View File
@@ -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)]