Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand the docs for Router::with_state #1580

Merged
merged 1 commit into from Nov 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions axum-core/src/extract/from_ref.rs
Expand Up @@ -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)]`.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The link on this didn't work for some reason but I don't actually think we need it since the macro is exported at the same path as FromRef itself.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, there were no square brackets around the code block.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even with I couldn't get it working 🤷

///
/// [`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> {
Expand Down
236 changes: 236 additions & 0 deletions axum/src/docs/routing/with_state.md
@@ -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.
58 changes: 1 addition & 57 deletions axum/src/routing/mod.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down