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

Improve State and Router docs #1543

Merged
merged 4 commits into from Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
13 changes: 12 additions & 1 deletion axum/src/docs/routing/merge.md
Expand Up @@ -37,7 +37,18 @@ let app = Router::new()
# };
```

## Panics
# Merging routers with state

When combining [`Router`]s with this function, each [`Router`] must have the
same type of state. See ["Combining stateful routers"][combining-stateful-routers]
for details.

If you want to compose axum services with different types of state, use
[`Router::nest_service`].
jimmycuadra marked this conversation as resolved.
Show resolved Hide resolved

# Panics

- If two routers that each have a [fallback](Router::fallback) are merged. This
is because `Router` only allows a single fallback.

[combining-stateful-routers]: crate::extract::State#combining-stateful-routers
45 changes: 9 additions & 36 deletions axum/src/docs/routing/nest.md
@@ -1,4 +1,4 @@
Nest a [`Service`] at some path.
Nest a [`Router`] at some path.

This allows you to break your application into smaller pieces and compose
them together.
Expand Down Expand Up @@ -64,7 +64,7 @@ let app = Router::new().nest("/:version/api", users_api);
# };
```

# Differences to wildcard routes
# Differences from wildcard routes

Nested routes are similar to wildcard routes. The difference is that
wildcard routes still see the whole URI whereas nested routes will have
Expand Down Expand Up @@ -147,42 +147,14 @@ let app = Router::new()

Here requests like `GET /api/not-found` will go to `api_fallback`.

# Nesting a router with a different state type
# Nesting routers with state

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`]:
When combining [`Router`]s with this function, each [`Router`] must have the
same type of state. See ["Combining stateful routers"][combining-stateful-routers]
for details.

```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.
If you want to compose axum services with different types of state, use
[`Router::nest_service`].

# Panics

Expand All @@ -193,3 +165,4 @@ for more details.

[`OriginalUri`]: crate::extract::OriginalUri
[fallbacks]: Router::fallback
[combining-stateful-routers]: crate::extract::State#combining-stateful-routers
73 changes: 72 additions & 1 deletion axum/src/extract/state.rs
Expand Up @@ -8,7 +8,7 @@ use std::{

/// Extractor for state.
///
/// See ["Accessing state in middleware"][state-from-middleware] for how to
/// See ["Accessing state in middleware"][state-from-middleware] for how to
/// access state in middleware.
///
/// [state-from-middleware]: crate::middleware#accessing-state-in-middleware
Expand Down Expand Up @@ -46,6 +46,77 @@ use std::{
/// # let _: axum::routing::RouterService = app;
/// ```
///
/// ## Combining stateful routers
///
/// Multiple [`Router`]s can be combined with [`Router::nest`] or [`Router::merge`]
/// When combining [`Router`]s with one of these methods, the [`Router`]s must have
/// the same state type. Generally, this can be inferred automatically:
///
/// ```
/// use axum::{Router, routing::get, extract::State};
///
/// #[derive(Clone)]
/// struct AppState {}
///
/// let state = AppState {};
///
/// // create a `Router` that will be nested within another
/// let api = Router::new()
/// .route("/posts", get(posts_handler));
///
/// let app = Router::new()
/// .nest("/api", api)
/// .with_state(state);
///
/// async fn posts_handler(State(state): State<AppState>) {
/// // use `state`...
/// }
/// # let _: axum::routing::RouterService = app;
/// ```
///
/// However, if you are composing [`Router`]s that are defined in separate scopes,
/// you may need to annotate the [`State`] type explicitly:
///
/// ```
/// use axum::{Router, RouterService, routing::get, extract::State};
///
/// #[derive(Clone)]
/// struct AppState {}
///
/// fn make_app() -> RouterService {
/// let state = AppState {};
///
/// Router::new()
/// .nest("/api", make_api())
/// .with_state(state) // the outer Router's state is inferred
/// }
///
/// // the inner Router must specify its state type to compose with the
/// // outer router
/// fn make_api() -> Router<AppState> {
/// Router::new()
/// .route("/posts", get(posts_handler))
/// }
///
/// async fn posts_handler(State(state): State<AppState>) {
/// // use `state`...
/// }
/// # let _: axum::routing::RouterService = make_app();
/// ```
///
/// In short, a [`Router`]'s generic state type defaults to `()`
/// (no state) unless [`Router::with_state`] is called or the value
/// of the generic type is given explicitly.
///
/// It's also possible to combine multiple axum services with different state
/// types. See [`Router::nest_service`] for details.
///
/// [`Router`]: crate::Router
/// [`Router::merge`]: crate::Router::merge
/// [`Router::nest_service`]: crate::Router::nest_service
/// [`Router::nest`]: crate::Router::nest
/// [`Router::with_state`]: crate::Router::with_state
///
/// # With `MethodRouter`
///
/// ```
Expand Down
7 changes: 4 additions & 3 deletions axum/src/lib.rs
Expand Up @@ -164,11 +164,12 @@
//!
//! # Sharing state with handlers
//!
//! It is common to share some state between handlers for example to share a
//! pool of database connections or clients to other services.
//! It is common to share some state between handlers. For example, a
//! pool of database connections or clients to other services may need to
//! be shared.
//!
//! The three most common ways of doing that are:
//! - Using the [`State`] extractor.
//! - Using the [`State`] extractor
//! - Using request extensions
//! - Using closure captures
//!
Expand Down
35 changes: 35 additions & 0 deletions axum/src/routing/mod.rs
Expand Up @@ -212,6 +212,41 @@ where
}

/// Like [`nest`](Self::nest), but accepts an arbitrary `Service`.
///
/// While [`nest`](Self::nest) requires [`Router`]s with the same type of
/// state, you can use this method to combine [`Router`]s with different
/// types of state:
///
/// ```
/// 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.
davidpdrsn marked this conversation as resolved.
Show resolved Hide resolved
#[track_caller]
pub fn nest_service<T>(self, path: &str, svc: T) -> Self
where
Expand Down