diff --git a/axum-core/src/extract/mod.rs b/axum-core/src/extract/mod.rs index a1406f50..f63bab5b 100644 --- a/axum-core/src/extract/mod.rs +++ b/axum-core/src/extract/mod.rs @@ -272,3 +272,19 @@ where Ok(T::from_request(req).await) } } + +/// TODO(david): docs +#[derive(Clone, Copy, Debug, Default)] +pub struct State(pub S); + +#[async_trait] +impl FromRequest for State +where + B: Send, +{ + type Rejection = Infallible; + + async fn from_request(req: &mut RequestParts) -> Result { + todo!() + } +} diff --git a/axum-extra/src/extract/cookie/mod.rs b/axum-extra/src/extract/cookie/mod.rs index 7bda1ea3..86a841cb 100644 --- a/axum-extra/src/extract/cookie/mod.rs +++ b/axum-extra/src/extract/cookie/mod.rs @@ -226,12 +226,13 @@ mod tests { jar.remove(Cookie::named("key")) } - let app = Router::::new() + let app = Router::<_, Body, _>::new() .route("/set", get(set_cookie)) .route("/get", get(get_cookie)) .route("/remove", get(remove_cookie)) .layer(Extension(Key::generate())) - .layer(Extension(CustomKey(Key::generate()))); + .layer(Extension(CustomKey(Key::generate()))) + .state(()); let res = app .clone() @@ -294,9 +295,10 @@ mod tests { format!("{:?}", jar.get("key")) } - let app = Router::::new() + let app = Router::<_, Body, _>::new() .route("/get", get(get_cookie)) - .layer(Extension(Key::generate())); + .layer(Extension(Key::generate())) + .state(()); let res = app .clone() diff --git a/axum-extra/src/extract/form.rs b/axum-extra/src/extract/form.rs index bcd8c180..238f409f 100644 --- a/axum-extra/src/extract/form.rs +++ b/axum-extra/src/extract/form.rs @@ -116,10 +116,12 @@ mod tests { values: Vec, } - let app = Router::new().route( - "/", - post(|Form(data): Form| async move { data.values.join(",") }), - ); + let app = Router::new() + .route( + "/", + post(|Form(data): Form| async move { data.values.join(",") }), + ) + .state(()); let client = TestClient::new(app); diff --git a/axum-extra/src/extract/query.rs b/axum-extra/src/extract/query.rs index dcbcdb31..8638e8ad 100644 --- a/axum-extra/src/extract/query.rs +++ b/axum-extra/src/extract/query.rs @@ -97,10 +97,12 @@ mod tests { values: Vec, } - let app = Router::new().route( - "/", - post(|Query(data): Query| async move { data.values.join(",") }), - ); + let app = Router::new() + .route( + "/", + post(|Query(data): Query| async move { data.values.join(",") }), + ) + .state(()); let client = TestClient::new(app); diff --git a/axum-extra/src/json_lines.rs b/axum-extra/src/json_lines.rs index 1bc8d1a0..f704f581 100644 --- a/axum-extra/src/json_lines.rs +++ b/axum-extra/src/json_lines.rs @@ -217,22 +217,24 @@ mod tests { #[tokio::test] async fn extractor() { - let app = Router::new().route( - "/", - post(|mut stream: JsonLines| async move { - assert_eq!(stream.next().await.unwrap().unwrap(), User { id: 1 }); - assert_eq!(stream.next().await.unwrap().unwrap(), User { id: 2 }); - assert_eq!(stream.next().await.unwrap().unwrap(), User { id: 3 }); + let app = Router::new() + .route( + "/", + post(|mut stream: JsonLines| async move { + assert_eq!(stream.next().await.unwrap().unwrap(), User { id: 1 }); + assert_eq!(stream.next().await.unwrap().unwrap(), User { id: 2 }); + assert_eq!(stream.next().await.unwrap().unwrap(), User { id: 3 }); - // sources are downcastable to `serde_json::Error` - let err = stream.next().await.unwrap().unwrap_err(); - let _: &serde_json::Error = err - .source() - .unwrap() - .downcast_ref::() - .unwrap(); - }), - ); + // sources are downcastable to `serde_json::Error` + let err = stream.next().await.unwrap().unwrap_err(); + let _: &serde_json::Error = err + .source() + .unwrap() + .downcast_ref::() + .unwrap(); + }), + ) + .state(()); let client = TestClient::new(app); @@ -255,17 +257,19 @@ mod tests { #[tokio::test] async fn response() { - let app = Router::new().route( - "/", - get(|| async { - let values = futures_util::stream::iter(vec![ - Ok::<_, Infallible>(User { id: 1 }), - Ok::<_, Infallible>(User { id: 2 }), - Ok::<_, Infallible>(User { id: 3 }), - ]); - JsonLines::new(values) - }), - ); + let app = Router::new() + .route( + "/", + get(|| async { + let values = futures_util::stream::iter(vec![ + Ok::<_, Infallible>(User { id: 1 }), + Ok::<_, Infallible>(User { id: 2 }), + Ok::<_, Infallible>(User { id: 3 }), + ]); + JsonLines::new(values) + }), + ) + .state(()); let client = TestClient::new(app); diff --git a/axum-extra/src/routing/mod.rs b/axum-extra/src/routing/mod.rs index 6298a36e..e78f3276 100644 --- a/axum-extra/src/routing/mod.rs +++ b/axum-extra/src/routing/mod.rs @@ -29,7 +29,7 @@ pub use self::typed::{FirstElementIs, TypedPath}; pub use self::spa::SpaRouter; /// Extension trait that adds additional methods to [`Router`]. -pub trait RouterExt: sealed::Sealed { +pub trait RouterExt: sealed::Sealed { /// Add a typed `GET` route to the router. /// /// The path will be inferred from the first argument to the handler function which must @@ -166,9 +166,11 @@ pub trait RouterExt: sealed::Sealed { Self: Sized; } -impl RouterExt for Router +impl RouterExt for Router where B: axum::body::HttpBody + Send + 'static, + R: 'static, + S: 'static, { #[cfg(feature = "typed-routing")] fn typed_get(self, handler: H) -> Self @@ -276,7 +278,7 @@ where mod sealed { pub trait Sealed {} - impl Sealed for axum::Router {} + impl Sealed for axum::Router {} } #[cfg(test)] @@ -289,7 +291,8 @@ mod tests { async fn test_tsr() { let app = Router::new() .route_with_tsr("/foo", get(|| async {})) - .route_with_tsr("/bar/", get(|| async {})); + .route_with_tsr("/bar/", get(|| async {})) + .state(()); let client = TestClient::new(app); diff --git a/axum-extra/src/routing/resource.rs b/axum-extra/src/routing/resource.rs index 720c0407..d67baa71 100644 --- a/axum-extra/src/routing/resource.rs +++ b/axum-extra/src/routing/resource.rs @@ -3,10 +3,10 @@ use axum::{ handler::Handler, http::Request, response::Response, - routing::{delete, get, on, post, MethodFilter}, + routing::{delete, get, on, post, MethodFilter, MissingState, WithState}, Router, }; -use std::convert::Infallible; +use std::{convert::Infallible, fmt}; use tower_service::Service; /// A resource which defines a set of conventional CRUD routes. @@ -47,13 +47,24 @@ use tower_service::Service; /// let app = Router::new().merge(users); /// # let _: Router = app; /// ``` -#[derive(Debug)] -pub struct Resource { +pub struct Resource { pub(crate) name: String, - pub(crate) router: Router, + pub(crate) router: Router, } -impl Resource +impl fmt::Debug for Resource +where + S: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Resource") + .field("name", &self.name) + .field("router", &self.router) + .finish() + } +} + +impl Resource where B: axum::body::HttpBody + Send + 'static, { @@ -67,6 +78,24 @@ where } } + /// TODO(david): docs + pub fn state(self, state: S) -> Resource + where + S: Clone, + { + Resource { + name: self.name, + router: self.router.state(state), + } + } +} + +impl Resource +where + B: axum::body::HttpBody + Send + 'static, + S: 'static, + R: 'static, +{ /// Add a handler at `GET /{resource_name}`. pub fn index(self, handler: H) -> Self where @@ -140,7 +169,7 @@ where /// Nest another router at the "member level". /// /// The routes will be nested at `/{resource_name}/:{resource_name}_id`. - pub fn nest(mut self, router: Router) -> Self { + pub fn nest(mut self, router: Router) -> Self { let path = self.show_update_destroy_path(); self.router = self.router.nest(&path, router); self @@ -149,7 +178,7 @@ where /// Nest another router at the "collection level". /// /// The routes will be nested at `/{resource_name}`. - pub fn nest_collection(mut self, router: Router) -> Self { + pub fn nest_collection(mut self, router: Router) -> Self { let path = self.index_create_path(); self.router = self.router.nest(&path, router); self @@ -173,8 +202,8 @@ where } } -impl From> for Router { - fn from(resource: Resource) -> Self { +impl From> for Router { + fn from(resource: Resource) -> Self { resource.router } } @@ -204,7 +233,7 @@ mod tests { Router::new().route("/featured", get(|| async move { "users#featured" })), ); - let mut app = Router::new().merge(users); + let mut app = Router::new().merge(users).state(()); assert_eq!( call_route(&mut app, Method::GET, "/users").await, @@ -257,7 +286,11 @@ mod tests { ); } - async fn call_route(app: &mut Router, method: Method, uri: &str) -> String { + async fn call_route( + app: &mut Router<(), Body, axum::routing::WithState>, + method: Method, + uri: &str, + ) -> String { let res = app .ready() .await diff --git a/axum-extra/src/routing/spa.rs b/axum-extra/src/routing/spa.rs index f8e3d0b0..68a50c5c 100644 --- a/axum-extra/src/routing/spa.rs +++ b/axum-extra/src/routing/spa.rs @@ -2,7 +2,7 @@ use axum::{ body::{Body, HttpBody}, error_handling::HandleError, response::Response, - routing::{get_service, Route}, + routing::{get_service, MissingState, Route}, Router, }; use http::{Request, StatusCode}; @@ -147,7 +147,7 @@ impl SpaRouter { } } -impl From> for Router +impl From> for Router where F: Clone + Send + 'static, HandleError, F, T>: @@ -155,6 +155,7 @@ where , F, T> as Service>>::Future: Send, B: HttpBody + Send + 'static, T: 'static, + S: 'static, { fn from(spa: SpaRouter) -> Self { let assets_service = get_service(ServeDir::new(&spa.paths.assets_dir)) @@ -214,7 +215,8 @@ mod tests { async fn basic() { let app = Router::new() .route("/foo", get(|| async { "GET /foo" })) - .merge(SpaRouter::new("/assets", "test_files")); + .merge(SpaRouter::new("/assets", "test_files")) + .state(()); let client = TestClient::new(app); let res = client.get("/").send().await; @@ -239,8 +241,9 @@ mod tests { #[tokio::test] async fn setting_index_file() { - let app = - Router::new().merge(SpaRouter::new("/assets", "test_files").index_file("index_2.html")); + let app = Router::new() + .merge(SpaRouter::new("/assets", "test_files").index_file("index_2.html")) + .state(()); let client = TestClient::new(app); let res = client.get("/").send().await; @@ -264,6 +267,6 @@ mod tests { let spa = SpaRouter::new("/assets", "test_files").handle_error(handle_error); - Router::::new().merge(spa); + Router::<(), Body, _>::new().merge(spa); } } diff --git a/axum/src/extract/connect_info.rs b/axum/src/extract/connect_info.rs index 8363a25e..100efe68 100644 --- a/axum/src/extract/connect_info.rs +++ b/axum/src/extract/connect_info.rs @@ -158,7 +158,7 @@ mod tests { let (tx, rx) = tokio::sync::oneshot::channel(); tokio::spawn(async move { - let app = Router::new().route("/", get(handler)); + let app = Router::new().route("/", get(handler)).state(()); let server = Server::from_tcp(listener) .unwrap() .serve(app.into_make_service_with_connect_info::()); @@ -198,7 +198,7 @@ mod tests { let (tx, rx) = tokio::sync::oneshot::channel(); tokio::spawn(async move { - let app = Router::new().route("/", get(handler)); + let app = Router::new().route("/", get(handler)).state(()); let server = Server::from_tcp(listener) .unwrap() .serve(app.into_make_service_with_connect_info::()); diff --git a/axum/src/extract/content_length_limit.rs b/axum/src/extract/content_length_limit.rs index 62148a8e..ffd55285 100644 --- a/axum/src/extract/content_length_limit.rs +++ b/axum/src/extract/content_length_limit.rs @@ -115,10 +115,12 @@ mod tests { const LIMIT: u64 = 8; - let app = Router::new().route( - "/", - post(|_body: ContentLengthLimit| async {}), - ); + let app = Router::new() + .route( + "/", + post(|_body: ContentLengthLimit| async {}), + ) + .state(()); let client = TestClient::new(app); let res = client @@ -154,7 +156,9 @@ mod tests { #[tokio::test] async fn get_request_without_content_length_is_accepted() { - let app = Router::new().route("/", get(|_body: ContentLengthLimit| async {})); + let app = Router::new() + .route("/", get(|_body: ContentLengthLimit| async {})) + .state(()); let client = TestClient::new(app); @@ -164,7 +168,9 @@ mod tests { #[tokio::test] async fn get_request_with_content_length_is_rejected() { - let app = Router::new().route("/", get(|_body: ContentLengthLimit| async {})); + let app = Router::new() + .route("/", get(|_body: ContentLengthLimit| async {})) + .state(()); let client = TestClient::new(app); @@ -179,7 +185,9 @@ mod tests { #[tokio::test] async fn get_request_with_chunked_encoding_is_rejected() { - let app = Router::new().route("/", get(|_body: ContentLengthLimit| async {})); + let app = Router::new() + .route("/", get(|_body: ContentLengthLimit| async {})) + .state(()); let client = TestClient::new(app); diff --git a/axum/src/extract/host.rs b/axum/src/extract/host.rs index 85ab8a9d..9e126239 100644 --- a/axum/src/extract/host.rs +++ b/axum/src/extract/host.rs @@ -84,7 +84,7 @@ mod tests { host } - TestClient::new(Router::new().route("/", get(host_as_body))) + TestClient::new(Router::new().route("/", get(host_as_body)).state(())) } #[tokio::test] diff --git a/axum/src/extract/matched_path.rs b/axum/src/extract/matched_path.rs index 192983cd..cdc04863 100644 --- a/axum/src/extract/matched_path.rs +++ b/axum/src/extract/matched_path.rs @@ -150,7 +150,8 @@ mod tests { Router::new().route("/assets/*path", get(handler)), ) .nest_service("/foo", handler.into_service()) - .layer(tower::layer::layer_fn(SetMatchedPathExtension)); + .layer(tower::layer::layer_fn(SetMatchedPathExtension)) + .state(()); let client = TestClient::new(app); @@ -181,13 +182,17 @@ mod tests { #[tokio::test] async fn nested_opaque_routers_append_to_matched_path() { - let app = Router::new().nest_service( - "/:a", - Router::new().route( - "/:b", - get(|path: MatchedPath| async move { path.as_str().to_owned() }), - ), - ); + let app = Router::new() + .nest_service( + "/:a", + Router::new() + .route( + "/:b", + get(|path: MatchedPath| async move { path.as_str().to_owned() }), + ) + .state(()), + ) + .state(()); let client = TestClient::new(app); diff --git a/axum/src/extract/mod.rs b/axum/src/extract/mod.rs index 6f0435f6..70289d00 100644 --- a/axum/src/extract/mod.rs +++ b/axum/src/extract/mod.rs @@ -16,7 +16,7 @@ mod raw_query; mod request_parts; #[doc(inline)] -pub use axum_core::extract::{FromRequest, RequestParts}; +pub use axum_core::extract::{FromRequest, RequestParts, State}; #[doc(inline)] #[allow(deprecated)] @@ -103,7 +103,9 @@ mod tests { #[tokio::test] async fn consume_body() { - let app = Router::new().route("/", get(|body: String| async { body })); + let app = Router::new() + .route("/", get(|body: String| async { body })) + .state(()); let client = TestClient::new(app); let res = client.get("/").body("foo").send().await; diff --git a/axum/src/extract/multipart.rs b/axum/src/extract/multipart.rs index 8f58455f..9391145a 100644 --- a/axum/src/extract/multipart.rs +++ b/axum/src/extract/multipart.rs @@ -257,7 +257,7 @@ mod tests { assert!(multipart.next_field().await.unwrap().is_none()); } - let app = Router::new().route("/", post(handle)); + let app = Router::new().route("/", post(handle)).state(()); let client = TestClient::new(app); diff --git a/axum/src/extract/path/mod.rs b/axum/src/extract/path/mod.rs index 20a23ce7..c6ae6577 100644 --- a/axum/src/extract/path/mod.rs +++ b/axum/src/extract/path/mod.rs @@ -418,15 +418,17 @@ mod tests { #[tokio::test] async fn extracting_url_params() { - let app = Router::new().route( - "/users/:id", - get(|Path(id): Path| async move { - assert_eq!(id, 42); - }) - .post(|Path(params_map): Path>| async move { - assert_eq!(params_map.get("id").unwrap(), &1337); - }), - ); + let app = Router::new() + .route( + "/users/:id", + get(|Path(id): Path| async move { + assert_eq!(id, 42); + }) + .post(|Path(params_map): Path>| async move { + assert_eq!(params_map.get("id").unwrap(), &1337); + }), + ) + .state(()); let client = TestClient::new(app); @@ -439,7 +441,9 @@ mod tests { #[tokio::test] async fn extracting_url_params_multiple_times() { - let app = Router::new().route("/users/:id", get(|_: Path, _: Path| async {})); + let app = Router::new() + .route("/users/:id", get(|_: Path, _: Path| async {})) + .state(()); let client = TestClient::new(app); @@ -449,10 +453,12 @@ mod tests { #[tokio::test] async fn percent_decoding() { - let app = Router::new().route( - "/:key", - get(|Path(param): Path| async move { param }), - ); + let app = Router::new() + .route( + "/:key", + get(|Path(param): Path| async move { param }), + ) + .state(()); let client = TestClient::new(app); @@ -471,7 +477,8 @@ mod tests { .route( "/u/:key", get(|Path(param): Path| async move { param.to_string() }), - ); + ) + .state(()); let client = TestClient::new(app); @@ -494,7 +501,8 @@ mod tests { get(|Path(params): Path>| async move { params.get("rest").unwrap().clone() }), - ); + ) + .state(()); let client = TestClient::new(app); @@ -507,7 +515,7 @@ mod tests { #[tokio::test] async fn captures_dont_match_empty_segments() { - let app = Router::new().route("/:key", get(|| async {})); + let app = Router::new().route("/:key", get(|| async {})).state(()); let client = TestClient::new(app); @@ -520,7 +528,9 @@ mod tests { #[tokio::test] async fn when_extensions_are_missing() { - let app = Router::new().route("/:key", get(|_: Request, _: Path| async {})); + let app = Router::new() + .route("/:key", get(|_: Request, _: Path| async {})) + .state(()); let client = TestClient::new(app); @@ -545,7 +555,9 @@ mod tests { } } - let app = Router::new().route("/:key", get(|param: Path| async move { param.0 .0 })); + let app = Router::new() + .route("/:key", get(|param: Path| async move { param.0 .0 })) + .state(()); let client = TestClient::new(app); @@ -559,7 +571,9 @@ mod tests { #[tokio::test] async fn two_path_extractors() { - let app = Router::new().route("/:a/:b", get(|_: Path, _: Path| async {})); + let app = Router::new() + .route("/:a/:b", get(|_: Path, _: Path| async {})) + .state(()); let client = TestClient::new(app); @@ -574,18 +588,20 @@ mod tests { #[tokio::test] async fn deserialize_into_vec_of_tuples() { - let app = Router::new().route( - "/:a/:b", - get(|Path(params): Path>| async move { - assert_eq!( - params, - vec![ - ("a".to_owned(), "foo".to_owned()), - ("b".to_owned(), "bar".to_owned()) - ] - ); - }), - ); + let app = Router::new() + .route( + "/:a/:b", + get(|Path(params): Path>| async move { + assert_eq!( + params, + vec![ + ("a".to_owned(), "foo".to_owned()), + ("b".to_owned(), "bar".to_owned()) + ] + ); + }), + ) + .state(()); let client = TestClient::new(app); diff --git a/axum/src/extract/request_parts.rs b/axum/src/extract/request_parts.rs index 02b044c5..4dd8a150 100644 --- a/axum/src/extract/request_parts.rs +++ b/axum/src/extract/request_parts.rs @@ -223,7 +223,7 @@ mod tests { async fn multiple_request_extractors() { async fn handler(_: Request, _: Request) {} - let app = Router::new().route("/", post(handler)); + let app = Router::new().route("/", post(handler)).state(()); let client = TestClient::new(app); @@ -248,7 +248,12 @@ mod tests { parts.extensions.get::().unwrap(); } - let client = TestClient::new(Router::new().route("/", get(handler)).layer(Extension(Ext))); + let client = TestClient::new( + Router::new() + .route("/", get(handler)) + .layer(Extension(Ext)) + .state(()), + ); let res = client.get("/").header("x-foo", "123").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -263,7 +268,7 @@ mod tests { assert_eq!(body, "foo"); } - let client = TestClient::new(Router::new().route("/", get(handler))); + let client = TestClient::new(Router::new().route("/", get(handler)).state(())); let res = client.get("/").body("foo").send().await; assert_eq!(res.status(), StatusCode::OK); diff --git a/axum/src/json.rs b/axum/src/json.rs index e1d068fa..8c03c4c0 100644 --- a/axum/src/json.rs +++ b/axum/src/json.rs @@ -221,7 +221,9 @@ mod tests { foo: String, } - let app = Router::new().route("/", post(|input: Json| async { input.0.foo })); + let app = Router::new() + .route("/", post(|input: Json| async { input.0.foo })) + .state(()); let client = TestClient::new(app); let res = client.post("/").json(&json!({ "foo": "bar" })).send().await; @@ -237,7 +239,9 @@ mod tests { foo: String, } - let app = Router::new().route("/", post(|input: Json| async { input.0.foo })); + let app = Router::new() + .route("/", post(|input: Json| async { input.0.foo })) + .state(()); let client = TestClient::new(app); let res = client.post("/").body(r#"{ "foo": "bar" }"#).send().await; @@ -253,7 +257,9 @@ mod tests { async fn valid_json_content_type(content_type: &str) -> bool { println!("testing {:?}", content_type); - let app = Router::new().route("/", post(|Json(_): Json| async {})); + let app = Router::new() + .route("/", post(|Json(_): Json| async {})) + .state(()); let res = TestClient::new(app) .post("/") @@ -274,7 +280,9 @@ mod tests { #[tokio::test] async fn invalid_json_syntax() { - let app = Router::new().route("/", post(|_: Json| async {})); + let app = Router::new() + .route("/", post(|_: Json| async {})) + .state(()); let client = TestClient::new(app); let res = client diff --git a/axum/src/middleware/from_extractor.rs b/axum/src/middleware/from_extractor.rs index 45bb9513..bef813f3 100644 --- a/axum/src/middleware/from_extractor.rs +++ b/axum/src/middleware/from_extractor.rs @@ -302,7 +302,8 @@ mod tests { async fn handler() {} - let app = Router::new().route("/", get(handler.layer(from_extractor::()))); + let app = + Router::with_state(()).route("/", get(handler.layer(from_extractor::()))); let client = TestClient::new(app); diff --git a/axum/src/middleware/from_fn.rs b/axum/src/middleware/from_fn.rs index 9f9563b5..fc386c50 100644 --- a/axum/src/middleware/from_fn.rs +++ b/axum/src/middleware/from_fn.rs @@ -296,7 +296,7 @@ mod tests { (&headers["x-axum-test"]).to_str().unwrap().to_owned() } - let app = Router::new() + let app = Router::with_state(()) .route("/", get(handle)) .layer(from_fn(insert_header)); diff --git a/axum/src/response/mod.rs b/axum/src/response/mod.rs index 695b931c..9655ddc8 100644 --- a/axum/src/response/mod.rs +++ b/axum/src/response/mod.rs @@ -93,7 +93,7 @@ mod tests { } } - Router::::new() + Router::<(), Body, _>::new() .route("/", get(impl_trait_ok)) .route("/", get(impl_trait_err)) .route("/", get(impl_trait_both)) @@ -203,7 +203,7 @@ mod tests { ) } - Router::::new() + Router::<(), Body, _>::new() .route("/", get(status)) .route("/", get(status_headermap)) .route("/", get(status_header_array)) diff --git a/axum/src/response/sse.rs b/axum/src/response/sse.rs index 9013d4f7..bf1e813e 100644 --- a/axum/src/response/sse.rs +++ b/axum/src/response/sse.rs @@ -508,7 +508,7 @@ mod tests { #[tokio::test] async fn basic() { - let app = Router::new().route( + let app = Router::with_state(()).route( "/", get(|| async { let stream = stream::iter(vec![ @@ -553,7 +553,7 @@ mod tests { async fn keep_alive() { const DELAY: Duration = Duration::from_secs(5); - let app = Router::new().route( + let app = Router::with_state(()).route( "/", get(|| async { let stream = stream::repeat_with(|| Event::default().data("msg")) @@ -589,7 +589,7 @@ mod tests { async fn keep_alive_ends_when_the_stream_ends() { const DELAY: Duration = Duration::from_secs(5); - let app = Router::new().route( + let app = Router::with_state(()).route( "/", get(|| async { let stream = stream::repeat_with(|| Event::default().data("msg")) diff --git a/axum/src/routing/method_routing.rs b/axum/src/routing/method_routing.rs index 4edb8bdb..3ac71594 100644 --- a/axum/src/routing/method_routing.rs +++ b/axum/src/routing/method_routing.rs @@ -1193,26 +1193,28 @@ mod tests { #[allow(dead_code)] fn buiding_complex_router() { - let app = crate::Router::new().route( - "/", - // use the all the things :bomb: - get(ok) - .post(ok) - .route_layer(RequireAuthorizationLayer::bearer("password")) - .merge( - delete_service(ServeDir::new(".")) - .handle_error(|_| async { StatusCode::NOT_FOUND }), - ) - .fallback((|| async { StatusCode::NOT_FOUND }).into_service()) - .put(ok) - .layer( - ServiceBuilder::new() - .layer(HandleErrorLayer::new(|_| async { - StatusCode::REQUEST_TIMEOUT - })) - .layer(TimeoutLayer::new(Duration::from_secs(10))), - ), - ); + let app = crate::Router::new() + .route( + "/", + // use the all the things :bomb: + get(ok) + .post(ok) + .route_layer(RequireAuthorizationLayer::bearer("password")) + .merge( + delete_service(ServeDir::new(".")) + .handle_error(|_| async { StatusCode::NOT_FOUND }), + ) + .fallback((|| async { StatusCode::NOT_FOUND }).into_service()) + .put(ok) + .layer( + ServiceBuilder::new() + .layer(HandleErrorLayer::new(|_| async { + StatusCode::REQUEST_TIMEOUT + })) + .layer(TimeoutLayer::new(Duration::from_secs(10))), + ), + ) + .state(()); crate::Server::bind(&"0.0.0.0:0".parse().unwrap()).serve(app.into_make_service()); } diff --git a/axum/src/routing/mod.rs b/axum/src/routing/mod.rs index 4c0a07b5..b7417231 100644 --- a/axum/src/routing/mod.rs +++ b/axum/src/routing/mod.rs @@ -16,6 +16,7 @@ use std::{ collections::HashMap, convert::Infallible, fmt, + marker::PhantomData, sync::Arc, task::{Context, Poll}, }; @@ -62,23 +63,33 @@ impl RouteId { } /// The router type for composing handlers and services. -pub struct Router { +pub struct Router { + // Invariant: If `R == MissingState` then `state` is `None` + // If `R == WithState` then state is `Some` + // `R` cannot have other values + state: Option, routes: HashMap>, node: Arc, fallback: Fallback, + _marker: PhantomData, } -impl Clone for Router { +impl Clone for Router +where + S: Clone, +{ fn clone(&self) -> Self { Self { + state: self.state.clone(), routes: self.routes.clone(), node: Arc::clone(&self.node), fallback: self.fallback.clone(), + _marker: PhantomData, } } } -impl Default for Router +impl Default for Router where B: HttpBody + Send + 'static, { @@ -87,12 +98,23 @@ where } } -impl fmt::Debug for Router { +impl fmt::Debug for Router +where + S: fmt::Debug, +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + state, + routes, + node, + fallback, + _marker, + } = self; f.debug_struct("Router") - .field("routes", &self.routes) - .field("node", &self.node) - .field("fallback", &self.fallback) + .field("state", &state) + .field("routes", &routes) + .field("node", &node) + .field("fallback", &fallback) .finish() } } @@ -100,7 +122,7 @@ impl fmt::Debug for Router { pub(crate) const NEST_TAIL_PARAM: &str = "__private__axum_nest_tail_param"; const NEST_TAIL_PARAM_CAPTURE: &str = "/*__private__axum_nest_tail_param"; -impl Router +impl Router where B: HttpBody + Send + 'static, { @@ -110,12 +132,42 @@ where /// all requests. pub fn new() -> Self { Self { + state: None, routes: Default::default(), node: Default::default(), fallback: Fallback::Default(Route::new(NotFound)), + _marker: PhantomData, } } + /// TODO(david): docs + pub fn state(self, state: S) -> Router { + Router { + state: Some(state), + routes: self.routes, + node: self.node, + fallback: self.fallback, + _marker: PhantomData, + } + } +} + +impl Router +where + B: HttpBody + Send + 'static, +{ + /// TODO(david): docs + pub fn with_state(state: S) -> Self { + Router::new().state(state) + } +} + +impl Router +where + B: HttpBody + Send + 'static, + S: 'static, + R: 'static, +{ #[doc = include_str!("../docs/routing/route.md")] pub fn route(mut self, path: &str, service: T) -> Self where @@ -128,7 +180,10 @@ where panic!("Paths must start with a `/`"); } - let service = match try_downcast::, _>(service) { + // Downcase to `WithState` rather than `R` because `Router` only implements + // `Service` if `R == WithState` so any other type of `R` cannot be passed to `.router` in + // the first place + let service = match try_downcast::, _>(service) { Ok(_) => { panic!("Invalid route: `Router::route` cannot be used with `Router`s. Use `Router::nest` instead") } @@ -171,7 +226,7 @@ where } #[doc = include_str!("../docs/routing/nest.md")] - pub fn nest(mut self, mut path: &str, router: Router) -> Self { + pub fn nest(mut self, mut path: &str, router: Router) -> Self { if path.is_empty() { // nesting at `""` and `"/"` should mean the same thing path = "/"; @@ -184,11 +239,15 @@ where let prefix = path; let Router { + state, mut routes, node, fallback, + _marker: _, } = router; + debug_assert!(state.is_none()); + if let Fallback::Custom(_) = fallback { panic!("Cannot nest `Router`s that has a fallback"); } @@ -255,16 +314,20 @@ where } #[doc = include_str!("../docs/routing/merge.md")] - pub fn merge(mut self, other: R) -> Self + pub fn merge(mut self, other: R2) -> Self where - R: Into>, + R2: Into>, { let Router { + state, routes, node, fallback, + _marker: _, } = other.into(); + debug_assert!(state.is_none()); + for (id, route) in routes { let path = node .route_id_to_path @@ -289,7 +352,7 @@ where } #[doc = include_str!("../docs/routing/layer.md")] - pub fn layer(self, layer: L) -> Router + pub fn layer(self, layer: L) -> Router where L: Layer>, L::Service: @@ -322,9 +385,11 @@ where let fallback = self.fallback.map(|svc| Route::new(layer.layer(svc))); Router { + state: self.state, routes, node: self.node, fallback, + _marker: self._marker, } } @@ -359,9 +424,11 @@ where .collect(); Router { + state: self.state, routes, node: self.node, fallback: self.fallback, + _marker: self._marker, } } @@ -406,7 +473,13 @@ where pub fn into_make_service_with_connect_info(self) -> IntoMakeServiceWithConnectInfo { IntoMakeServiceWithConnectInfo::new(self) } +} +impl Router +where + B: HttpBody + Send + 'static, + S: Clone + Send + Sync + 'static, +{ #[inline] fn call_route( &self, @@ -442,6 +515,10 @@ where url_params::insert_url_params(req.extensions_mut(), match_.params); + // the `unwrap` is safe because `self.state` is always some if `R = WithState`, which it is + req.extensions_mut() + .insert(crate::extract::State(self.state.as_ref().unwrap().clone())); + let mut route = self .routes .get(&id) @@ -455,9 +532,10 @@ where } } -impl Service> for Router +impl Service> for Router where B: HttpBody + Send + 'static, + S: Clone + Send + Sync + 'static, { type Response = Response; type Error = Infallible; @@ -496,6 +574,12 @@ where } } +#[derive(Copy, Clone, Debug)] +pub enum MissingState {} + +#[derive(Copy, Clone, Debug)] +pub enum WithState {} + /// Wrapper around `matchit::Router` that supports merging two `Router`s. #[derive(Clone, Default)] struct Node { @@ -599,5 +683,6 @@ impl fmt::Debug for Endpoint { #[allow(warnings)] fn traits() { use crate::test_helpers::*; - assert_send::>(); + assert_send::>(); + assert_send::>(); } diff --git a/axum/src/routing/tests/fallback.rs b/axum/src/routing/tests/fallback.rs index 49874077..bd25e43e 100644 --- a/axum/src/routing/tests/fallback.rs +++ b/axum/src/routing/tests/fallback.rs @@ -5,7 +5,8 @@ use crate::handler::Handler; async fn basic() { let app = Router::new() .route("/foo", get(|| async {})) - .fallback((|| async { "fallback" }).into_service()); + .fallback((|| async { "fallback" }).into_service()) + .state(()); let client = TestClient::new(app); @@ -20,7 +21,8 @@ async fn basic() { async fn nest() { let app = Router::new() .nest("/foo", Router::new().route("/bar", get(|| async {}))) - .fallback((|| async { "fallback" }).into_service()); + .fallback((|| async { "fallback" }).into_service()) + .state(()); let client = TestClient::new(app); @@ -38,7 +40,8 @@ async fn or() { let app = one .merge(two) - .fallback((|| async { "fallback" }).into_service()); + .fallback((|| async { "fallback" }).into_service()) + .state(()); let client = TestClient::new(app); diff --git a/axum/src/routing/tests/get_to_head.rs b/axum/src/routing/tests/get_to_head.rs index 21888e6e..a82f21c2 100644 --- a/axum/src/routing/tests/get_to_head.rs +++ b/axum/src/routing/tests/get_to_head.rs @@ -8,14 +8,16 @@ mod for_handlers { #[tokio::test] async fn get_handles_head() { - let app = Router::new().route( - "/", - get(|| async { - let mut headers = HeaderMap::new(); - headers.insert("x-some-header", "foobar".parse().unwrap()); - (headers, "you shouldn't see this") - }), - ); + let app = Router::new() + .route( + "/", + get(|| async { + let mut headers = HeaderMap::new(); + headers.insert("x-some-header", "foobar".parse().unwrap()); + (headers, "you shouldn't see this") + }), + ) + .state(()); // don't use reqwest because it always strips bodies from HEAD responses let res = app @@ -43,14 +45,16 @@ mod for_services { #[tokio::test] async fn get_handles_head() { - let app = Router::new().route( - "/", - get_service(service_fn(|_req: Request| async move { - Ok::<_, Infallible>( - ([("x-some-header", "foobar")], "you shouldn't see this").into_response(), - ) - })), - ); + let app = Router::new() + .route( + "/", + get_service(service_fn(|_req: Request| async move { + Ok::<_, Infallible>( + ([("x-some-header", "foobar")], "you shouldn't see this").into_response(), + ) + })), + ) + .state(()); // don't use reqwest because it always strips bodies from HEAD responses let res = app diff --git a/axum/src/routing/tests/handle_error.rs b/axum/src/routing/tests/handle_error.rs index 378e2bef..b47bcc25 100644 --- a/axum/src/routing/tests/handle_error.rs +++ b/axum/src/routing/tests/handle_error.rs @@ -31,7 +31,7 @@ impl Service for Svc { #[tokio::test] async fn handler() { - let app = Router::new().route( + let app = Router::with_state(()).route( "/", get(forever.layer( ServiceBuilder::new() @@ -50,7 +50,7 @@ async fn handler() { #[tokio::test] async fn handler_multiple_methods_first() { - let app = Router::new().route( + let app = Router::with_state(()).route( "/", get(forever.layer( ServiceBuilder::new() @@ -70,7 +70,7 @@ async fn handler_multiple_methods_first() { #[tokio::test] async fn handler_multiple_methods_middle() { - let app = Router::new().route( + let app = Router::with_state(()).route( "/", delete(unit) .get( @@ -93,7 +93,7 @@ async fn handler_multiple_methods_middle() { #[tokio::test] async fn handler_multiple_methods_last() { - let app = Router::new().route( + let app = Router::with_state(()).route( "/", delete(unit).get( forever.layer( diff --git a/axum/src/routing/tests/merge.rs b/axum/src/routing/tests/merge.rs index 58af0151..c5eb4dab 100644 --- a/axum/src/routing/tests/merge.rs +++ b/axum/src/routing/tests/merge.rs @@ -12,7 +12,7 @@ async fn basic() { .route("/foo", get(|| async {})) .route("/bar", get(|| async {})); let two = Router::new().route("/baz", get(|| async {})); - let app = one.merge(two); + let app = one.merge(two).state(()); let client = TestClient::new(app); @@ -41,7 +41,8 @@ async fn multiple_ors_balanced_differently() { one.clone() .merge(two.clone()) .merge(three.clone()) - .merge(four.clone()), + .merge(four.clone()) + .state(()), ) .await; @@ -49,18 +50,20 @@ async fn multiple_ors_balanced_differently() { "two", one.clone() .merge(two.clone()) - .merge(three.clone().merge(four.clone())), + .merge(three.clone().merge(four.clone())) + .state(()), ) .await; test( "three", one.clone() - .merge(two.clone().merge(three.clone()).merge(four.clone())), + .merge(two.clone().merge(three.clone()).merge(four.clone())) + .state(()), ) .await; - test("four", one.merge(two.merge(three.merge(four)))).await; + test("four", one.merge(two.merge(three.merge(four))).state(())).await; async fn test(name: &str, app: S) where @@ -89,11 +92,11 @@ async fn nested_or() { let bar_or_baz = bar.merge(baz); - let client = TestClient::new(bar_or_baz.clone()); + let client = TestClient::new(bar_or_baz.clone().state(())); assert_eq!(client.get("/bar").send().await.text().await, "bar"); assert_eq!(client.get("/baz").send().await.text().await, "baz"); - let client = TestClient::new(Router::new().nest("/foo", bar_or_baz)); + let client = TestClient::new(Router::new().nest("/foo", bar_or_baz).state(())); assert_eq!(client.get("/foo/bar").send().await.text().await, "bar"); assert_eq!(client.get("/foo/baz").send().await.text().await, "baz"); } @@ -102,7 +105,10 @@ async fn nested_or() { async fn or_with_route_following() { let one = Router::new().route("/one", get(|| async { "one" })); let two = Router::new().route("/two", get(|| async { "two" })); - let app = one.merge(two).route("/three", get(|| async { "three" })); + let app = one + .merge(two) + .route("/three", get(|| async { "three" })) + .state(()); let client = TestClient::new(app); @@ -122,7 +128,7 @@ async fn layer() { let two = Router::new() .route("/bar", get(|| async {})) .layer(ConcurrencyLimitLayer::new(10)); - let app = one.merge(two); + let app = one.merge(two).state(()); let client = TestClient::new(app); @@ -145,7 +151,7 @@ async fn layer_and_handle_error() { })) .layer(TimeoutLayer::new(Duration::from_millis(10))), ); - let app = one.merge(two); + let app = one.merge(two).state(()); let client = TestClient::new(app); @@ -159,7 +165,7 @@ async fn nesting() { let two = Router::new().nest("/bar", Router::new().route("/baz", get(|| async {}))); let app = one.merge(two); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/bar/baz").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -171,7 +177,7 @@ async fn boxed() { let two = Router::new().route("/bar", get(|| async {})); let app = one.merge(two); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/bar").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -188,7 +194,7 @@ async fn many_ors() { .merge(Router::new().route("/r6", get(|| async {}))) .merge(Router::new().route("/r7", get(|| async {}))); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); for n in 1..=7 { let res = client.get(&format!("/r{}", n)).send().await; @@ -217,7 +223,7 @@ async fn services() { })), )); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/foo").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -243,7 +249,7 @@ async fn nesting_and_seeing_the_right_uri() { let one = Router::new().nest("/foo", Router::new().route("/bar", get(all_the_uris))); let two = Router::new().route("/foo", get(all_the_uris)); - let client = TestClient::new(one.merge(two)); + let client = TestClient::new(one.merge(two).state(())); let res = client.get("/foo/bar").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -276,7 +282,7 @@ async fn nesting_and_seeing_the_right_uri_at_more_levels_of_nesting() { ); let two = Router::new().route("/foo", get(all_the_uris)); - let client = TestClient::new(one.merge(two)); + let client = TestClient::new(one.merge(two).state(())); let res = client.get("/foo/bar/baz").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -310,7 +316,7 @@ async fn nesting_and_seeing_the_right_uri_ors_with_nesting() { let two = Router::new().nest("/two", Router::new().route("/qux", get(all_the_uris))); let three = Router::new().route("/three", get(all_the_uris)); - let client = TestClient::new(one.merge(two).merge(three)); + let client = TestClient::new(one.merge(two).merge(three).state(())); let res = client.get("/one/bar/baz").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -354,7 +360,7 @@ async fn nesting_and_seeing_the_right_uri_ors_with_multi_segment_uris() { ); let two = Router::new().route("/two/foo", get(all_the_uris)); - let client = TestClient::new(one.merge(two)); + let client = TestClient::new(one.merge(two).state(())); let res = client.get("/one/foo/bar").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -387,7 +393,7 @@ async fn middleware_that_return_early() { let public = Router::new().route("/public", get(|| async {})); - let client = TestClient::new(private.merge(public)); + let client = TestClient::new(private.merge(public).state(())); assert_eq!( client.get("/").send().await.status(), diff --git a/axum/src/routing/tests/mod.rs b/axum/src/routing/tests/mod.rs index ab61ea49..f644c2d7 100644 --- a/axum/src/routing/tests/mod.rs +++ b/axum/src/routing/tests/mod.rs @@ -46,7 +46,7 @@ async fn hello_world() { .route("/", get(root).post(foo)) .route("/users", post(users_create)); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/").send().await; let body = res.text().await; @@ -75,7 +75,7 @@ async fn routing() { get(|_: Request| async { "users#action" }), ); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/").send().await; assert_eq!(res.status(), StatusCode::NOT_FOUND); @@ -99,7 +99,7 @@ async fn routing() { #[tokio::test] async fn router_type_doesnt_change() { - let app: Router = Router::new() + let app: Router<()> = Router::new() .route( "/", on(MethodFilter::GET, |_: Request| async { @@ -111,7 +111,7 @@ async fn router_type_doesnt_change() { ) .layer(tower_http::compression::CompressionLayer::new()); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -149,7 +149,7 @@ async fn routing_between_services() { ) .route("/two", on_service(MethodFilter::GET, handle.into_service())); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/one").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -187,7 +187,7 @@ async fn middleware_on_single_route() { )), ); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/").send().await; let body = res.text().await; @@ -203,7 +203,7 @@ async fn service_in_bottom() { let app = Router::new().route("/", get_service(service_fn(handler))); - TestClient::new(app); + TestClient::new(app.state(())); } #[tokio::test] @@ -212,7 +212,7 @@ async fn wrong_method_handler() { .route("/", get(|| async {}).post(|| async {})) .route("/foo", patch(|| async {})); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.patch("/").send().await; assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED); @@ -250,7 +250,7 @@ async fn wrong_method_service() { .route("/", get_service(Svc).post_service(Svc)) .route("/foo", patch_service(Svc)); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.patch("/").send().await; assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED); @@ -273,7 +273,7 @@ async fn multiple_methods_for_one_handler() { let app = Router::new().route("/", on(MethodFilter::GET | MethodFilter::POST, root)); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -286,7 +286,7 @@ async fn multiple_methods_for_one_handler() { async fn wildcard_sees_whole_url() { let app = Router::new().route("/api/*rest", get(|uri: Uri| async move { uri.to_string() })); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/api/foo/bar").send().await; assert_eq!(res.text().await, "/api/foo/bar"); @@ -305,7 +305,7 @@ async fn middleware_applies_to_routes_above() { ) .route("/two", get(|| async {})); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/one").send().await; assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT); @@ -318,7 +318,7 @@ async fn middleware_applies_to_routes_above() { async fn not_found_for_extra_trailing_slash() { let app = Router::new().route("/foo", get(|| async {})); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/foo/").send().await; assert_eq!(res.status(), StatusCode::NOT_FOUND); @@ -331,7 +331,7 @@ async fn not_found_for_extra_trailing_slash() { async fn not_found_for_missing_trailing_slash() { let app = Router::new().route("/foo/", get(|| async {})); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/foo").send().await; assert_eq!(res.status(), StatusCode::NOT_FOUND); @@ -343,7 +343,7 @@ async fn with_and_without_trailing_slash() { .route("/foo", get(|| async { "without tsr" })) .route("/foo/", get(|| async { "with tsr" })); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/foo/").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -362,7 +362,7 @@ async fn wildcard_doesnt_match_just_trailing_slash() { get(|Path(path): Path| async move { path }), ); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/x").send().await; assert_eq!(res.status(), StatusCode::NOT_FOUND); @@ -384,7 +384,7 @@ async fn static_and_dynamic_paths() { ) .route("/foo", get(|| async { "static" })); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/bar").send().await; assert_eq!(res.text().await, "dynamic: bar"); @@ -397,7 +397,7 @@ async fn static_and_dynamic_paths() { #[should_panic(expected = "Paths must start with a `/`. Use \"/\" for root routes")] async fn empty_route() { let app = Router::new().route("", get(|| async {})); - TestClient::new(app); + TestClient::new(app.state(())); } #[tokio::test] @@ -429,7 +429,7 @@ async fn middleware_still_run_for_unmatched_requests() { .route("/", get(|| async {})) .layer(tower::layer::layer_fn(CountMiddleware)); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); assert_eq!(COUNT.load(Ordering::SeqCst), 0); @@ -445,7 +445,7 @@ async fn middleware_still_run_for_unmatched_requests() { expected = "Invalid route: `Router::route` cannot be used with `Router`s. Use `Router::nest` instead" )] async fn routing_to_router_panics() { - TestClient::new(Router::new().route("/", Router::new())); + TestClient::new(Router::new().route("/", Router::new().state(())).state(())); } #[tokio::test] @@ -454,7 +454,7 @@ async fn route_layer() { .route("/foo", get(|| async {})) .route_layer(RequireAuthorizationLayer::bearer("password")); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client .get("/foo") @@ -482,7 +482,7 @@ async fn different_methods_added_in_different_routes() { .route("/", get(|| async { "GET" })) .route("/", post(|| async { "POST" })); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/").send().await; let body = res.text().await; @@ -505,7 +505,7 @@ async fn different_methods_added_in_different_routes_deeply_nested() { ), ); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/foo/bar/baz").send().await; let body = res.text().await; @@ -522,7 +522,7 @@ async fn merging_routers_with_fallbacks_panics() { async fn fallback() {} let one = Router::new().fallback(fallback.into_service()); let two = Router::new().fallback(fallback.into_service()); - TestClient::new(one.merge(two)); + TestClient::new(one.merge(two).state(())); } #[tokio::test] @@ -531,7 +531,7 @@ async fn nesting_router_with_fallbacks_panics() { async fn fallback() {} let one = Router::new().fallback(fallback.into_service()); let app = Router::new().nest("/", one); - TestClient::new(app); + TestClient::new(app.state(())); } #[tokio::test] @@ -539,7 +539,7 @@ async fn merging_routers_with_same_paths_but_different_methods() { let one = Router::new().route("/", get(|| async { "GET" })); let two = Router::new().route("/", post(|| async { "POST" })); - let client = TestClient::new(one.merge(two)); + let client = TestClient::new(one.merge(two).state(())); let res = client.get("/").send().await; let body = res.text().await; @@ -556,7 +556,7 @@ async fn head_content_length_through_hyper_server() { .route("/", get(|| async { "foo" })) .route("/json", get(|| async { Json(json!({ "foo": 1 })) })); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.head("/").send().await; assert_eq!(res.headers()["content-length"], "3"); @@ -571,7 +571,7 @@ async fn head_content_length_through_hyper_server() { async fn head_content_length_through_hyper_server_that_hits_fallback() { let app = Router::new().fallback((|| async { "foo" }).into_service()); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.head("/").send().await; assert_eq!(res.headers()["content-length"], "3"); @@ -585,7 +585,7 @@ async fn head_with_middleware_applied() { .route("/", get(|| async { "Hello, World!" })) .layer(CompressionLayer::new().compress_when(SizeAbove::new(0))); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); // send GET request let res = client @@ -614,7 +614,7 @@ async fn head_with_middleware_applied() { #[should_panic(expected = "Paths must start with a `/`")] async fn routes_must_start_with_slash() { let app = Router::new().route(":foo", get(|| async {})); - TestClient::new(app); + TestClient::new(app.state(())); } #[tokio::test] @@ -630,7 +630,7 @@ async fn limited_body_with_content_length() { ) .layer(RequestBodyLimitLayer::new(LIMIT)); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.post("/").body("a".repeat(LIMIT)).send().await; assert_eq!(res.status(), StatusCode::OK); @@ -652,7 +652,7 @@ async fn limited_body_with_streaming_body() { ) .layer(RequestBodyLimitLayer::new(LIMIT)); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let stream = futures_util::stream::iter(vec![Ok::<_, hyper::Error>("a".repeat(LIMIT))]); let res = client diff --git a/axum/src/routing/tests/nest.rs b/axum/src/routing/tests/nest.rs index 7113d2b7..7819b620 100644 --- a/axum/src/routing/tests/nest.rs +++ b/axum/src/routing/tests/nest.rs @@ -40,7 +40,7 @@ async fn nesting_apps() { .route("/", get(|| async { "hi" })) .nest("/:version/api", api_routes); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -64,7 +64,7 @@ async fn wrong_method_nest() { let nested_app = Router::new().route("/", get(|| async {})); let app = Router::new().nest("/", nested_app); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -81,7 +81,7 @@ async fn nesting_router_at_root() { let nested = Router::new().route("/foo", get(|uri: Uri| async move { uri.to_string() })); let app = Router::new().nest("/", nested); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/").send().await; assert_eq!(res.status(), StatusCode::NOT_FOUND); @@ -99,7 +99,7 @@ async fn nesting_router_at_empty_path() { let nested = Router::new().route("/foo", get(|uri: Uri| async move { uri.to_string() })); let app = Router::new().nest("", nested); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/").send().await; assert_eq!(res.status(), StatusCode::NOT_FOUND); @@ -116,7 +116,7 @@ async fn nesting_router_at_empty_path() { async fn nesting_handler_at_root() { let app = Router::new().nest_service("/", get(|uri: Uri| async move { uri.to_string() })); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -146,7 +146,7 @@ async fn nested_url_extractor() { ), ); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/foo/bar/baz").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -170,7 +170,7 @@ async fn nested_url_original_extractor() { ), ); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/foo/bar/baz").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -193,7 +193,7 @@ async fn nested_service_sees_stripped_uri() { ), ); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/foo/bar/baz").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -212,7 +212,7 @@ async fn nest_static_file_server() { }), ); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/static/README.md").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -229,7 +229,7 @@ async fn nested_multiple_routes() { ) .route("/", get(|| async { "root" })); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); assert_eq!(client.get("/").send().await.text().await, "root"); assert_eq!(client.get("/api/users").send().await.text().await, "users"); @@ -245,7 +245,7 @@ async fn nested_with_other_route_also_matching_with_route_first() { .route("/teams", get(|| async { "teams" })), ); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); assert_eq!(client.get("/api").send().await.text().await, "api"); assert_eq!(client.get("/api/users").send().await.text().await, "users"); @@ -263,7 +263,7 @@ async fn nested_with_other_route_also_matching_with_route_last() { ) .route("/api", get(|| async { "api" })); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); assert_eq!(client.get("/api").send().await.text().await, "api"); assert_eq!(client.get("/api/users").send().await.text().await, "users"); @@ -282,7 +282,7 @@ async fn multiple_top_level_nests() { Router::new().route("/route", get(|| async { "two" })), ); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); assert_eq!(client.get("/one/route").send().await.text().await, "one"); assert_eq!(client.get("/two/route").send().await.text().await, "two"); @@ -291,7 +291,7 @@ async fn multiple_top_level_nests() { #[tokio::test] #[should_panic(expected = "Invalid route: nested routes cannot contain wildcards (*)")] async fn nest_cannot_contain_wildcards() { - Router::::new().nest("/one/*rest", Router::new()); + Router::<(), Body, _>::new().nest("/one/*rest", Router::new()); } #[tokio::test] @@ -333,7 +333,7 @@ async fn outer_middleware_still_see_whole_url() { .fallback(handler.into_service()) .layer(tower::layer::layer_fn(SetUriExtension)); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); assert_eq!(client.get("/").send().await.text().await, "/"); assert_eq!(client.get("/foo").send().await.text().await, "/foo"); @@ -352,11 +352,12 @@ async fn nest_at_capture() { "/:b", get(|Path((a, b)): Path<(String, String)>| async move { format!("a={} b={}", a, b) }), ) + .state(()) .boxed_clone(); let app = Router::new().nest_service("/:a", api_routes); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/foo/bar").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -367,7 +368,7 @@ async fn nest_at_capture() { async fn nest_with_and_without_trailing() { let app = Router::new().nest_service("/foo", get(|| async {})); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get("/foo").send().await; assert_eq!(res.status(), StatusCode::OK); @@ -393,14 +394,14 @@ macro_rules! nested_route_test { async fn $name() { let inner = Router::new().route($route_path, get(|| async {})); let app = Router::new().nest($nested_path, inner); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get($expected_path).send().await; let status = res.status(); assert_eq!(status, StatusCode::OK, "Router"); - let inner = Router::new().route($route_path, get(|| async {})); + let inner = Router::new().route($route_path, get(|| async {})).state(()); let app = Router::new().nest_service($nested_path, inner); - let client = TestClient::new(app); + let client = TestClient::new(app.state(())); let res = client.get(dbg!($expected_path)).send().await; assert_eq!(res.status(), StatusCode::OK, "opaque"); } diff --git a/axum/src/typed_header.rs b/axum/src/typed_header.rs index 1ce72d04..d3df7b72 100644 --- a/axum/src/typed_header.rs +++ b/axum/src/typed_header.rs @@ -177,7 +177,7 @@ mod tests { user_agent.to_string() } - let app = Router::new().route("/", get(handle)); + let app = Router::new().route("/", get(handle)).state(()); let client = TestClient::new(app);