From 6771729d274870e55f34a7e970bfa203113183bc Mon Sep 17 00:00:00 2001 From: jimmycuadra Date: Tue, 22 Nov 2022 00:08:39 -0800 Subject: [PATCH] Improve `State` and `Router` docs (#1543) --- axum/src/docs/routing/merge.md | 10 ++++- axum/src/docs/routing/nest.md | 45 +++++---------------- axum/src/extract/state.rs | 71 ++++++++++++++++++++++++++++++++++ axum/src/lib.rs | 7 ++-- axum/src/routing/mod.rs | 35 +++++++++++++++++ 5 files changed, 128 insertions(+), 40 deletions(-) diff --git a/axum/src/docs/routing/merge.md b/axum/src/docs/routing/merge.md index a8038d38df..5d2b94bebd 100644 --- a/axum/src/docs/routing/merge.md +++ b/axum/src/docs/routing/merge.md @@ -37,7 +37,15 @@ 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. + +# 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 diff --git a/axum/src/docs/routing/nest.md b/axum/src/docs/routing/nest.md index 96dbd3345e..d1a8dafce8 100644 --- a/axum/src/docs/routing/nest.md +++ b/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. @@ -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 @@ -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) {} - -let inner_router = Router::new() - .route("/bar", get(inner_handler)) - .with_state(InnerState {}); - -async fn outer_handler(state: State) {} - -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 @@ -193,3 +165,4 @@ for more details. [`OriginalUri`]: crate::extract::OriginalUri [fallbacks]: Router::fallback +[combining-stateful-routers]: crate::extract::State#combining-stateful-routers diff --git a/axum/src/extract/state.rs b/axum/src/extract/state.rs index 89a4ceb9fd..3563cef110 100644 --- a/axum/src/extract/state.rs +++ b/axum/src/extract/state.rs @@ -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) { +/// // 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 { +/// Router::new() +/// .route("/posts", get(posts_handler)) +/// } +/// +/// async fn posts_handler(State(state): State) { +/// // 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` /// /// ``` diff --git a/axum/src/lib.rs b/axum/src/lib.rs index d3a1be4b81..f73d279b13 100644 --- a/axum/src/lib.rs +++ b/axum/src/lib.rs @@ -165,11 +165,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 //! diff --git a/axum/src/routing/mod.rs b/axum/src/routing/mod.rs index 2544ac4125..2618cb66ec 100644 --- a/axum/src/routing/mod.rs +++ b/axum/src/routing/mod.rs @@ -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) {} + /// + /// let inner_router = Router::new() + /// .route("/bar", get(inner_handler)) + /// .with_state(InnerState {}); + /// + /// async fn outer_handler(state: State) {} + /// + /// 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. #[track_caller] pub fn nest_service(self, path: &str, svc: T) -> Self where