mirror of
https://github.com/tokio-rs/axum.git
synced 2024-12-28 23:38:20 +01:00
Expand the docs for Router::with_state
(#1580)
This commit is contained in:
parent
8d62697c72
commit
eb2e933054
3 changed files with 238 additions and 59 deletions
|
@ -5,10 +5,9 @@
|
|||
///
|
||||
/// See [`State`] for more details on how library authors should use this trait.
|
||||
///
|
||||
/// This trait can be derived using `#[derive(axum_macros::FromRef)]`.
|
||||
/// This trait can be derived using `#[derive(FromRef)]`.
|
||||
///
|
||||
/// [`State`]: https://docs.rs/axum/0.6/axum/extract/struct.State.html
|
||||
/// [`#[derive(axum_macros::FromRef)]`]: https://docs.rs/axum-macros/latest/axum_macros/derive.FromRef.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> {
|
||||
|
|
236
axum/src/docs/routing/with_state.md
Normal file
236
axum/src/docs/routing/with_state.md
Normal file
|
@ -0,0 +1,236 @@
|
|||
Provide the state for the router.
|
||||
|
||||
```rust
|
||||
use axum::{Router, routing::get, extract::State};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {}
|
||||
|
||||
let routes = Router::new()
|
||||
.route("/", get(|State(state): State<AppState>| async {
|
||||
// use state
|
||||
}))
|
||||
.with_state(AppState {});
|
||||
|
||||
# async {
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(routes.into_make_service())
|
||||
.await;
|
||||
# };
|
||||
```
|
||||
|
||||
# Returning routers with states from functions
|
||||
|
||||
When returning `Router`s from functions it is generally recommend not set the
|
||||
state directly:
|
||||
|
||||
```rust
|
||||
use axum::{Router, routing::get, extract::State};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {}
|
||||
|
||||
// Don't call `Router::with_state` here
|
||||
fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(|_: State<AppState>| async {}))
|
||||
}
|
||||
|
||||
// Instead do it before you run the server
|
||||
let routes = routes().with_state(AppState {});
|
||||
|
||||
# async {
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(routes.into_make_service())
|
||||
.await;
|
||||
# };
|
||||
```
|
||||
|
||||
If you do need to provide the state, and you're _not_ nesting/merging the router
|
||||
into another router, then return `Router` without any type parameters:
|
||||
|
||||
```rust
|
||||
# use axum::{Router, routing::get, extract::State};
|
||||
# #[derive(Clone)]
|
||||
# struct AppState {}
|
||||
#
|
||||
// Don't return `Router<AppState>`
|
||||
fn routes(state: AppState) -> Router {
|
||||
Router::new()
|
||||
.route("/", get(|_: State<AppState>| async {}))
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
let routes = routes(AppState {});
|
||||
|
||||
# async {
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(routes.into_make_service())
|
||||
.await;
|
||||
# };
|
||||
```
|
||||
|
||||
This is because we can only call `Router::into_make_service` on `Router<()>`,
|
||||
not `Router<AppState>`. See below for more details about why that is.
|
||||
|
||||
Note that the state defaults to `()` so `Router` and `Router<()>` is the same.
|
||||
|
||||
If you are nesting/merging the router it is recommended to use a generic state
|
||||
type on the resulting router:
|
||||
|
||||
```rust
|
||||
# use axum::{Router, routing::get, extract::State};
|
||||
# #[derive(Clone)]
|
||||
# struct AppState {}
|
||||
#
|
||||
fn routes<S>(state: AppState) -> Router<S> {
|
||||
Router::new()
|
||||
.route("/", get(|_: State<AppState>| async {}))
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
let routes = Router::new().nest("/api", routes(AppState {}));
|
||||
|
||||
# async {
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(routes.into_make_service())
|
||||
.await;
|
||||
# };
|
||||
```
|
||||
|
||||
# What `S` in `Router<S>` means
|
||||
|
||||
`Router<S>` means a router that is _missing_ a state of type `S` to be able to
|
||||
handle requests. It does _not_ mean a `Router` that _has_ a state of type `S`.
|
||||
|
||||
For example:
|
||||
|
||||
```rust
|
||||
# use axum::{Router, routing::get, extract::State};
|
||||
# #[derive(Clone)]
|
||||
# struct AppState {}
|
||||
#
|
||||
// A router that _needs_ an `AppState` to handle requests
|
||||
let router: Router<AppState> = Router::new()
|
||||
.route("/", get(|_: State<AppState>| async {}));
|
||||
|
||||
// Once we call `Router::with_state` the router isn't missing
|
||||
// the state anymore, because we just provided it
|
||||
//
|
||||
// Therefore the router type becomes `Router<()>`, i.e a router
|
||||
// that is not missing any state
|
||||
let router: Router<()> = router.with_state(AppState {});
|
||||
|
||||
// Only `Router<()>` has the `into_make_service` method.
|
||||
//
|
||||
// You cannot call `into_make_service` on a `Router<AppState>`
|
||||
// because it is still missing an `AppState`.
|
||||
# async {
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(router.into_make_service())
|
||||
.await;
|
||||
# };
|
||||
```
|
||||
|
||||
Perhaps a little counter intuitively, `Router::with_state` doesn't always return a
|
||||
`Router<()>`. Instead you get to pick what the new missing state type is:
|
||||
|
||||
```rust
|
||||
# use axum::{Router, routing::get, extract::State};
|
||||
# #[derive(Clone)]
|
||||
# struct AppState {}
|
||||
#
|
||||
let router: Router<AppState> = Router::new()
|
||||
.route("/", get(|_: State<AppState>| async {}));
|
||||
|
||||
// When we call `with_state` we're able to pick what the next missing state type is.
|
||||
// Here we pick `String`.
|
||||
let string_router: Router<String> = router.with_state(AppState {});
|
||||
|
||||
// That allows us to add new routes that uses `String` as the state type
|
||||
let string_router = string_router
|
||||
.route("/needs-string", get(|_: State<String>| async {}));
|
||||
|
||||
// Provide the `String` and choose `()` as the new missing state.
|
||||
let final_router: Router<()> = string_router.with_state("foo".to_owned());
|
||||
|
||||
// Since we have a `Router<()>` we can run it.
|
||||
# async {
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(final_router.into_make_service())
|
||||
.await;
|
||||
# };
|
||||
```
|
||||
|
||||
This why this returning `Router<AppState>` after calling `with_state` doesn't
|
||||
work:
|
||||
|
||||
```rust,compile_fail
|
||||
# use axum::{Router, routing::get, extract::State};
|
||||
# #[derive(Clone)]
|
||||
# struct AppState {}
|
||||
#
|
||||
// This wont work because we're returning a `Router<AppState>`
|
||||
// i.e. we're saying we're still missing an `AppState`
|
||||
fn routes(state: AppState) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(|_: State<AppState>| async {}))
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
let app = routes(AppState {});
|
||||
|
||||
// We can only call `Router::into_make_service` on a `Router<()>`
|
||||
// but `app` is a `Router<AppState>`
|
||||
# async {
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
.await;
|
||||
# };
|
||||
```
|
||||
|
||||
Instead return `Router<()>` since we have provided all the state needed:
|
||||
|
||||
```rust
|
||||
# use axum::{Router, routing::get, extract::State};
|
||||
# #[derive(Clone)]
|
||||
# struct AppState {}
|
||||
#
|
||||
// We've provided all the state necessary so return `Router<()>`
|
||||
fn routes(state: AppState) -> Router<()> {
|
||||
Router::new()
|
||||
.route("/", get(|_: State<AppState>| async {}))
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
let app = routes(AppState {});
|
||||
|
||||
// We can now call `Router::into_make_service`
|
||||
# async {
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
.await;
|
||||
# };
|
||||
```
|
||||
|
||||
# A note about performance
|
||||
|
||||
If you need a `Router` that implements `Service` but you don't need any state (perhaps
|
||||
you're making a library that uses axum internally) then it is recommended to call this
|
||||
method before you start serving requests:
|
||||
|
||||
```rust
|
||||
use axum::{Router, routing::get};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { /* ... */ }))
|
||||
// even though we don't need any state, call `with_state(())` anyway
|
||||
.with_state(());
|
||||
# let _: Router = app;
|
||||
```
|
||||
|
||||
This is not required but it gives axum a chance to update some internals in the router
|
||||
which may impact performance and reduce allocations.
|
||||
|
||||
Note that [`Router::into_make_service`] and [`Router::into_make_service_with_connect_info`]
|
||||
do this automatically.
|
|
@ -103,8 +103,6 @@ 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 {}
|
||||
|
||||
impl<S, B> Router<S, B>
|
||||
where
|
||||
B: HttpBody + Send + 'static,
|
||||
|
@ -396,61 +394,7 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Provide the state for the router.
|
||||
///
|
||||
/// This method returns a router with a different state type. This can be used to nest or merge
|
||||
/// routers with different state types. See [`Router::nest`] and [`Router::merge`] for more
|
||||
/// details.
|
||||
///
|
||||
/// # Implementing `Service`
|
||||
///
|
||||
/// This can also be used to get a `Router` that implements [`Service`], since it only does so
|
||||
/// when the state is `()`:
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// body::Body,
|
||||
/// http::Request,
|
||||
/// };
|
||||
/// use tower::{Service, ServiceExt};
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// struct AppState {}
|
||||
///
|
||||
/// // this router doesn't implement `Service` because its state isn't `()`
|
||||
/// let router: Router<AppState> = Router::new();
|
||||
///
|
||||
/// // by providing the state and setting the new state to `()`...
|
||||
/// let router_service: Router<()> = router.with_state(AppState {});
|
||||
///
|
||||
/// // ...makes it implement `Service`
|
||||
/// # async {
|
||||
/// router_service.oneshot(Request::new(Body::empty())).await;
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// # A note about performance
|
||||
///
|
||||
/// If you need a `Router` that implements `Service` but you don't need any state (perhaps
|
||||
/// you're making a library that uses axum internally) then it is recommended to call this
|
||||
/// method before you start serving requests:
|
||||
///
|
||||
/// ```
|
||||
/// use axum::{Router, routing::get};
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/", get(|| async { /* ... */ }))
|
||||
/// // even though we don't need any state, call `with_state(())` anyway
|
||||
/// .with_state(());
|
||||
/// # let _: Router = app;
|
||||
/// ```
|
||||
///
|
||||
/// This is not required but it gives axum a chance to update some internals in the router
|
||||
/// which may impact performance and reduce allocations.
|
||||
///
|
||||
/// Note that [`Router::into_make_service`] and [`Router::into_make_service_with_connect_info`]
|
||||
/// do this automatically.
|
||||
#[doc = include_str!("../docs/routing/with_state.md")]
|
||||
pub fn with_state<S2>(self, state: S) -> Router<S2, B> {
|
||||
let routes = self
|
||||
.routes
|
||||
|
|
Loading…
Reference in a new issue