diff --git a/axum/src/error_handling/mod.rs b/axum/src/error_handling/mod.rs index b423f041..3bb1c9e8 100644 --- a/axum/src/error_handling/mod.rs +++ b/axum/src/error_handling/mod.rs @@ -22,8 +22,6 @@ use tower_service::Service; /// that handles errors by converting them into responses. /// /// See [module docs](self) for more details on axum's error handling model. -// TODO(david): cannot access state, is that bad? It leads to inference issues and one has to -// specify the type manually and risk getting it wrong. So its basically an Extension at that point pub struct HandleErrorLayer { f: F, _extractor: PhantomData T>, diff --git a/axum/src/extract/state.rs b/axum/src/extract/state.rs index 9c2c5516..fd9b0c48 100644 --- a/axum/src/extract/state.rs +++ b/axum/src/extract/state.rs @@ -2,8 +2,7 @@ use super::{FromRequest, RequestParts}; use async_trait::async_trait; use std::convert::Infallible; -/// TODO(david): docs -// TODO(david): document how to extract this from middleware +// document how to extract this from middleware #[derive(Clone, Copy, Debug, Default)] pub struct State(pub S); diff --git a/axum/src/handler/into_extension_service.rs b/axum/src/handler/into_extension_service.rs index 1d9e54ef..3864acd1 100644 --- a/axum/src/handler/into_extension_service.rs +++ b/axum/src/handler/into_extension_service.rs @@ -1,5 +1,5 @@ use super::Handler; -use crate::{extract::State, response::Response}; +use crate::{response::Response, util::extract_state_assume_present}; use http::Request; use std::{ convert::Infallible, @@ -59,18 +59,7 @@ where let handler = self.handler.clone(); - // TODO(david): this is duplicated in `axum/src/routing/mod.rs` - // extract into helper function - let State(state) = req - .extensions() - .get::>() - .unwrap_or_else(|| { - panic!( - "no state of type `{}` was found. Please file an issue", - std::any::type_name::>() - ) - }) - .clone(); + let state = extract_state_assume_present::(&req); let future = Handler::call(handler, state, req); let future = future.map(Ok as _); diff --git a/axum/src/handler/mod.rs b/axum/src/handler/mod.rs index b4e20a5b..ec477bcd 100644 --- a/axum/src/handler/mod.rs +++ b/axum/src/handler/mod.rs @@ -64,8 +64,7 @@ pub mod future; /// See the [module docs](crate::handler) for more details. /// #[doc = include_str!("../docs/debugging_handler_type_errors.md")] -// TODO(david): Add back `B = Body` default -pub trait Handler: Clone + Send + Sized + 'static { +pub trait Handler: Clone + Send + Sized + 'static { /// The type of future calling this handler returns. type Future: Future + Send + 'static; @@ -151,7 +150,6 @@ pub trait Handler: Clone + Send + Sized + 'static { /// ``` /// /// [`Router::fallback`]: crate::routing::Router::fallback - // TODO(david): remove this fn into_service(self, state: S) -> IntoService { IntoService::new(self, state) } @@ -179,7 +177,6 @@ pub trait Handler: Clone + Send + Sized + 'static { /// ``` /// /// [`MakeService`]: tower::make::MakeService - // TODO(david): remove this fn into_make_service(self, state: S) -> IntoMakeService> { IntoMakeService::new(self.into_service(state)) } @@ -212,7 +209,6 @@ pub trait Handler: Clone + Send + Sized + 'static { /// /// [`MakeService`]: tower::make::MakeService /// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info - // TODO(david): remove this fn into_make_service_with_connect_info( self, state: S, @@ -350,16 +346,20 @@ where #[cfg(test)] mod tests { use super::*; - use crate::test_helpers::*; + use crate::{extract::State, test_helpers::*}; use http::StatusCode; #[tokio::test] async fn handler_into_service() { - async fn handle(body: String) -> impl IntoResponse { + async fn handle(State(state): State, body: String) -> impl IntoResponse { + assert_eq!(state.0, 1337); format!("you said: {}", body) } - let client = TestClient::new(handle.into_service(())); + #[derive(Clone)] + struct AppState(i32); + + let client = TestClient::new(handle.into_service(AppState(1337))); let res = client.post("/").body("hi there!").send().await; assert_eq!(res.status(), StatusCode::OK); diff --git a/axum/src/lib.rs b/axum/src/lib.rs index aead8fde..c5f19505 100644 --- a/axum/src/lib.rs +++ b/axum/src/lib.rs @@ -382,8 +382,8 @@ rust_2018_idioms, future_incompatible, nonstandard_style, - // missing_debug_implementations, - // missing_docs + missing_debug_implementations, + missing_docs )] #![deny(unreachable_pub, private_in_public)] #![allow(elided_lifetimes_in_paths, clippy::type_complexity)] diff --git a/axum/src/middleware/from_fn.rs b/axum/src/middleware/from_fn.rs index dfefcebe..1346e01f 100644 --- a/axum/src/middleware/from_fn.rs +++ b/axum/src/middleware/from_fn.rs @@ -400,7 +400,7 @@ mod tests { #[tokio::test] async fn extracting_state() { - async fn access_state(req: Request, next: Next) -> impl IntoResponse { + async fn access_state(req: Request, _next: Next) -> impl IntoResponse { let State(state) = req.extensions().get::>().unwrap().clone(); state.value } diff --git a/axum/src/response/mod.rs b/axum/src/response/mod.rs index b8d5e9e5..a15571bf 100644 --- a/axum/src/response/mod.rs +++ b/axum/src/response/mod.rs @@ -61,7 +61,7 @@ impl From for Html { #[cfg(test)] mod tests { use crate::extract::Extension; - use crate::{body::Body, routing::get, Router}; + use crate::{routing::get, Router}; use axum_core::response::IntoResponse; use http::HeaderMap; use http::{StatusCode, Uri}; diff --git a/axum/src/routing/method_routing.rs b/axum/src/routing/method_routing.rs index a8856ec2..5a93c420 100644 --- a/axum/src/routing/method_routing.rs +++ b/axum/src/routing/method_routing.rs @@ -76,7 +76,7 @@ macro_rules! top_level_service_fn { $name:ident, $method:ident ) => { $(#[$m])+ - pub fn $name(svc: T) -> MethodRouter + pub fn $name(svc: T) -> MethodRouter where T: Service, Response = Response> + Clone + Send + 'static, T::Future: Send + 'static, @@ -137,7 +137,7 @@ macro_rules! top_level_handler_fn { $name:ident, $method:ident ) => { $(#[$m])+ - pub fn $name(handler: H) -> MethodRouter + pub fn $name(handler: H) -> MethodRouter where H: Handler, B: Send + 'static, @@ -321,7 +321,7 @@ top_level_service_fn!(trace_service, TRACE); pub fn on_service( filter: MethodFilter, svc: T, -) -> MethodRouter +) -> MethodRouter where T: Service, Response = Response> + Clone + Send + 'static, T::Future: Send + 'static, @@ -386,7 +386,7 @@ where /// ``` pub fn any_service( svc: T, -) -> MethodRouter +) -> MethodRouter where T: Service, Response = Response> + Clone + Send + 'static, T::Future: Send + 'static, @@ -429,7 +429,7 @@ top_level_handler_fn!(trace, TRACE); pub fn on( filter: MethodFilter, handler: H, -) -> MethodRouter +) -> MethodRouter where H: Handler, B: Send + 'static, @@ -476,7 +476,7 @@ where /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # }; /// ``` -pub fn any(handler: H) -> MethodRouter +pub fn any(handler: H) -> MethodRouter where H: Handler, B: Send + 'static, @@ -490,9 +490,7 @@ where /// A [`Service`] that accepts requests based on a [`MethodFilter`] and /// allows chaining additional handlers and services. -// TODO(david): Bring back `B = Body, E = Infallible` defaults -// TODO(david): think about ordering of type params here -pub struct MethodRouter { +pub struct MethodRouter { // Invariant: If `R == MissingState` then `state` is `None` // If `R == WithState` then state is `Some` // `R` cannot have other values @@ -520,7 +518,7 @@ enum AllowHeader { Bytes(BytesMut), } -impl fmt::Debug for MethodRouter +impl fmt::Debug for MethodRouter where S: fmt::Debug, { @@ -540,7 +538,7 @@ where } } -impl MethodRouter { +impl MethodRouter { /// Create a default `MethodRouter` that will respond with `405 Method Not Allowed` to all /// requests. pub fn new() -> Self { @@ -566,8 +564,7 @@ impl MethodRouter { } } - /// TODO(david): docs - pub fn state(self, state: S) -> MethodRouter { + pub fn state(self, state: S) -> MethodRouter { MethodRouter { state: Some(state), get: self.get, @@ -585,21 +582,19 @@ impl MethodRouter { } } -impl MethodRouter { - /// TODO(david): docs +impl MethodRouter { pub fn with_state(state: S) -> Self { MethodRouter::new().state(state) } } -impl MethodRouter<(), B, E, WithState> { - /// TODO(david): docs +impl MethodRouter<(), WithState, B, E> { pub fn without_state() -> Self { MethodRouter::with_state(()) } } -impl MethodRouter +impl MethodRouter where B: Send + 'static, S: Clone + Send + Sync + 'static, @@ -645,7 +640,7 @@ where chained_handler_fn!(trace, TRACE); #[doc = include_str!("../docs/routing/fallback.md")] - pub fn fallback(mut self, handler: H) -> Self + pub fn fallback(self, handler: H) -> Self where H: Handler, T: 'static, @@ -654,8 +649,8 @@ where } } -impl MethodRouter { - pub(crate) fn change_state_marker(self) -> MethodRouter { +impl MethodRouter { + pub(crate) fn change_state_marker(self) -> MethodRouter { MethodRouter { state: self.state, get: self.get, @@ -673,8 +668,8 @@ impl MethodRouter { } } -impl MethodRouter { - pub(crate) fn change_state(self) -> MethodRouter { +impl MethodRouter { + pub(crate) fn change_state(self) -> MethodRouter { debug_assert!(self.state.is_none()); MethodRouter { state: None, @@ -693,7 +688,7 @@ impl MethodRouter { } } -impl MethodRouter +impl MethodRouter where B: Send + 'static, S: Clone + Send + Sync + 'static, @@ -767,7 +762,7 @@ where } } -impl MethodRouter { +impl MethodRouter { /// Chain an additional service that will accept requests matching the given /// `MethodFilter`. /// @@ -837,7 +832,7 @@ impl MethodRouter { pub fn layer( self, layer: L, - ) -> MethodRouter + ) -> MethodRouter where L: Layer>, L::Service: Service, Response = Response, Error = NewError> @@ -872,7 +867,7 @@ impl MethodRouter { } #[doc = include_str!("../docs/method_routing/route_layer.md")] - pub fn route_layer(self, layer: L) -> MethodRouter + pub fn route_layer(self, layer: L) -> MethodRouter where L: Layer>, L::Service: Service, Response = Response, Error = E> @@ -907,7 +902,7 @@ impl MethodRouter { } #[doc = include_str!("../docs/method_routing/merge.md")] - pub fn merge(self, other: MethodRouter) -> Self { + pub fn merge(self, other: MethodRouter) -> Self { macro_rules! merge { ( $first:ident, $second:ident ) => { match ($first, $second) { @@ -1003,7 +998,7 @@ impl MethodRouter { /// Apply a [`HandleErrorLayer`]. /// /// This is a convenience method for doing `self.layer(HandleErrorLayer::new(f))`. - pub fn handle_error(self, f: F) -> MethodRouter + pub fn handle_error(self, f: F) -> MethodRouter where F: Clone + Send + 'static, HandleError, F, T>: @@ -1120,7 +1115,7 @@ fn append_allow_header(allow_header: &mut AllowHeader, method: &'static str) { } } -impl Clone for MethodRouter +impl Clone for MethodRouter where S: Clone, { @@ -1142,7 +1137,7 @@ where } } -impl Default for MethodRouter +impl Default for MethodRouter where B: Send + 'static, { @@ -1151,7 +1146,7 @@ where } } -impl Service> for MethodRouter +impl Service> for MethodRouter where B: HttpBody, S: Clone + Send + Sync + 'static, @@ -1430,7 +1425,7 @@ mod tests { expected = "Overlapping method route. Cannot add two method routes that both handle `GET`" )] async fn handler_overlaps() { - let _: MethodRouter<(), Body, Infallible, _> = get(ok).get(ok); + let _: MethodRouter<(), _, Body, Infallible> = get(ok).get(ok); } #[tokio::test] @@ -1438,18 +1433,18 @@ mod tests { expected = "Overlapping method route. Cannot add two method routes that both handle `POST`" )] async fn service_overlaps() { - let _: MethodRouter<(), Body, Infallible, _> = + let _: MethodRouter<(), _, Body, Infallible> = post_service(ok.into_service(())).post_service(ok.into_service(())); } #[tokio::test] async fn get_head_does_not_overlap() { - let _: MethodRouter<(), Body, Infallible, _> = get(ok).head(ok); + let _: MethodRouter<(), _, Body, Infallible> = get(ok).head(ok); } #[tokio::test] async fn head_get_does_not_overlap() { - let _: MethodRouter<(), Body, Infallible, _> = head(ok).get(ok); + let _: MethodRouter<(), _, Body, Infallible> = head(ok).get(ok); } async fn call(method: Method, svc: &mut S) -> (StatusCode, HeaderMap, String) diff --git a/axum/src/routing/mod.rs b/axum/src/routing/mod.rs index f2055d71..ea54be3c 100644 --- a/axum/src/routing/mod.rs +++ b/axum/src/routing/mod.rs @@ -7,7 +7,7 @@ use crate::{ handler::{Handler, IntoExtensionService}, response::Response, routing::strip_prefix::StripPrefix, - util::try_downcast, + util::{extract_state_assume_present, try_downcast}, BoxError, }; use http::Request; @@ -207,23 +207,9 @@ where _marker: PhantomData, } .layer(MapRequestLayer::new(move |mut req: Request<_>| { - // TODO(david): this is duplicated in `axum/src/handler/into_extension_service.rs` - // extract into helper function - let State(outer_state) = req - .extensions() - .get::>() - .unwrap_or_else(|| { - panic!( - "no state of type `{}` was found. Please file an issue", - std::any::type_name::>() - ) - }) - .clone(); - + let outer_state = extract_state_assume_present::(&req); let inner_state = f(outer_state); - req.extensions_mut().insert(State(inner_state)); - req })) } @@ -234,7 +220,6 @@ where B: HttpBody + Send + 'static, S: Clone, { - /// TODO(david): docs pub fn with_state(state: S) -> Self { Router::new().state(state) } @@ -244,7 +229,6 @@ impl Router<(), WithState, B> where B: HttpBody + Send + 'static, { - /// TODO(david): docs pub fn without_state() -> Self { Router::with_state(()) } @@ -262,7 +246,7 @@ where path: &str, // TODO(david): constrain this so it only accepts methods // routers containing handlers - method_router: MethodRouter, + method_router: MethodRouter, ) -> Self { validate_path_for_route(path); @@ -301,7 +285,6 @@ where } } - /// TODO(david): docs pub fn route_service(mut self, path: &str, service: T) -> Self where T: Service, Response = Response, Error = Infallible> + Clone + Send + 'static, @@ -530,8 +513,9 @@ where } } + // TODO(david): update docs #[doc = include_str!("../docs/routing/fallback.md")] - pub fn fallback(mut self, handler: H) -> Self + pub fn fallback(self, handler: H) -> Self where H: Handler, T: 'static, @@ -540,7 +524,6 @@ where self.fallback_service(IntoExtensionService::new(handler)) } - /// TODO(david): docs pub fn fallback_service(mut self, svc: T) -> Self where T: Service, Response = Response, Error = Infallible> + Clone + Send + 'static, @@ -786,7 +769,7 @@ impl Fallback { } enum Endpoint { - MethodRouter(MethodRouter), + MethodRouter(MethodRouter), Route(Route), } diff --git a/axum/src/util.rs b/axum/src/util.rs index b494159c..8e3bd604 100644 --- a/axum/src/util.rs +++ b/axum/src/util.rs @@ -1,6 +1,9 @@ +use http::Request; use pin_project_lite::pin_project; use std::{ops::Deref, sync::Arc}; +use crate::extract::State; + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) struct PercentDecodedStr(Arc); @@ -54,6 +57,27 @@ where } } +/// Extract the state from request extensions and panic if its not there. +/// +/// This should only be called after `Router::call` or `MethodRouter::call` have been called. +pub(crate) fn extract_state_assume_present(req: &Request) -> S +where + S: Clone + Send + Sync + 'static, +{ + let State(state) = req + .extensions() + .get::>() + .unwrap_or_else(|| { + panic!( + "no state of type `{}` was found. Please file an issue", + std::any::type_name::>() + ) + }) + .clone(); + + state +} + #[test] fn test_try_downcast() { assert_eq!(try_downcast::(5_u32), Err(5_u32));