mirror of
https://github.com/tokio-rs/axum.git
synced 2026-08-28 00:00:20 +02:00
Type safe state inheritance (#1532)
* Make state type safe * fix examples * remove unnecessary `#[track_caller]`s * Router::into_service -> Router::with_state * fixup docs * macro docs * add missing docs * fix examples * format * changelog * Update trybuild tests * Make sure fallbacks are still inherited for opaque services (#1540) * Document nesting routers with different state * fix leftover conflicts
This commit is contained in:
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
# Unreleased
|
||||
|
||||
- **breaking:** `Router::with_state` is no longer a constructor. It is instead
|
||||
used to convert the router into a `RouterService` ([#1532])
|
||||
|
||||
This nested router on 0.6.0-rc.4
|
||||
|
||||
```rust
|
||||
Router::with_state(state).route(...);
|
||||
```
|
||||
|
||||
Becomes this in 0.6.0-rc.5
|
||||
|
||||
```rust
|
||||
Router::new().route(...).with_state(state);
|
||||
```
|
||||
|
||||
- **breaking:**: `Router::nest` and `Router::merge` now only supports nesting
|
||||
routers that use the same state type as the router they're being merged into.
|
||||
Use `FromRef` for substates ([#1532])
|
||||
|
||||
- **added:** Add `accept_unmasked_frames` setting in WebSocketUpgrade ([#1529])
|
||||
- **fixed:** Nested routers will now inherit fallbacks from outer routers ([#1521])
|
||||
- **added:** Add `accept_unmasked_frames` setting in WebSocketUpgrade ([#1529])
|
||||
- **added:** Add `WebSocketUpgrade::on_failed_upgrade` to customize what to do
|
||||
@@ -15,6 +35,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
[#1539]: https://github.com/tokio-rs/axum/pull/1539
|
||||
|
||||
[#1521]: https://github.com/tokio-rs/axum/pull/1521
|
||||
[#1529]: https://github.com/tokio-rs/axum/pull/1529
|
||||
[#1532]: https://github.com/tokio-rs/axum/pull/1532
|
||||
|
||||
# 0.6.0-rc.4 (9. November, 2022)
|
||||
|
||||
|
||||
+33
-20
@@ -1,7 +1,7 @@
|
||||
use axum::{
|
||||
extract::State,
|
||||
routing::{get, post},
|
||||
Extension, Json, Router, Server,
|
||||
Extension, Json, Router, RouterService, Server,
|
||||
};
|
||||
use hyper::server::conn::AddrIncoming;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -17,9 +17,13 @@ fn main() {
|
||||
ensure_rewrk_is_installed();
|
||||
}
|
||||
|
||||
benchmark("minimal").run(Router::new);
|
||||
benchmark("minimal").run(|| Router::new().into_service());
|
||||
|
||||
benchmark("basic").run(|| Router::new().route("/", get(|| async { "Hello, World!" })));
|
||||
benchmark("basic").run(|| {
|
||||
Router::new()
|
||||
.route("/", get(|| async { "Hello, World!" }))
|
||||
.into_service()
|
||||
});
|
||||
|
||||
benchmark("routing").path("/foo/bar/baz").run(|| {
|
||||
let mut app = Router::new();
|
||||
@@ -30,26 +34,32 @@ fn main() {
|
||||
}
|
||||
}
|
||||
}
|
||||
app.route("/foo/bar/baz", get(|| async {}))
|
||||
app.route("/foo/bar/baz", get(|| async {})).into_service()
|
||||
});
|
||||
|
||||
benchmark("receive-json")
|
||||
.method("post")
|
||||
.headers(&[("content-type", "application/json")])
|
||||
.body(r#"{"n": 123, "s": "hi there", "b": false}"#)
|
||||
.run(|| Router::new().route("/", post(|_: Json<Payload>| async {})));
|
||||
.run(|| {
|
||||
Router::new()
|
||||
.route("/", post(|_: Json<Payload>| async {}))
|
||||
.into_service()
|
||||
});
|
||||
|
||||
benchmark("send-json").run(|| {
|
||||
Router::new().route(
|
||||
"/",
|
||||
get(|| async {
|
||||
Json(Payload {
|
||||
n: 123,
|
||||
s: "hi there".to_owned(),
|
||||
b: false,
|
||||
})
|
||||
}),
|
||||
)
|
||||
Router::new()
|
||||
.route(
|
||||
"/",
|
||||
get(|| async {
|
||||
Json(Payload {
|
||||
n: 123,
|
||||
s: "hi there".to_owned(),
|
||||
b: false,
|
||||
})
|
||||
}),
|
||||
)
|
||||
.into_service()
|
||||
});
|
||||
|
||||
let state = AppState {
|
||||
@@ -65,10 +75,14 @@ fn main() {
|
||||
Router::new()
|
||||
.route("/", get(|_: Extension<AppState>| async {}))
|
||||
.layer(Extension(state.clone()))
|
||||
.into_service()
|
||||
});
|
||||
|
||||
benchmark("state")
|
||||
.run(|| Router::with_state(state.clone()).route("/", get(|_: State<AppState>| async {})));
|
||||
benchmark("state").run(|| {
|
||||
Router::new()
|
||||
.route("/", get(|_: State<AppState>| async {}))
|
||||
.with_state(state.clone())
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -117,10 +131,9 @@ impl BenchmarkBuilder {
|
||||
config_method!(headers, &'static [(&'static str, &'static str)]);
|
||||
config_method!(body, &'static str);
|
||||
|
||||
fn run<F, S>(self, f: F)
|
||||
fn run<F>(self, f: F)
|
||||
where
|
||||
F: FnOnce() -> Router<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
F: FnOnce() -> RouterService,
|
||||
{
|
||||
// support only running some benchmarks with
|
||||
// ```
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
use std::{convert::Infallible, fmt};
|
||||
|
||||
use crate::{body::HttpBody, handler::Handler, routing::Route, Router};
|
||||
|
||||
pub(crate) struct BoxedIntoRoute<S, B, E>(Box<dyn ErasedIntoRoute<S, B, E>>);
|
||||
|
||||
impl<S, B> BoxedIntoRoute<S, B, Infallible>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
B: Send + 'static,
|
||||
{
|
||||
pub(crate) fn from_handler<H, T>(handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
Self(Box::new(MakeErasedHandler {
|
||||
handler,
|
||||
into_route: |handler, state| Route::new(Handler::with_state(handler, state)),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn from_router(router: Router<S, B>) -> Self
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
Self(Box::new(MakeErasedRouter {
|
||||
router,
|
||||
into_route: |router, state| Route::new(router.with_state(state)),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B, E> BoxedIntoRoute<S, B, E> {
|
||||
pub(crate) fn map<F, B2, E2>(self, f: F) -> BoxedIntoRoute<S, B2, E2>
|
||||
where
|
||||
S: 'static,
|
||||
B: 'static,
|
||||
E: 'static,
|
||||
F: FnOnce(Route<B, E>) -> Route<B2, E2> + Clone + Send + 'static,
|
||||
B2: 'static,
|
||||
E2: 'static,
|
||||
{
|
||||
BoxedIntoRoute(Box::new(Map {
|
||||
inner: self.0,
|
||||
layer: Box::new(f),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn into_route(self, state: S) -> Route<B, E> {
|
||||
self.0.into_route(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B, E> Clone for BoxedIntoRoute<S, B, E> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone_box())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B, E> fmt::Debug for BoxedIntoRoute<S, B, E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("BoxedIntoRoute").finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait ErasedIntoRoute<S, B, E>: Send {
|
||||
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, B, E>>;
|
||||
|
||||
fn into_route(self: Box<Self>, state: S) -> Route<B, E>;
|
||||
}
|
||||
|
||||
pub(crate) struct MakeErasedHandler<H, S, B> {
|
||||
pub(crate) handler: H,
|
||||
pub(crate) into_route: fn(H, S) -> Route<B>,
|
||||
}
|
||||
|
||||
impl<H, S, B> ErasedIntoRoute<S, B, Infallible> for MakeErasedHandler<H, S, B>
|
||||
where
|
||||
H: Clone + Send + 'static,
|
||||
S: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, B, Infallible>> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn into_route(self: Box<Self>, state: S) -> Route<B> {
|
||||
(self.into_route)(self.handler, state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, S, B> Clone for MakeErasedHandler<H, S, B>
|
||||
where
|
||||
H: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
handler: self.handler.clone(),
|
||||
into_route: self.into_route,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MakeErasedRouter<S, B> {
|
||||
pub(crate) router: Router<S, B>,
|
||||
pub(crate) into_route: fn(Router<S, B>, S) -> Route<B>,
|
||||
}
|
||||
|
||||
impl<S, B> ErasedIntoRoute<S, B, Infallible> for MakeErasedRouter<S, B>
|
||||
where
|
||||
S: Clone + Send + 'static,
|
||||
B: 'static,
|
||||
{
|
||||
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, B, Infallible>> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn into_route(self: Box<Self>, state: S) -> Route<B> {
|
||||
(self.into_route)(self.router, state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> Clone for MakeErasedRouter<S, B>
|
||||
where
|
||||
S: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
router: self.router.clone(),
|
||||
into_route: self.into_route,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Map<S, B, E, B2, E2> {
|
||||
pub(crate) inner: Box<dyn ErasedIntoRoute<S, B, E>>,
|
||||
pub(crate) layer: Box<dyn LayerFn<B, E, B2, E2>>,
|
||||
}
|
||||
|
||||
impl<S, B, E, B2, E2> ErasedIntoRoute<S, B2, E2> for Map<S, B, E, B2, E2>
|
||||
where
|
||||
S: 'static,
|
||||
B: 'static,
|
||||
E: 'static,
|
||||
B2: 'static,
|
||||
E2: 'static,
|
||||
{
|
||||
fn clone_box(&self) -> Box<dyn ErasedIntoRoute<S, B2, E2>> {
|
||||
Box::new(Self {
|
||||
inner: self.inner.clone_box(),
|
||||
layer: self.layer.clone_box(),
|
||||
})
|
||||
}
|
||||
|
||||
fn into_route(self: Box<Self>, state: S) -> Route<B2, E2> {
|
||||
(self.layer)(self.inner.into_route(state))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait LayerFn<B, E, B2, E2>: FnOnce(Route<B, E>) -> Route<B2, E2> + Send {
|
||||
fn clone_box(&self) -> Box<dyn LayerFn<B, E, B2, E2>>;
|
||||
}
|
||||
|
||||
impl<F, B, E, B2, E2> LayerFn<B, E, B2, E2> for F
|
||||
where
|
||||
F: FnOnce(Route<B, E>) -> Route<B2, E2> + Clone + Send + 'static,
|
||||
{
|
||||
fn clone_box(&self) -> Box<dyn LayerFn<B, E, B2, E2>> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
@@ -462,10 +462,11 @@ async fn handler(_: State<AppState>) {}
|
||||
|
||||
let state = AppState {};
|
||||
|
||||
let app = Router::with_state(state.clone())
|
||||
let app = Router::new()
|
||||
.route("/", get(handler))
|
||||
.layer(MyLayer { state });
|
||||
# let _: Router<_> = app;
|
||||
.layer(MyLayer { state: state.clone() })
|
||||
.with_state(state);
|
||||
# let _: axum::routing::RouterService = app;
|
||||
```
|
||||
|
||||
# Passing state from middleware to handlers
|
||||
|
||||
@@ -2,6 +2,10 @@ Convert this router into a [`MakeService`], that will store `C`'s
|
||||
associated `ConnectInfo` in a request extension such that [`ConnectInfo`]
|
||||
can extract it.
|
||||
|
||||
This is a convenience method for routers that don't have any state (i.e. the
|
||||
state type is `()`). Use [`RouterService::into_make_service_with_connect_info`]
|
||||
otherwise.
|
||||
|
||||
This enables extracting things like the client's remote address.
|
||||
|
||||
Extracting [`std::net::SocketAddr`] is supported out of the box:
|
||||
|
||||
@@ -147,6 +147,43 @@ let app = Router::new()
|
||||
|
||||
Here requests like `GET /api/not-found` will go to `api_fallback`.
|
||||
|
||||
# Nesting a router with a different state type
|
||||
|
||||
By default `nest` requires a `Router` with the same state type as the outer
|
||||
`Router`. If you need to nest a `Router` with a different state type you can
|
||||
use [`Router::with_state`] and [`Router::nest_service`]:
|
||||
|
||||
```rust
|
||||
use axum::{
|
||||
Router,
|
||||
routing::get,
|
||||
extract::State,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InnerState {}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct OuterState {}
|
||||
|
||||
async fn inner_handler(state: State<InnerState>) {}
|
||||
|
||||
let inner_router = Router::new()
|
||||
.route("/bar", get(inner_handler))
|
||||
.with_state(InnerState {});
|
||||
|
||||
async fn outer_handler(state: State<OuterState>) {}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(outer_handler))
|
||||
.nest_service("/foo", inner_router)
|
||||
.with_state(OuterState {});
|
||||
# let _: axum::routing::RouterService = app;
|
||||
```
|
||||
|
||||
Note that the inner router will still inherit the fallback from the outer
|
||||
router.
|
||||
|
||||
# Panics
|
||||
|
||||
- If the route overlaps with another route. See [`Router::route`]
|
||||
|
||||
@@ -31,7 +31,10 @@ use std::{
|
||||
/// let state = AppState {};
|
||||
///
|
||||
/// // create a `Router` that holds our state
|
||||
/// let app = Router::with_state(state).route("/", get(handler));
|
||||
/// let app = Router::new()
|
||||
/// .route("/", get(handler))
|
||||
/// // provide the state so the router can access it
|
||||
/// .with_state(state);
|
||||
///
|
||||
/// async fn handler(
|
||||
/// // access the state via the `State` extractor
|
||||
@@ -40,7 +43,7 @@ use std::{
|
||||
/// ) {
|
||||
/// // use `state`...
|
||||
/// }
|
||||
/// # let _: Router<AppState> = app;
|
||||
/// # let _: axum::routing::RouterService = app;
|
||||
/// ```
|
||||
///
|
||||
/// # With `MethodRouter`
|
||||
@@ -119,9 +122,10 @@ use std::{
|
||||
/// api_state: ApiState {},
|
||||
/// };
|
||||
///
|
||||
/// let app = Router::with_state(state)
|
||||
/// let app = Router::new()
|
||||
/// .route("/", get(handler))
|
||||
/// .route("/api/users", get(api_users));
|
||||
/// .route("/api/users", get(api_users))
|
||||
/// .with_state(state);
|
||||
///
|
||||
/// async fn api_users(
|
||||
/// // access the api specific state
|
||||
@@ -134,9 +138,11 @@ use std::{
|
||||
/// State(state): State<AppState>,
|
||||
/// ) {
|
||||
/// }
|
||||
/// # let _: Router<AppState> = app;
|
||||
/// # let _: axum::routing::RouterService = app;
|
||||
/// ```
|
||||
///
|
||||
/// For convenience `FromRef` can also be derived using `#[derive(FromRef)]`.
|
||||
///
|
||||
/// # For library authors
|
||||
///
|
||||
/// If you're writing a library that has an extractor that needs state, this is the recommended way
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
use std::convert::Infallible;
|
||||
|
||||
use super::Handler;
|
||||
use crate::routing::Route;
|
||||
|
||||
pub(crate) struct BoxedHandler<S, B, E = Infallible>(Box<dyn ErasedHandler<S, B, E>>);
|
||||
|
||||
impl<S, B> BoxedHandler<S, B>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
B: Send + 'static,
|
||||
{
|
||||
pub(crate) fn new<H, T>(handler: H) -> Self
|
||||
where
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
Self(Box::new(MakeErasedHandler {
|
||||
handler,
|
||||
into_route: |handler, state| Route::new(Handler::with_state(handler, state)),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B, E> BoxedHandler<S, B, E> {
|
||||
pub(crate) fn map<F, B2, E2>(self, f: F) -> BoxedHandler<S, B2, E2>
|
||||
where
|
||||
S: 'static,
|
||||
B: 'static,
|
||||
E: 'static,
|
||||
F: FnOnce(Route<B, E>) -> Route<B2, E2> + Clone + Send + 'static,
|
||||
B2: 'static,
|
||||
E2: 'static,
|
||||
{
|
||||
BoxedHandler(Box::new(Map {
|
||||
handler: self.0,
|
||||
layer: Box::new(f),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn into_route(self, state: S) -> Route<B, E> {
|
||||
self.0.into_route(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B, E> Clone for BoxedHandler<S, B, E> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone_box())
|
||||
}
|
||||
}
|
||||
|
||||
trait ErasedHandler<S, B, E = Infallible>: Send {
|
||||
fn clone_box(&self) -> Box<dyn ErasedHandler<S, B, E>>;
|
||||
|
||||
fn into_route(self: Box<Self>, state: S) -> Route<B, E>;
|
||||
}
|
||||
|
||||
struct MakeErasedHandler<H, S, B> {
|
||||
handler: H,
|
||||
into_route: fn(H, S) -> Route<B>,
|
||||
}
|
||||
|
||||
impl<H, S, B> ErasedHandler<S, B> for MakeErasedHandler<H, S, B>
|
||||
where
|
||||
H: Clone + Send + 'static,
|
||||
S: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
fn clone_box(&self) -> Box<dyn ErasedHandler<S, B>> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn into_route(self: Box<Self>, state: S) -> Route<B> {
|
||||
(self.into_route)(self.handler, state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Clone, S, B> Clone for MakeErasedHandler<H, S, B> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
handler: self.handler.clone(),
|
||||
into_route: self.into_route,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Map<S, B, E, B2, E2> {
|
||||
handler: Box<dyn ErasedHandler<S, B, E>>,
|
||||
layer: Box<dyn LayerFn<B, E, B2, E2>>,
|
||||
}
|
||||
|
||||
impl<S, B, E, B2, E2> ErasedHandler<S, B2, E2> for Map<S, B, E, B2, E2>
|
||||
where
|
||||
S: 'static,
|
||||
B: 'static,
|
||||
E: 'static,
|
||||
B2: 'static,
|
||||
E2: 'static,
|
||||
{
|
||||
fn clone_box(&self) -> Box<dyn ErasedHandler<S, B2, E2>> {
|
||||
Box::new(Self {
|
||||
handler: self.handler.clone_box(),
|
||||
layer: self.layer.clone_box(),
|
||||
})
|
||||
}
|
||||
|
||||
fn into_route(self: Box<Self>, state: S) -> Route<B2, E2> {
|
||||
(self.layer)(self.handler.into_route(state))
|
||||
}
|
||||
}
|
||||
|
||||
trait LayerFn<B, E, B2, E2>: FnOnce(Route<B, E>) -> Route<B2, E2> + Send {
|
||||
fn clone_box(&self) -> Box<dyn LayerFn<B, E, B2, E2>>;
|
||||
}
|
||||
|
||||
impl<F, B, E, B2, E2> LayerFn<B, E, B2, E2> for F
|
||||
where
|
||||
F: FnOnce(Route<B, E>) -> Route<B2, E2> + Clone + Send + 'static,
|
||||
{
|
||||
fn clone_box(&self) -> Box<dyn LayerFn<B, E, B2, E2>> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
@@ -49,11 +49,9 @@ use tower::ServiceExt;
|
||||
use tower_layer::Layer;
|
||||
use tower_service::Service;
|
||||
|
||||
mod boxed;
|
||||
pub mod future;
|
||||
mod service;
|
||||
|
||||
pub(crate) use self::boxed::BoxedHandler;
|
||||
pub use self::service::HandlerService;
|
||||
|
||||
/// Trait for async functions that can be used to handle requests.
|
||||
|
||||
+4
-2
@@ -188,8 +188,9 @@
|
||||
//!
|
||||
//! let shared_state = Arc::new(AppState { /* ... */ });
|
||||
//!
|
||||
//! let app = Router::with_state(shared_state)
|
||||
//! .route("/", get(handler));
|
||||
//! let app = Router::new()
|
||||
//! .route("/", get(handler))
|
||||
//! .with_state(shared_state);
|
||||
//!
|
||||
//! async fn handler(
|
||||
//! State(state): State<Arc<AppState>>,
|
||||
@@ -434,6 +435,7 @@
|
||||
#[macro_use]
|
||||
pub(crate) mod macros;
|
||||
|
||||
mod boxed;
|
||||
mod extension;
|
||||
#[cfg(feature = "form")]
|
||||
mod form;
|
||||
|
||||
@@ -133,10 +133,11 @@ pub fn from_fn<F, T>(f: F) -> FromFnLayer<F, (), T> {
|
||||
///
|
||||
/// let state = AppState { /* ... */ };
|
||||
///
|
||||
/// let app = Router::with_state(state.clone())
|
||||
/// let app = Router::new()
|
||||
/// .route("/", get(|| async { /* ... */ }))
|
||||
/// .route_layer(middleware::from_fn_with_state(state, my_middleware));
|
||||
/// # let app: Router<_> = app;
|
||||
/// .route_layer(middleware::from_fn_with_state(state.clone(), my_middleware))
|
||||
/// .with_state(state);
|
||||
/// # let _: axum::routing::RouterService = app;
|
||||
/// ```
|
||||
pub fn from_fn_with_state<F, S, T>(state: S, f: F) -> FromFnLayer<F, S, T> {
|
||||
FromFnLayer {
|
||||
|
||||
@@ -148,10 +148,11 @@ pub fn map_request<F, T>(f: F) -> MapRequestLayer<F, (), T> {
|
||||
///
|
||||
/// let state = AppState { /* ... */ };
|
||||
///
|
||||
/// let app = Router::with_state(state.clone())
|
||||
/// let app = Router::new()
|
||||
/// .route("/", get(|| async { /* ... */ }))
|
||||
/// .route_layer(map_request_with_state(state, my_middleware));
|
||||
/// # let app: Router<_> = app;
|
||||
/// .route_layer(map_request_with_state(state.clone(), my_middleware))
|
||||
/// .with_state(state);
|
||||
/// # let _: axum::routing::RouterService = app;
|
||||
/// ```
|
||||
pub fn map_request_with_state<F, S, T>(state: S, f: F) -> MapRequestLayer<F, S, T> {
|
||||
MapRequestLayer {
|
||||
|
||||
@@ -132,10 +132,11 @@ pub fn map_response<F, T>(f: F) -> MapResponseLayer<F, (), T> {
|
||||
///
|
||||
/// let state = AppState { /* ... */ };
|
||||
///
|
||||
/// let app = Router::with_state(state.clone())
|
||||
/// let app = Router::new()
|
||||
/// .route("/", get(|| async { /* ... */ }))
|
||||
/// .route_layer(map_response_with_state(state, my_middleware));
|
||||
/// # let app: Router<_> = app;
|
||||
/// .route_layer(map_response_with_state(state.clone(), my_middleware))
|
||||
/// .with_state(state);
|
||||
/// # let _: axum::routing::RouterService = app;
|
||||
/// ```
|
||||
pub fn map_response_with_state<F, S, T>(state: S, f: F) -> MapResponseLayer<F, S, T> {
|
||||
MapResponseLayer {
|
||||
|
||||
@@ -98,7 +98,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
Router::<_, Body>::new()
|
||||
Router::<(), Body>::new()
|
||||
.route("/", get(impl_trait_ok))
|
||||
.route("/", get(impl_trait_err))
|
||||
.route("/", get(impl_trait_both))
|
||||
@@ -208,7 +208,7 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
Router::<_, Body>::new()
|
||||
Router::<(), Body>::new()
|
||||
.route("/", get(status))
|
||||
.route("/", get(status_headermap))
|
||||
.route("/", get(status_header_array))
|
||||
|
||||
@@ -5,12 +5,12 @@ use super::{FallbackRoute, IntoMakeService};
|
||||
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
|
||||
use crate::{
|
||||
body::{Body, Bytes, HttpBody},
|
||||
boxed::BoxedIntoRoute,
|
||||
error_handling::{HandleError, HandleErrorLayer},
|
||||
handler::{BoxedHandler, Handler},
|
||||
handler::Handler,
|
||||
http::{Method, Request, StatusCode},
|
||||
response::Response,
|
||||
routing::{future::RouteFuture, Fallback, MethodFilter, Route},
|
||||
util::try_downcast,
|
||||
};
|
||||
use axum_core::response::IntoResponse;
|
||||
use bytes::BytesMut;
|
||||
@@ -606,7 +606,7 @@ where
|
||||
{
|
||||
self.on_endpoint(
|
||||
filter,
|
||||
MethodEndpoint::BoxedHandler(BoxedHandler::new(handler)),
|
||||
MethodEndpoint::BoxedHandler(BoxedIntoRoute::from_handler(handler)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -626,7 +626,7 @@ where
|
||||
T: 'static,
|
||||
S: Send + Sync + 'static,
|
||||
{
|
||||
self.fallback = Fallback::BoxedHandler(BoxedHandler::new(handler));
|
||||
self.fallback = Fallback::BoxedHandler(BoxedIntoRoute::from_handler(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -749,46 +749,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_state<S2>(self, state: &S) -> MethodRouter<S2, B, E>
|
||||
where
|
||||
E: 'static,
|
||||
S: 'static,
|
||||
S2: 'static,
|
||||
{
|
||||
MethodRouter {
|
||||
get: self.get.map_state(state),
|
||||
head: self.head.map_state(state),
|
||||
delete: self.delete.map_state(state),
|
||||
options: self.options.map_state(state),
|
||||
patch: self.patch.map_state(state),
|
||||
post: self.post.map_state(state),
|
||||
put: self.put.map_state(state),
|
||||
trace: self.trace.map_state(state),
|
||||
fallback: self.fallback.map_state(state),
|
||||
allow_header: self.allow_header,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn downcast_state<S2>(self) -> Option<MethodRouter<S2, B, E>>
|
||||
where
|
||||
E: 'static,
|
||||
S: 'static,
|
||||
S2: 'static,
|
||||
{
|
||||
Some(MethodRouter {
|
||||
get: self.get.downcast_state()?,
|
||||
head: self.head.downcast_state()?,
|
||||
delete: self.delete.downcast_state()?,
|
||||
options: self.options.downcast_state()?,
|
||||
patch: self.patch.downcast_state()?,
|
||||
post: self.post.downcast_state()?,
|
||||
put: self.put.downcast_state()?,
|
||||
trace: self.trace.downcast_state()?,
|
||||
fallback: self.fallback.downcast_state()?,
|
||||
allow_header: self.allow_header,
|
||||
})
|
||||
}
|
||||
|
||||
/// Chain an additional service that will accept requests matching the given
|
||||
/// `MethodFilter`.
|
||||
///
|
||||
@@ -964,17 +924,14 @@ where
|
||||
) -> MethodRouter<S, NewReqBody, NewError>
|
||||
where
|
||||
L: Layer<Route<B, E>> + Clone + Send + 'static,
|
||||
L::Service: Service<Request<NewReqBody>, Error = NewError> + Clone + Send + 'static,
|
||||
L::Service: Service<Request<NewReqBody>> + Clone + Send + 'static,
|
||||
<L::Service as Service<Request<NewReqBody>>>::Response: IntoResponse + 'static,
|
||||
<L::Service as Service<Request<NewReqBody>>>::Error: Into<NewError> + 'static,
|
||||
<L::Service as Service<Request<NewReqBody>>>::Future: Send + 'static,
|
||||
E: 'static,
|
||||
S: 'static,
|
||||
{
|
||||
let layer_fn = move |svc| {
|
||||
let svc = layer.layer(svc);
|
||||
let svc = MapResponseLayer::new(IntoResponse::into_response).layer(svc);
|
||||
Route::new(svc)
|
||||
};
|
||||
let layer_fn = move |route: Route<B, E>| route.layer(layer.clone());
|
||||
|
||||
MethodRouter {
|
||||
get: self.get.map(layer_fn.clone()),
|
||||
@@ -1182,7 +1139,7 @@ where
|
||||
enum MethodEndpoint<S, B, E> {
|
||||
None,
|
||||
Route(Route<B, E>),
|
||||
BoxedHandler(BoxedHandler<S, B, E>),
|
||||
BoxedHandler(BoxedIntoRoute<S, B, E>),
|
||||
}
|
||||
|
||||
impl<S, B, E> MethodEndpoint<S, B, E>
|
||||
@@ -1213,32 +1170,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn map_state<S2>(self, state: &S) -> MethodEndpoint<S2, B, E> {
|
||||
match self {
|
||||
Self::None => MethodEndpoint::None,
|
||||
Self::Route(route) => MethodEndpoint::Route(route),
|
||||
Self::BoxedHandler(handler) => MethodEndpoint::Route(handler.into_route(state.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
fn downcast_state<S2>(self) -> Option<MethodEndpoint<S2, B, E>>
|
||||
where
|
||||
S: 'static,
|
||||
B: 'static,
|
||||
E: 'static,
|
||||
S2: 'static,
|
||||
{
|
||||
match self {
|
||||
Self::None => Some(MethodEndpoint::None),
|
||||
Self::Route(route) => Some(MethodEndpoint::Route(route)),
|
||||
Self::BoxedHandler(handler) => {
|
||||
try_downcast::<BoxedHandler<S2, B, E>, BoxedHandler<S, B, E>>(handler)
|
||||
.map(MethodEndpoint::BoxedHandler)
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn into_route(self, state: &S) -> Option<Route<B, E>> {
|
||||
match self {
|
||||
Self::None => None,
|
||||
|
||||
+137
-219
@@ -1,27 +1,19 @@
|
||||
//! Routing between [`Service`]s and handlers.
|
||||
|
||||
use self::not_found::NotFound;
|
||||
use self::{not_found::NotFound, strip_prefix::StripPrefix};
|
||||
#[cfg(feature = "tokio")]
|
||||
use crate::extract::connect_info::IntoMakeServiceWithConnectInfo;
|
||||
use crate::{
|
||||
body::{Body, HttpBody},
|
||||
handler::{BoxedHandler, Handler},
|
||||
boxed::BoxedIntoRoute,
|
||||
handler::Handler,
|
||||
util::try_downcast,
|
||||
};
|
||||
use axum_core::response::{IntoResponse, Response};
|
||||
use http::Request;
|
||||
use matchit::MatchError;
|
||||
use std::{
|
||||
any::{type_name, TypeId},
|
||||
collections::HashMap,
|
||||
convert::Infallible,
|
||||
fmt,
|
||||
sync::Arc,
|
||||
};
|
||||
use tower::{
|
||||
util::{BoxCloneService, MapResponseLayer, Oneshot},
|
||||
ServiceBuilder,
|
||||
};
|
||||
use std::{collections::HashMap, convert::Infallible, fmt, sync::Arc};
|
||||
use tower::util::{BoxCloneService, Oneshot};
|
||||
use tower_layer::Layer;
|
||||
use tower_service::Service;
|
||||
|
||||
@@ -68,19 +60,14 @@ impl RouteId {
|
||||
|
||||
/// The router type for composing handlers and services.
|
||||
pub struct Router<S = (), B = Body> {
|
||||
state: Option<S>,
|
||||
routes: HashMap<RouteId, Endpoint<S, B>>,
|
||||
node: Arc<Node>,
|
||||
fallback: Fallback<S, B>,
|
||||
}
|
||||
|
||||
impl<S, B> Clone for Router<S, B>
|
||||
where
|
||||
S: Clone,
|
||||
{
|
||||
impl<S, B> Clone for Router<S, B> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
state: self.state.clone(),
|
||||
routes: self.routes.clone(),
|
||||
node: Arc::clone(&self.node),
|
||||
fallback: self.fallback.clone(),
|
||||
@@ -91,10 +78,10 @@ where
|
||||
impl<S, B> Default for Router<S, B>
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
S: Default + Clone + Send + Sync + 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::with_state(S::default())
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +91,6 @@ where
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Router")
|
||||
.field("state", &self.state)
|
||||
.field("routes", &self.routes)
|
||||
.field("node", &self.node)
|
||||
.field("fallback", &self.fallback)
|
||||
@@ -115,71 +101,19 @@ where
|
||||
pub(crate) const NEST_TAIL_PARAM: &str = "__private__axum_nest_tail_param";
|
||||
pub(crate) const NEST_TAIL_PARAM_CAPTURE: &str = "/*__private__axum_nest_tail_param";
|
||||
|
||||
impl<B> Router<(), B>
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
{
|
||||
/// Create a new `Router`.
|
||||
///
|
||||
/// Unless you add additional routes this will respond with `404 Not Found` to
|
||||
/// all requests.
|
||||
pub fn new() -> Self {
|
||||
Self::with_state(())
|
||||
}
|
||||
}
|
||||
impl<B> Router<(), B> where B: HttpBody + Send + 'static {}
|
||||
|
||||
impl<S, B> Router<S, B>
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
/// Create a new `Router` with the given state.
|
||||
///
|
||||
/// See [`State`](crate::extract::State) for more details about accessing state.
|
||||
/// Create a new `Router`.
|
||||
///
|
||||
/// Unless you add additional routes this will respond with `404 Not Found` to
|
||||
/// all requests.
|
||||
pub fn with_state(state: S) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: Some(state),
|
||||
routes: Default::default(),
|
||||
node: Default::default(),
|
||||
fallback: Fallback::Default(Route::new(NotFound)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `Router` that inherits its state from another `Router` that it is merged into
|
||||
/// or nested under.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{Router, routing::get, extract::State};
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// struct AppState {}
|
||||
///
|
||||
/// // A router that will be nested under the `app` router.
|
||||
/// //
|
||||
/// // By using `inherit_state` we'll reuse the state from the `app` router.
|
||||
/// let nested_router = Router::inherit_state()
|
||||
/// .route("/bar", get(|state: State<AppState>| async {}));
|
||||
///
|
||||
/// // A router that will be merged into the `app` router.
|
||||
/// let merged_router = Router::inherit_state()
|
||||
/// .route("/baz", get(|state: State<AppState>| async {}));
|
||||
///
|
||||
/// let app = Router::with_state(AppState {})
|
||||
/// .route("/", get(|state: State<AppState>| async {}))
|
||||
/// .nest("/foo", nested_router)
|
||||
/// .merge(merged_router);
|
||||
///
|
||||
/// // `app` now has routes for `/`, `/foo/bar`, and `/baz` that all use the same state.
|
||||
/// # let _: Router<AppState> = app;
|
||||
/// ```
|
||||
pub fn inherit_state() -> Self {
|
||||
Self {
|
||||
state: None,
|
||||
routes: Default::default(),
|
||||
node: Default::default(),
|
||||
fallback: Fallback::Default(Route::new(NotFound)),
|
||||
@@ -228,18 +162,12 @@ where
|
||||
}
|
||||
|
||||
#[doc = include_str!("../docs/routing/route_service.md")]
|
||||
pub fn route_service<T>(mut self, path: &str, service: T) -> Self
|
||||
pub fn route_service<T>(self, path: &str, service: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
|
||||
T::Response: IntoResponse,
|
||||
T::Future: Send + 'static,
|
||||
{
|
||||
if path.is_empty() {
|
||||
panic!("Paths must start with a `/`. Use \"/\" for root routes");
|
||||
} else if !path.starts_with('/') {
|
||||
panic!("Paths must start with a `/`");
|
||||
}
|
||||
|
||||
let service = match try_downcast::<RouterService<B>, _>(service) {
|
||||
Ok(_) => {
|
||||
panic!(
|
||||
@@ -250,11 +178,20 @@ where
|
||||
Err(svc) => svc,
|
||||
};
|
||||
|
||||
self.route_endpoint(path, Endpoint::Route(Route::new(service)))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn route_endpoint(mut self, path: &str, endpoint: Endpoint<S, B>) -> Self {
|
||||
if path.is_empty() {
|
||||
panic!("Paths must start with a `/`. Use \"/\" for root routes");
|
||||
} else if !path.starts_with('/') {
|
||||
panic!("Paths must start with a `/`");
|
||||
}
|
||||
|
||||
let id = RouteId::next();
|
||||
let endpoint = Endpoint::Route(Route::new(service));
|
||||
self.set_node(path, id);
|
||||
self.routes.insert(id, endpoint);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
@@ -270,29 +207,27 @@ where
|
||||
|
||||
#[doc = include_str!("../docs/routing/nest.md")]
|
||||
#[track_caller]
|
||||
pub fn nest<S2>(self, path: &str, mut router: Router<S2, B>) -> Self
|
||||
where
|
||||
S2: Clone + Send + Sync + 'static,
|
||||
{
|
||||
if router.state.is_none() {
|
||||
let s = self.state.clone();
|
||||
router.state = match try_downcast::<Option<S2>, Option<S>>(s) {
|
||||
Ok(state) => state,
|
||||
Err(_) => panic!(
|
||||
"can't nest a `Router` that wants to inherit state of type `{}` \
|
||||
into a `Router` with a state type of `{}`",
|
||||
type_name::<S2>(),
|
||||
type_name::<S>(),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
self.nest_service(path, router.into_service())
|
||||
pub fn nest(self, path: &str, router: Router<S, B>) -> Self {
|
||||
self.nest_endpoint(path, RouterOrService::<_, _, NotFound>::Router(router))
|
||||
}
|
||||
|
||||
/// Like [`nest`](Self::nest), but accepts an arbitrary `Service`.
|
||||
#[track_caller]
|
||||
pub fn nest_service<T>(mut self, mut path: &str, svc: T) -> Self
|
||||
pub fn nest_service<T>(self, path: &str, svc: T) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
|
||||
T::Response: IntoResponse,
|
||||
T::Future: Send + 'static,
|
||||
{
|
||||
self.nest_endpoint(path, RouterOrService::Service(svc))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn nest_endpoint<T>(
|
||||
mut self,
|
||||
mut path: &str,
|
||||
router_or_service: RouterOrService<S, B, T>,
|
||||
) -> Self
|
||||
where
|
||||
T: Service<Request<B>, Error = Infallible> + Clone + Send + 'static,
|
||||
T::Response: IntoResponse,
|
||||
@@ -315,16 +250,27 @@ where
|
||||
format!("{path}/*{NEST_TAIL_PARAM}")
|
||||
};
|
||||
|
||||
let svc = strip_prefix::StripPrefix::new(svc, prefix);
|
||||
self = self.route_service(&path, svc.clone());
|
||||
let endpoint = match router_or_service {
|
||||
RouterOrService::Router(router) => {
|
||||
let prefix = prefix.to_owned();
|
||||
let boxed = BoxedIntoRoute::from_router(router)
|
||||
.map(move |route| Route::new(StripPrefix::new(route, &prefix)));
|
||||
Endpoint::NestedRouter(boxed)
|
||||
}
|
||||
RouterOrService::Service(svc) => {
|
||||
Endpoint::Route(Route::new(StripPrefix::new(svc, prefix)))
|
||||
}
|
||||
};
|
||||
|
||||
self = self.route_endpoint(&path, endpoint.clone());
|
||||
|
||||
// `/*rest` is not matched by `/` so we need to also register a router at the
|
||||
// prefix itself. Otherwise if you were to nest at `/foo` then `/foo` itself
|
||||
// wouldn't match, which it should
|
||||
self = self.route_service(prefix, svc.clone());
|
||||
self = self.route_endpoint(prefix, endpoint.clone());
|
||||
if !prefix.ends_with('/') {
|
||||
// same goes for `/foo/`, that should also match
|
||||
self = self.route_service(&format!("{prefix}/"), svc);
|
||||
self = self.route_endpoint(&format!("{prefix}/"), endpoint);
|
||||
}
|
||||
|
||||
self
|
||||
@@ -332,66 +278,27 @@ where
|
||||
|
||||
#[doc = include_str!("../docs/routing/merge.md")]
|
||||
#[track_caller]
|
||||
pub fn merge<S2, R>(mut self, other: R) -> Self
|
||||
pub fn merge<R>(mut self, other: R) -> Self
|
||||
where
|
||||
R: Into<Router<S2, B>>,
|
||||
S2: Clone + Send + Sync + 'static,
|
||||
R: Into<Router<S, B>>,
|
||||
{
|
||||
let Router {
|
||||
state,
|
||||
routes,
|
||||
node,
|
||||
fallback,
|
||||
} = other.into();
|
||||
|
||||
let cast_method_router_closure_slot;
|
||||
let (fallback, cast_method_router) = match state {
|
||||
// other has its state set
|
||||
Some(state) => {
|
||||
let fallback = fallback.map_state(&state);
|
||||
cast_method_router_closure_slot = move |r: MethodRouter<_, _>| r.map_state(&state);
|
||||
let cast_method_router = &cast_method_router_closure_slot
|
||||
as &dyn Fn(MethodRouter<_, _>) -> MethodRouter<_, _>;
|
||||
|
||||
(fallback, cast_method_router)
|
||||
}
|
||||
// other wants to inherit its state
|
||||
None => {
|
||||
if TypeId::of::<S>() != TypeId::of::<S2>() {
|
||||
panic!(
|
||||
"can't merge a `Router` that wants to inherit state of type `{}` \
|
||||
into a `Router` with a state type of `{}`",
|
||||
type_name::<S2>(),
|
||||
type_name::<S>(),
|
||||
);
|
||||
}
|
||||
|
||||
// With the branch above not taken, we know we can cast S2 to S
|
||||
let fallback = fallback.downcast_state::<S>().unwrap();
|
||||
|
||||
fn cast_method_router<S, S2, B>(r: MethodRouter<S2, B>) -> MethodRouter<S, B>
|
||||
where
|
||||
B: Send + 'static,
|
||||
S: 'static,
|
||||
S2: Clone + 'static,
|
||||
{
|
||||
r.downcast_state().unwrap()
|
||||
}
|
||||
|
||||
(fallback, &cast_method_router as _)
|
||||
}
|
||||
};
|
||||
|
||||
for (id, route) in routes {
|
||||
let path = node
|
||||
.route_id_to_path
|
||||
.get(&id)
|
||||
.expect("no path for route id. This is a bug in axum. Please file an issue");
|
||||
self = match route {
|
||||
Endpoint::MethodRouter(method_router) => {
|
||||
self.route(path, cast_method_router(method_router))
|
||||
}
|
||||
Endpoint::MethodRouter(method_router) => self.route(path, method_router),
|
||||
Endpoint::Route(route) => self.route_service(path, route),
|
||||
Endpoint::NestedRouter(router) => {
|
||||
self.route_endpoint(path, Endpoint::NestedRouter(router))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -412,30 +319,18 @@ where
|
||||
<L::Service as Service<Request<NewReqBody>>>::Error: Into<Infallible> + 'static,
|
||||
<L::Service as Service<Request<NewReqBody>>>::Future: Send + 'static,
|
||||
{
|
||||
let layer = ServiceBuilder::new()
|
||||
.map_err(Into::into)
|
||||
.layer(MapResponseLayer::new(IntoResponse::into_response))
|
||||
.layer(layer)
|
||||
.into_inner();
|
||||
|
||||
let routes = self
|
||||
.routes
|
||||
.into_iter()
|
||||
.map(|(id, route)| {
|
||||
let route = match route {
|
||||
Endpoint::MethodRouter(method_router) => {
|
||||
Endpoint::MethodRouter(method_router.layer(layer.clone()))
|
||||
}
|
||||
Endpoint::Route(route) => Endpoint::Route(Route::new(layer.layer(route))),
|
||||
};
|
||||
.map(|(id, endpoint)| {
|
||||
let route = endpoint.layer(layer.clone());
|
||||
(id, route)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let fallback = self.fallback.map(move |svc| Route::new(layer.layer(svc)));
|
||||
let fallback = self.fallback.map(|route| route.layer(layer));
|
||||
|
||||
Router {
|
||||
state: self.state,
|
||||
routes,
|
||||
node: self.node,
|
||||
fallback,
|
||||
@@ -459,28 +354,16 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
let layer = ServiceBuilder::new()
|
||||
.map_err(Into::into)
|
||||
.layer(MapResponseLayer::new(IntoResponse::into_response))
|
||||
.layer(layer)
|
||||
.into_inner();
|
||||
|
||||
let routes = self
|
||||
.routes
|
||||
.into_iter()
|
||||
.map(|(id, route)| {
|
||||
let route = match route {
|
||||
Endpoint::MethodRouter(method_router) => {
|
||||
Endpoint::MethodRouter(method_router.layer(layer.clone()))
|
||||
}
|
||||
Endpoint::Route(route) => Endpoint::Route(Route::new(layer.layer(route))),
|
||||
};
|
||||
.map(|(id, endpoint)| {
|
||||
let route = endpoint.layer(layer.clone());
|
||||
(id, route)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Router {
|
||||
state: self.state,
|
||||
routes,
|
||||
node: self.node,
|
||||
fallback: self.fallback,
|
||||
@@ -493,7 +376,7 @@ where
|
||||
H: Handler<T, S, B>,
|
||||
T: 'static,
|
||||
{
|
||||
self.fallback = Fallback::BoxedHandler(BoxedHandler::new(handler));
|
||||
self.fallback = Fallback::BoxedHandler(BoxedIntoRoute::from_handler(handler));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -510,14 +393,26 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Convert this router into a [`RouterService`] by providing the state.
|
||||
///
|
||||
/// Once this method has been called you cannot add more routes. So it must be called as last.
|
||||
pub fn with_state(self, state: S) -> RouterService<B> {
|
||||
RouterService::new(self, state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Router<(), B>
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
{
|
||||
/// Convert this router into a [`RouterService`].
|
||||
///
|
||||
/// # Panics
|
||||
/// This is a convenience method for routers that don't have any state (i.e. the state type is
|
||||
/// `()`). Use [`Router::with_state`] otherwise.
|
||||
///
|
||||
/// Panics if the router was constructed with [`Router::inherit_state`].
|
||||
#[track_caller]
|
||||
/// Once this method has been called you cannot add more routes. So it must be called as last.
|
||||
pub fn into_service(self) -> RouterService<B> {
|
||||
RouterService::new(self)
|
||||
RouterService::new(self, ())
|
||||
}
|
||||
|
||||
/// Convert this router into a [`MakeService`], that is a [`Service`] whose
|
||||
@@ -542,8 +437,10 @@ where
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// This is a convenience method for routers that don't have any state (i.e. the state type is
|
||||
/// `()`). Use [`RouterService::into_make_service`] otherwise.
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
#[track_caller]
|
||||
pub fn into_make_service(self) -> IntoMakeService<RouterService<B>> {
|
||||
IntoMakeService::new(self.into_service())
|
||||
}
|
||||
@@ -601,39 +498,13 @@ impl fmt::Debug for Node {
|
||||
enum Fallback<S, B, E = Infallible> {
|
||||
Default(Route<B, E>),
|
||||
Service(Route<B, E>),
|
||||
BoxedHandler(BoxedHandler<S, B, E>),
|
||||
BoxedHandler(BoxedIntoRoute<S, B, E>),
|
||||
}
|
||||
|
||||
impl<S, B, E> Fallback<S, B, E>
|
||||
where
|
||||
S: Clone,
|
||||
{
|
||||
fn map_state<S2>(self, state: &S) -> Fallback<S2, B, E> {
|
||||
match self {
|
||||
Self::Default(route) => Fallback::Default(route),
|
||||
Self::Service(route) => Fallback::Service(route),
|
||||
Self::BoxedHandler(handler) => Fallback::Service(handler.into_route(state.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
fn downcast_state<S2>(self) -> Option<Fallback<S2, B, E>>
|
||||
where
|
||||
S: 'static,
|
||||
B: 'static,
|
||||
E: 'static,
|
||||
S2: 'static,
|
||||
{
|
||||
match self {
|
||||
Self::Default(route) => Some(Fallback::Default(route)),
|
||||
Self::Service(route) => Some(Fallback::Service(route)),
|
||||
Self::BoxedHandler(handler) => {
|
||||
try_downcast::<BoxedHandler<S2, B, E>, BoxedHandler<S, B, E>>(handler)
|
||||
.map(Fallback::BoxedHandler)
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge(self, other: Self) -> Option<Self> {
|
||||
match (self, other) {
|
||||
(Self::Default(_), pick @ Self::Default(_)) => Some(pick),
|
||||
@@ -729,6 +600,41 @@ impl<B, E> FallbackRoute<B, E> {
|
||||
enum Endpoint<S, B> {
|
||||
MethodRouter(MethodRouter<S, B>),
|
||||
Route(Route<B>),
|
||||
NestedRouter(BoxedIntoRoute<S, B, Infallible>),
|
||||
}
|
||||
|
||||
impl<S, B> Endpoint<S, B>
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
fn into_route(self, state: S) -> Route<B> {
|
||||
match self {
|
||||
Endpoint::MethodRouter(method_router) => Route::new(method_router.with_state(state)),
|
||||
Endpoint::Route(route) => route,
|
||||
Endpoint::NestedRouter(router) => router.into_route(state),
|
||||
}
|
||||
}
|
||||
|
||||
fn layer<L, NewReqBody>(self, layer: L) -> Endpoint<S, NewReqBody>
|
||||
where
|
||||
L: Layer<Route<B>> + Clone + Send + 'static,
|
||||
L::Service: Service<Request<NewReqBody>> + Clone + Send + 'static,
|
||||
<L::Service as Service<Request<NewReqBody>>>::Response: IntoResponse + 'static,
|
||||
<L::Service as Service<Request<NewReqBody>>>::Error: Into<Infallible> + 'static,
|
||||
<L::Service as Service<Request<NewReqBody>>>::Future: Send + 'static,
|
||||
NewReqBody: 'static,
|
||||
{
|
||||
match self {
|
||||
Endpoint::MethodRouter(method_router) => {
|
||||
Endpoint::MethodRouter(method_router.layer(layer))
|
||||
}
|
||||
Endpoint::Route(route) => Endpoint::Route(route.layer(layer)),
|
||||
Endpoint::NestedRouter(router) => {
|
||||
Endpoint::NestedRouter(router.map(|route| route.layer(layer)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> Clone for Endpoint<S, B> {
|
||||
@@ -736,19 +642,31 @@ impl<S, B> Clone for Endpoint<S, B> {
|
||||
match self {
|
||||
Self::MethodRouter(inner) => Self::MethodRouter(inner.clone()),
|
||||
Self::Route(inner) => Self::Route(inner.clone()),
|
||||
Self::NestedRouter(router) => Self::NestedRouter(router.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> fmt::Debug for Endpoint<S, B> {
|
||||
impl<S, B> fmt::Debug for Endpoint<S, B>
|
||||
where
|
||||
S: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::MethodRouter(inner) => inner.fmt(f),
|
||||
Self::Route(inner) => inner.fmt(f),
|
||||
Self::MethodRouter(method_router) => {
|
||||
f.debug_tuple("MethodRouter").field(method_router).finish()
|
||||
}
|
||||
Self::Route(route) => f.debug_tuple("Route").field(route).finish(),
|
||||
Self::NestedRouter(router) => f.debug_tuple("NestedRouter").field(router).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum RouterOrService<S, B, T> {
|
||||
Router(Router<S, B>),
|
||||
Service(T),
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(warnings)]
|
||||
fn traits() {
|
||||
|
||||
@@ -17,9 +17,10 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tower::{
|
||||
util::{BoxCloneService, Oneshot},
|
||||
ServiceExt,
|
||||
util::{BoxCloneService, MapResponseLayer, Oneshot},
|
||||
ServiceBuilder, ServiceExt,
|
||||
};
|
||||
use tower_layer::Layer;
|
||||
use tower_service::Service;
|
||||
|
||||
/// How routes are stored inside a [`Router`](super::Router).
|
||||
@@ -46,6 +47,25 @@ impl<B, E> Route<B, E> {
|
||||
) -> Oneshot<BoxCloneService<Request<B>, Response, E>, Request<B>> {
|
||||
self.0.clone().oneshot(req)
|
||||
}
|
||||
|
||||
pub(crate) fn layer<L, NewReqBody, NewError>(self, layer: L) -> Route<NewReqBody, NewError>
|
||||
where
|
||||
L: Layer<Route<B, E>> + Clone + Send + 'static,
|
||||
L::Service: Service<Request<NewReqBody>> + Clone + Send + 'static,
|
||||
<L::Service as Service<Request<NewReqBody>>>::Response: IntoResponse + 'static,
|
||||
<L::Service as Service<Request<NewReqBody>>>::Error: Into<NewError> + 'static,
|
||||
<L::Service as Service<Request<NewReqBody>>>::Future: Send + 'static,
|
||||
NewReqBody: 'static,
|
||||
NewError: 'static,
|
||||
{
|
||||
let layer = ServiceBuilder::new()
|
||||
.map_err(Into::into)
|
||||
.layer(MapResponseLayer::new(IntoResponse::into_response))
|
||||
.layer(layer)
|
||||
.into_inner();
|
||||
|
||||
Route::new(layer.layer(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E> Clone for Route<B, E> {
|
||||
|
||||
+27
-14
@@ -1,5 +1,5 @@
|
||||
use super::{
|
||||
future::RouteFuture, url_params, Endpoint, FallbackRoute, Node, Route, RouteId, Router,
|
||||
future::RouteFuture, url_params, FallbackRoute, IntoMakeService, Node, Route, RouteId, Router,
|
||||
};
|
||||
use crate::{
|
||||
body::{Body, HttpBody},
|
||||
@@ -28,26 +28,17 @@ impl<B> RouterService<B>
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
pub(super) fn new<S>(router: Router<S, B>) -> Self
|
||||
pub(super) fn new<S>(router: Router<S, B>, state: S) -> Self
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let state = router
|
||||
.state
|
||||
.expect("Can't turn a `Router` that wants to inherit state into a service");
|
||||
let fallback = router.fallback.into_fallback_route(&state);
|
||||
|
||||
let routes = router
|
||||
.routes
|
||||
.into_iter()
|
||||
.map(|(route_id, endpoint)| {
|
||||
let route = match endpoint {
|
||||
Endpoint::MethodRouter(method_router) => {
|
||||
Route::new(method_router.with_state(state.clone()))
|
||||
}
|
||||
Endpoint::Route(route) => route,
|
||||
};
|
||||
|
||||
let route = endpoint.into_route(state.clone());
|
||||
(route_id, route)
|
||||
})
|
||||
.collect();
|
||||
@@ -55,7 +46,7 @@ where
|
||||
Self {
|
||||
routes,
|
||||
node: router.node,
|
||||
fallback: router.fallback.into_fallback_route(&state),
|
||||
fallback,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +75,28 @@ where
|
||||
|
||||
route.call(req)
|
||||
}
|
||||
|
||||
/// Convert the router into a [`MakeService`] and no state.
|
||||
///
|
||||
/// See [`Router::into_make_service`] for more details.
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
pub fn into_make_service(self) -> IntoMakeService<RouterService<B>> {
|
||||
IntoMakeService::new(self)
|
||||
}
|
||||
|
||||
/// Convert the router into a [`MakeService`] which stores information
|
||||
/// about the incoming connection and has no state.
|
||||
///
|
||||
/// See [`Router::into_make_service_with_connect_info`] for more details.
|
||||
///
|
||||
/// [`MakeService`]: tower::make::MakeService
|
||||
#[cfg(feature = "tokio")]
|
||||
pub fn into_make_service_with_connect_info<C>(
|
||||
self,
|
||||
) -> crate::extract::connect_info::IntoMakeServiceWithConnectInfo<RouterService<B>, C> {
|
||||
crate::extract::connect_info::IntoMakeServiceWithConnectInfo::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Clone for RouterService<B> {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use tower::ServiceExt;
|
||||
|
||||
use super::*;
|
||||
use crate::middleware::{map_request, map_response};
|
||||
|
||||
@@ -50,10 +52,11 @@ async fn or() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn fallback_accessing_state() {
|
||||
let app = Router::with_state("state")
|
||||
.fallback(|State(state): State<&'static str>| async move { state });
|
||||
let app = Router::new()
|
||||
.fallback(|State(state): State<&'static str>| async move { state })
|
||||
.with_state("state");
|
||||
|
||||
let client = TestClient::new(app);
|
||||
let client = TestClient::from_service(app);
|
||||
|
||||
let res = client.get("/does-not-exist").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
@@ -158,3 +161,45 @@ async fn also_inherits_default_layered_fallback() {
|
||||
assert_eq!(res.headers()["x-from-fallback"], "1");
|
||||
assert_eq!(res.text().await, "outer");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fallback_inherited_into_nested_router_service() {
|
||||
let inner = Router::new()
|
||||
.route(
|
||||
"/bar",
|
||||
get(|State(state): State<&'static str>| async move { state }),
|
||||
)
|
||||
.with_state("inner");
|
||||
|
||||
// with a different state
|
||||
let app = Router::<()>::new()
|
||||
.nest_service("/foo", inner)
|
||||
.fallback(outer_fallback);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
let res = client.get("/foo/not-found").send().await;
|
||||
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||
assert_eq!(res.text().await, "outer");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fallback_inherited_into_nested_opaque_service() {
|
||||
let inner = Router::new()
|
||||
.route(
|
||||
"/bar",
|
||||
get(|State(state): State<&'static str>| async move { state }),
|
||||
)
|
||||
.with_state("inner")
|
||||
// even if the service is made more opaque it should still inherit the fallback
|
||||
.boxed_clone();
|
||||
|
||||
// with a different state
|
||||
let app = Router::<()>::new()
|
||||
.nest_service("/foo", inner)
|
||||
.fallback(outer_fallback);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
let res = client.get("/foo/not-found").send().await;
|
||||
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||
assert_eq!(res.text().await, "outer");
|
||||
}
|
||||
|
||||
@@ -397,87 +397,3 @@ async fn middleware_that_return_early() {
|
||||
);
|
||||
assert_eq!(client.get("/public").send().await.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn merge_with_different_state_type() {
|
||||
let inner = Router::with_state("inner".to_owned()).route(
|
||||
"/foo",
|
||||
get(|State(state): State<String>| async move { state }),
|
||||
);
|
||||
|
||||
let app = Router::with_state("outer").merge(inner).route(
|
||||
"/bar",
|
||||
get(|State(state): State<&'static str>| async move { state }),
|
||||
);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/foo").send().await;
|
||||
assert_eq!(res.text().await, "inner");
|
||||
|
||||
let res = client.get("/bar").send().await;
|
||||
assert_eq!(res.text().await, "outer");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn merging_routes_different_method_different_states() {
|
||||
let get = Router::with_state("get state").route(
|
||||
"/",
|
||||
get(|State(state): State<&'static str>| async move { state }),
|
||||
);
|
||||
|
||||
let post = Router::with_state("post state").route(
|
||||
"/",
|
||||
post(|State(state): State<&'static str>| async move { state }),
|
||||
);
|
||||
|
||||
let app = Router::new().merge(get).merge(post);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/").send().await;
|
||||
assert_eq!(res.text().await, "get state");
|
||||
|
||||
let res = client.post("/").send().await;
|
||||
assert_eq!(res.text().await, "post state");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn merging_routes_different_paths_different_states() {
|
||||
let foo = Router::with_state("foo state").route(
|
||||
"/foo",
|
||||
get(|State(state): State<&'static str>| async move { state }),
|
||||
);
|
||||
|
||||
let bar = Router::with_state("bar state").route(
|
||||
"/bar",
|
||||
get(|State(state): State<&'static str>| async move { state }),
|
||||
);
|
||||
|
||||
let app = Router::new().merge(foo).merge(bar);
|
||||
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/foo").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.text().await, "foo state");
|
||||
|
||||
let res = client.get("/bar").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.text().await, "bar state");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inherit_state_via_merge() {
|
||||
let foo = Router::inherit_state().route(
|
||||
"/foo",
|
||||
get(|State(state): State<&'static str>| async move { state }),
|
||||
);
|
||||
|
||||
let app = Router::with_state("state").merge(foo);
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/foo").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.text().await, "state");
|
||||
}
|
||||
|
||||
@@ -760,8 +760,8 @@ async fn extract_state() {
|
||||
inner: InnerState { value: 2 },
|
||||
};
|
||||
|
||||
let app = Router::with_state(state).route("/", get(handler));
|
||||
let client = TestClient::new(app);
|
||||
let app = Router::new().route("/", get(handler)).with_state(state);
|
||||
let client = TestClient::from_service(app);
|
||||
|
||||
let res = client.get("/").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
@@ -769,12 +769,14 @@ async fn extract_state() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn explicitly_set_state() {
|
||||
let app = Router::with_state("...").route_service(
|
||||
"/",
|
||||
get(|State(state): State<&'static str>| async move { state }).with_state("foo"),
|
||||
);
|
||||
let app = Router::new()
|
||||
.route_service(
|
||||
"/",
|
||||
get(|State(state): State<&'static str>| async move { state }).with_state("foo"),
|
||||
)
|
||||
.with_state("...");
|
||||
|
||||
let client = TestClient::new(app);
|
||||
let client = TestClient::from_service(app);
|
||||
let res = client.get("/").send().await;
|
||||
assert_eq!(res.text().await, "foo");
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ async fn multiple_top_level_nests() {
|
||||
#[tokio::test]
|
||||
#[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")]
|
||||
async fn nest_cannot_contain_wildcards() {
|
||||
Router::<_, Body>::new().nest("/one/*rest", Router::new());
|
||||
Router::<(), Body>::new().nest("/one/*rest", Router::new());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -424,48 +424,3 @@ nested_route_test!(nest_9, nest = "/a", route = "/a/", expected = "/a/a/");
|
||||
nested_route_test!(nest_11, nest = "/a/", route = "/", expected = "/a/");
|
||||
nested_route_test!(nest_12, nest = "/a/", route = "/a", expected = "/a/a");
|
||||
nested_route_test!(nest_13, nest = "/a/", route = "/a/", expected = "/a/a/");
|
||||
|
||||
#[tokio::test]
|
||||
async fn nesting_with_different_state() {
|
||||
let inner = Router::with_state("inner".to_owned()).route(
|
||||
"/foo",
|
||||
get(|State(state): State<String>| async move { state }),
|
||||
);
|
||||
|
||||
let outer = Router::with_state("outer")
|
||||
.route(
|
||||
"/foo",
|
||||
get(|State(state): State<&'static str>| async move { state }),
|
||||
)
|
||||
.nest("/nested", inner)
|
||||
.route(
|
||||
"/bar",
|
||||
get(|State(state): State<&'static str>| async move { state }),
|
||||
);
|
||||
|
||||
let client = TestClient::new(outer);
|
||||
|
||||
let res = client.get("/foo").send().await;
|
||||
assert_eq!(res.text().await, "outer");
|
||||
|
||||
let res = client.get("/nested/foo").send().await;
|
||||
assert_eq!(res.text().await, "inner");
|
||||
|
||||
let res = client.get("/bar").send().await;
|
||||
assert_eq!(res.text().await, "outer");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn inherit_state_via_nest() {
|
||||
let foo = Router::inherit_state().route(
|
||||
"/foo",
|
||||
get(|State(state): State<&'static str>| async move { state }),
|
||||
);
|
||||
|
||||
let app = Router::with_state("state").nest("/test", foo);
|
||||
let client = TestClient::new(app);
|
||||
|
||||
let res = client.get("/test/foo").send().await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(res.text().await, "state");
|
||||
}
|
||||
|
||||
@@ -15,10 +15,7 @@ pub(crate) struct TestClient {
|
||||
}
|
||||
|
||||
impl TestClient {
|
||||
pub(crate) fn new<S>(router: Router<S, Body>) -> Self
|
||||
where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
pub(crate) fn new(router: Router<(), Body>) -> Self {
|
||||
Self::from_service(router.into_service())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user