Expand the docs for Router::with_state (#1580)

This commit is contained in:
David Pedersen 2022-11-27 01:01:59 +01:00 committed by GitHub
parent 8d62697c72
commit eb2e933054
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 238 additions and 59 deletions

View file

@ -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> {

View 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.

View file

@ -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