diff --git a/CHANGELOG.md b/CHANGELOG.md index d236d9a6..f62dd2d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,20 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased +- **fixed:** All known compile time issues are resolved, including those with + `boxed` and those introduced by Rust 1.56 ([#404]) - Big internal refactoring of routing leading to several improvements ([#363]) - - Wildcard routes like `.route("/api/users/*rest", service)` are now supported. - - The order routes are added in no longer matters. - - Adding a conflicting route will now cause a panic instead of silently making + - **added:** Wildcard routes like `.route("/api/users/*rest", service)` are now supported. + - **fixed:** The order routes are added in no longer matters. + - **fixed:** Adding a conflicting route will now cause a panic instead of silently making a route unreachable. - - Route matching is faster as number of routes increase. + - **fixed:** Route matching is faster as number of routes increase. - **breaking:** The routes `/foo` and `/:key` are considered to overlap and will cause a panic when constructing the router. This might be fixed in the future. -- Improve performance of `BoxRoute` ([#339]) -- Expand accepted content types for JSON requests ([#378]) +- **fixed:** Expand accepted content types for JSON requests ([#378]) +- **breaking:** The router's type is now always `Router` regardless of how many routes or + middleware are applies ([#404]) + + This means router types are all always nameable: + + ```rust + fn my_routes() -> Router { + Router::new().route( + "/users", + post(|| async { "Hello, World!" }), + ) + } + ``` +- **breaking:** `Route::boxed` and `BoxRoute` have been removed as they're no longer + necessary ([#404]) +- **breaking:** `Route`, `Nested`, `Or` types are now private. They no longer had to be + public because `Router` is internally boxed ([#404]) - **breaking:** Automatically do percent decoding in `extract::Path` ([#272]) -- **breaking:** `Router::boxed` now the inner service to implement `Clone` and - `Sync` in addition to the previous trait bounds ([#339]) - **breaking:** Added feature flags for HTTP1 and JSON. This enables removing a few dependencies if your app only uses HTTP2 or doesn't use JSON. Its only a breaking change if you depend on axum with `default_features = false`. ([#286]) @@ -103,6 +119,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#363]: https://github.com/tokio-rs/axum/pull/363 [#396]: https://github.com/tokio-rs/axum/pull/396 [#402]: https://github.com/tokio-rs/axum/pull/402 +[#404]: https://github.com/tokio-rs/axum/pull/404 # 0.2.8 (07. October, 2021) diff --git a/Cargo.toml b/Cargo.toml index 6c7bfa97..6a6aec92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,6 @@ ws = ["tokio-tungstenite", "sha-1", "base64"] async-trait = "0.1.43" bitflags = "1.0" bytes = "1.0" -dyn-clone = "1.0" futures-util = { version = "0.3", default-features = false, features = ["alloc"] } http = "0.2" http-body = "0.4.3" diff --git a/examples/key-value-store/src/main.rs b/examples/key-value-store/src/main.rs index c768b441..c0ddba48 100644 --- a/examples/key-value-store/src/main.rs +++ b/examples/key-value-store/src/main.rs @@ -13,7 +13,6 @@ use axum::{ handler::{delete, get, Handler}, http::StatusCode, response::IntoResponse, - routing::BoxRoute, Router, }; use std::{ @@ -108,7 +107,7 @@ async fn list_keys(Extension(state): Extension) -> String { .join("\n") } -fn admin_routes() -> Router { +fn admin_routes() -> Router { async fn delete_all_keys(Extension(state): Extension) { state.write().unwrap().db.clear(); } @@ -122,7 +121,6 @@ fn admin_routes() -> Router { .route("/key/:key", delete(remove_key)) // Require bearer auth for all admin routes .layer(RequireAuthorizationLayer::bearer("secret-token")) - .boxed() } fn handle_error(error: BoxError) -> impl IntoResponse { diff --git a/examples/testing/src/main.rs b/examples/testing/src/main.rs index 28e63533..54eddb58 100644 --- a/examples/testing/src/main.rs +++ b/examples/testing/src/main.rs @@ -6,7 +6,6 @@ use axum::{ handler::{get, post}, - routing::BoxRoute, Json, Router, }; use tower_http::trace::TraceLayer; @@ -32,7 +31,7 @@ async fn main() { /// Having a function that produces our app makes it easy to call it from tests /// without having to create an HTTP server. #[allow(dead_code)] -fn app() -> Router { +fn app() -> Router { Router::new() .route("/", get(|| async { "Hello, World!" })) .route( @@ -43,7 +42,6 @@ fn app() -> Router { ) // We can still add middleware .layer(TraceLayer::new_for_http()) - .boxed() } #[cfg(test)] @@ -59,7 +57,7 @@ mod tests { async fn hello_world() { let app = app(); - // `BoxRoute` implements `tower::Service>` so we can + // `Router` implements `tower::Service>` so we can // call it like any tower service, no need to run an HTTP server. let response = app .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) diff --git a/examples/tls-rustls/src/main.rs b/examples/tls-rustls/src/main.rs index 461d9bbb..0f7511be 100644 --- a/examples/tls-rustls/src/main.rs +++ b/examples/tls-rustls/src/main.rs @@ -4,26 +4,31 @@ //! cargo run -p example-tls-rustls //! ``` -use axum::{handler::get, Router}; +// NOTE: This example is currently broken since axum-server requires `S: Sync`, +// that isn't necessary and will be fixed in a future release -#[tokio::main] -async fn main() { - // Set the RUST_LOG, if it hasn't been explicitly defined - if std::env::var_os("RUST_LOG").is_none() { - std::env::set_var("RUST_LOG", "example_tls_rustls=debug") - } - tracing_subscriber::fmt::init(); +fn main() {} - let app = Router::new().route("/", get(handler)); +// use axum::{handler::get, Router}; - axum_server::bind_rustls("127.0.0.1:3000") - .private_key_file("examples/tls-rustls/self_signed_certs/key.pem") - .certificate_file("examples/tls-rustls/self_signed_certs/cert.pem") - .serve(app) - .await - .unwrap(); -} +// #[tokio::main] +// async fn main() { +// // Set the RUST_LOG, if it hasn't been explicitly defined +// if std::env::var_os("RUST_LOG").is_none() { +// std::env::set_var("RUST_LOG", "example_tls_rustls=debug") +// } +// tracing_subscriber::fmt::init(); -async fn handler() -> &'static str { - "Hello, World!" -} +// // let app = Router::new().route("/", get(handler)); + +// // axum_server::bind_rustls("127.0.0.1:3000") +// // .private_key_file("examples/tls-rustls/self_signed_certs/key.pem") +// // .certificate_file("examples/tls-rustls/self_signed_certs/cert.pem") +// // .serve(app) +// // .await +// // .unwrap(); +// } + +// async fn handler() -> &'static str { +// "Hello, World!" +// } diff --git a/src/clone_box_service.rs b/src/clone_box_service.rs index 3642d4b1..b659feec 100644 --- a/src/clone_box_service.rs +++ b/src/clone_box_service.rs @@ -3,19 +3,18 @@ use std::task::{Context, Poll}; use tower::ServiceExt; use tower_service::Service; -/// A `Clone + Send + Sync` boxed `Service` +/// A `Clone + Send` boxed `Service` pub(crate) struct CloneBoxService( Box< dyn CloneService>> - + Send - + Sync, + + Send, >, ); impl CloneBoxService { pub(crate) fn new(inner: S) -> Self where - S: Service + Clone + Send + Sync + 'static, + S: Service + Clone + Send + 'static, S::Future: Send + 'static, { let inner = inner.map_future(|f| Box::pin(f) as _); @@ -48,22 +47,18 @@ trait CloneService: Service { &self, ) -> Box< dyn CloneService - + Send - + Sync, + + Send, >; } impl CloneService for T where - T: Service + Send + Sync + Clone + 'static, + T: Service + Send + Clone + 'static, { fn clone_box( &self, - ) -> Box< - dyn CloneService - + Send - + Sync, - > { + ) -> Box + Send> + { Box::new(self.clone()) } } diff --git a/src/lib.rs b/src/lib.rs index d83aa0bf..19d84c98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -360,15 +360,13 @@ //! http::Request, //! handler::get, //! Router, -//! routing::BoxRoute //! }; //! use tower_http::services::ServeFile; //! use http::Response; //! -//! fn api_routes() -> Router { +//! fn api_routes() -> Router { //! Router::new() //! .route("/users", get(|_: Request| async { /* ... */ })) -//! .boxed() //! } //! //! let app = Router::new() diff --git a/src/routing/future.rs b/src/routing/future.rs index 3be347f3..4c755cb6 100644 --- a/src/routing/future.rs +++ b/src/routing/future.rs @@ -2,14 +2,12 @@ use crate::{ body::BoxBody, - clone_box_service::CloneBoxService, routing::{FromEmptyRouter, UriStack}, }; use http::{Request, Response}; use pin_project_lite::pin_project; use std::{ convert::Infallible, - fmt, future::Future, pin::Pin, task::{Context, Poll}, @@ -17,43 +15,22 @@ use std::{ use tower::util::Oneshot; use tower_service::Service; -pub use super::or::ResponseFuture as OrResponseFuture; - opaque_future! { /// Response future for [`EmptyRouter`](super::EmptyRouter). pub type EmptyRouterFuture = std::future::Ready, E>>; } -pin_project! { - /// The response future for [`BoxRoute`](super::BoxRoute). - pub struct BoxRouteFuture { - #[pin] - pub(super) inner: Oneshot< - CloneBoxService, Response, Infallible>, - Request, - >, - } -} - -impl Future for BoxRouteFuture { - type Output = Result, Infallible>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.project().inner.poll(cx) - } -} - -impl fmt::Debug for BoxRouteFuture { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("BoxRouteFuture").finish() - } +opaque_future! { + /// Response future for [`Routes`](super::Routes). + pub type RoutesFuture = + futures_util::future::BoxFuture<'static, Result, Infallible>>; } pin_project! { /// The response future for [`Route`](super::Route). #[derive(Debug)] - pub struct RouteFuture + pub(crate) struct RouteFuture where S: Service>, F: Service> @@ -119,7 +96,7 @@ where pin_project! { /// The response future for [`Nested`](super::Nested). #[derive(Debug)] - pub struct NestedFuture + pub(crate) struct NestedFuture where S: Service>, F: Service> diff --git a/src/routing/mod.rs b/src/routing/mod.rs index 912c70a5..c489b234 100644 --- a/src/routing/mod.rs +++ b/src/routing/mod.rs @@ -1,8 +1,8 @@ //! Routing between [`Service`]s. -use self::future::{BoxRouteFuture, EmptyRouterFuture, NestedFuture, RouteFuture}; +use self::future::{EmptyRouterFuture, NestedFuture, RouteFuture, RoutesFuture}; use crate::{ - body::{box_body, BoxBody}, + body::{box_body, Body, BoxBody}, clone_box_service::CloneBoxService, extract::{ connect_info::{Connected, IntoMakeServiceWithConnectInfo}, @@ -22,8 +22,8 @@ use std::{ marker::PhantomData, task::{Context, Poll}, }; -use tower::{util::ServiceExt, ServiceBuilder}; -use tower_http::map_response_body::MapResponseBodyLayer; +use tower::util::ServiceExt; +use tower_http::map_response_body::MapResponseBody; use tower_layer::Layer; use tower_service::Service; @@ -32,7 +32,7 @@ pub mod future; mod method_filter; mod or; -pub use self::{method_filter::MethodFilter, or::Or}; +pub use self::method_filter::MethodFilter; #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct RouteId(u64); @@ -46,35 +46,30 @@ impl RouteId { } /// The router type for composing handlers and services. -#[derive(Clone)] -pub struct Router { - routes: S, +pub struct Router { + routes: Routes, node: Node, } -impl Router { - /// Create a new `Router`. - /// - /// Unless you add additional routes this will respond to `404 Not Found` to - /// all requests. - pub fn new() -> Self { +impl Clone for Router { + fn clone(&self) -> Self { Self { - routes: EmptyRouter::not_found(), - node: Node::new(), + routes: self.routes.clone(), + node: self.node.clone(), } } } -impl Default for Router { +impl Default for Router +where + B: Send + Sync + 'static, +{ fn default() -> Self { Self::new() } } -impl fmt::Debug for Router -where - S: fmt::Debug, -{ +impl fmt::Debug for Router { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Router") .field("routes", &self.routes) @@ -84,7 +79,21 @@ where const NEST_TAIL_PARAM: &str = "__axum_nest"; -impl Router { +impl Router +where + B: Send + Sync + 'static, +{ + /// Create a new `Router`. + /// + /// Unless you add additional routes this will respond to `404 Not Found` to + /// all requests. + pub fn new() -> Self { + Self { + routes: Routes(CloneBoxService::new(EmptyRouter::not_found())), + node: Node::new(), + } + } + /// Add another route to the router. /// /// `path` is a string of path segments separated by `/`. Each segment @@ -164,9 +173,13 @@ impl Router { /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # }; /// ``` - pub fn route(mut self, path: &str, svc: T) -> Router> + pub fn route(mut self, path: &str, svc: T) -> Self where - T: Service, Error = Infallible>, + T: Service, Response = Response, Error = Infallible> + + Clone + + Send + + 'static, + T::Future: Send + 'static, { let id = RouteId::next(); @@ -175,11 +188,11 @@ impl Router { } Router { - routes: Route { + routes: Routes(CloneBoxService::new(Route { id, svc, fallback: self.routes, - }, + })), node: self.node, } } @@ -274,10 +287,6 @@ impl Router { /// # }; /// ``` /// - /// If necessary you can use [`Router::boxed`] to box a group of routes - /// making the type easier to name. This is sometimes useful when working with - /// `nest`. - /// /// # Wildcard routes /// /// Nested routes are similar to wildcard routes. The difference is that @@ -305,9 +314,13 @@ impl Router { /// for more details. /// /// [`OriginalUri`]: crate::extract::OriginalUri - pub fn nest(mut self, path: &str, svc: T) -> Router> + pub fn nest(mut self, path: &str, svc: T) -> Self where - T: Service, Error = Infallible>, + T: Service, Response = Response, Error = Infallible> + + Clone + + Send + + 'static, + T::Future: Send + 'static, { let id = RouteId::next(); @@ -326,64 +339,15 @@ impl Router { } Router { - routes: Nested { + routes: Routes(CloneBoxService::new(Nested { id, svc, fallback: self.routes, - }, + })), node: self.node, } } - /// Create a boxed route trait object. - /// - /// This makes it easier to name the types of routers to, for example, - /// return them from functions: - /// - /// ```rust - /// use axum::{ - /// body::Body, - /// handler::get, - /// Router, - /// routing::BoxRoute - /// }; - /// - /// async fn first_handler() { /* ... */ } - /// - /// async fn second_handler() { /* ... */ } - /// - /// async fn third_handler() { /* ... */ } - /// - /// fn app() -> Router { - /// Router::new() - /// .route("/", get(first_handler).post(second_handler)) - /// .route("/foo", get(third_handler)) - /// .boxed() - /// } - /// ``` - /// - /// It also helps with compile times when you have a very large number of - /// routes. - pub fn boxed(self) -> Router> - where - S: Service, Response = Response, Error = Infallible> - + Clone - + Send - + Sync - + 'static, - S::Future: Send, - ReqBody: Send + 'static, - ResBody: http_body::Body + Send + Sync + 'static, - ResBody::Error: Into, - { - self.layer( - ServiceBuilder::new() - .layer_fn(BoxRoute) - .layer_fn(CloneBoxService::new) - .layer(MapResponseBodyLayer::new(box_body)), - ) - } - /// Apply a [`tower::Layer`] to the router. /// /// All requests to the router will be processed by the layer's @@ -451,11 +415,21 @@ impl Router { /// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # }; /// ``` - pub fn layer(self, layer: L) -> Router + pub fn layer(self, layer: L) -> Router where - L: Layer, + L: Layer>, + L::Service: Service< + Request, + Response = Response, + Error = Infallible, + > + Clone + + Send + + 'static, + >>::Future: Send + 'static, + LayeredResBody: http_body::Body + Send + Sync + 'static, + LayeredResBody::Error: Into, { - self.map(|svc| layer.layer(svc)) + self.map(|svc| MapResponseBody::new(layer.layer(svc), box_body)) } /// Convert this router into a [`MakeService`], that is a [`Service`] who's @@ -481,10 +455,7 @@ impl Router { /// ``` /// /// [`MakeService`]: tower::make::MakeService - pub fn into_make_service(self) -> IntoMakeService - where - S: Clone, - { + pub fn into_make_service(self) -> IntoMakeService { IntoMakeService::new(self) } @@ -572,7 +543,6 @@ impl Router { self, ) -> IntoMakeServiceWithConnectInfo where - S: Clone, C: Connected, { IntoMakeServiceWithConnectInfo::new(self) @@ -606,35 +576,43 @@ impl Router { /// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap(); /// # }; /// ``` - pub fn or(self, other: T) -> Router> + pub fn or(self, other: T) -> Self where - T: Service, Error = Infallible>, + T: Service, Response = Response, Error = Infallible> + + Clone + + Send + + 'static, + T::Future: Send + 'static, { - self.map(|first| Or { + self.map(|first| or::Or { first, second: other, }) } - fn map(self, f: F) -> Router + fn map(self, f: F) -> Router where - F: FnOnce(S) -> T, + F: FnOnce(Routes) -> T, + T: Service, Response = Response, Error = Infallible> + + Clone + + Send + + 'static, + T::Future: Send + 'static, { Router { - routes: f(self.routes), + routes: Routes(CloneBoxService::new(f(self.routes))), node: self.node, } } } -impl Service> for Router +impl Service> for Router where - S: Service, Response = Response, Error = Infallible> + Clone, - ReqBody: Send + Sync + 'static, + B: Send + Sync + 'static, { - type Response = Response; + type Response = Response; type Error = Infallible; - type Future = S::Future; + type Future = RoutesFuture; #[inline] fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { @@ -642,7 +620,7 @@ where } #[inline] - fn call(&mut self, mut req: Request) -> Self::Future { + fn call(&mut self, mut req: Request) -> Self::Future { if req.extensions().get::().is_none() { let original_uri = OriginalUri(req.uri().clone()); req.extensions_mut().insert(original_uri); @@ -827,10 +805,8 @@ struct FromEmptyRouter { request: Request, } -/// A route that sends requests to one of two [`Service`]s depending on the -/// path. #[derive(Debug, Clone)] -pub struct Route { +struct Route { id: RouteId, svc: S, fallback: T, @@ -869,7 +845,7 @@ where /// /// Created with [`Router::nest`]. #[derive(Debug, Clone)] -pub struct Nested { +struct Nested { id: RouteId, svc: S, fallback: T, @@ -906,43 +882,6 @@ where } } -/// A boxed route trait object. -/// -/// See [`Router::boxed`] for more details. -pub struct BoxRoute( - CloneBoxService, Response, Infallible>, -); - -impl Clone for BoxRoute { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl fmt::Debug for BoxRoute { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("BoxRoute").finish() - } -} - -impl Service> for BoxRoute { - type Response = Response; - type Error = Infallible; - type Future = BoxRouteFuture; - - #[inline] - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - #[inline] - fn call(&mut self, req: Request) -> Self::Future { - BoxRouteFuture { - inner: self.0.clone().oneshot(req), - } - } -} - fn with_path(uri: &Uri, new_path: &str) -> Uri { let path_and_query = if let Some(path_and_query) = uri.path_and_query() { let new_path = if new_path.starts_with('/') { @@ -1005,6 +944,41 @@ where } } +/// How routes are stored inside a [`Router`]. +/// +/// You normally shouldn't need to care about this type. +pub struct Routes(CloneBoxService, Response, Infallible>); + +impl Clone for Routes { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl fmt::Debug for Routes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Router").finish() + } +} + +impl Service> for Routes { + type Response = Response; + type Error = Infallible; + type Future = future::RoutesFuture; + + #[inline] + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.0.poll_ready(cx) + } + + #[inline] + fn call(&mut self, req: Request) -> Self::Future { + future::RoutesFuture { + future: self.0.call(req), + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -1014,7 +988,6 @@ mod tests { use crate::tests::*; assert_send::>(); - assert_sync::>(); assert_send::>(); assert_sync::>(); @@ -1022,9 +995,6 @@ mod tests { assert_send::>(); assert_sync::>(); - assert_send::>(); - assert_sync::>(); - assert_send::>(); assert_sync::>(); diff --git a/src/routing/or.rs b/src/routing/or.rs index 4c985e95..7bf61538 100644 --- a/src/routing/or.rs +++ b/src/routing/or.rs @@ -20,7 +20,7 @@ use tower_service::Service; /// /// [`Router::or`]: super::Router::or #[derive(Debug, Clone, Copy)] -pub struct Or { +pub(crate) struct Or { pub(super) first: A, pub(super) second: B, } @@ -62,7 +62,7 @@ where pin_project! { /// Response future for [`Or`]. - pub struct ResponseFuture + pub(crate) struct ResponseFuture where A: Service>, B: Service>, diff --git a/src/tests/mod.rs b/src/tests/mod.rs index a859d5fa..30ad64c5 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -25,8 +25,8 @@ use std::{ task::{Context, Poll}, time::Duration, }; -use tower::service_fn; use tower::timeout::TimeoutLayer; +use tower::{service_fn, ServiceBuilder}; use tower_service::Service; pub(crate) use helpers::*; @@ -251,8 +251,7 @@ async fn boxing() { "hi from POST" }), ) - .layer(tower_http::compression::CompressionLayer::new()) - .boxed(); + .layer(tower_http::compression::CompressionLayer::new()); let client = TestClient::new(app); @@ -532,10 +531,13 @@ async fn wildcard_sees_whole_url() { async fn middleware_applies_to_routes_above() { let app = Router::new() .route("/one", get(std::future::pending::<()>)) - .layer(TimeoutLayer::new(Duration::new(0, 0))) - .layer(HandleErrorLayer::new(|_: BoxError| { - StatusCode::REQUEST_TIMEOUT - })) + .layer( + ServiceBuilder::new() + .layer(HandleErrorLayer::new(|_: BoxError| { + StatusCode::REQUEST_TIMEOUT + })) + .layer(TimeoutLayer::new(Duration::new(0, 0))), + ) .route("/two", get(|| async {})); let client = TestClient::new(app); diff --git a/src/tests/nest.rs b/src/tests/nest.rs index a91238ae..db3f3585 100644 --- a/src/tests/nest.rs +++ b/src/tests/nest.rs @@ -1,7 +1,6 @@ use super::*; use crate::body::box_body; use crate::error_handling::HandleErrorExt; -use crate::routing::EmptyRouter; use std::collections::HashMap; #[tokio::test] @@ -256,5 +255,5 @@ 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::<_, Body>("/one/*rest", Router::::new()); + Router::::new().nest("/one/*rest", Router::new()); } diff --git a/src/tests/or.rs b/src/tests/or.rs index e9b78f94..b930a5f7 100644 --- a/src/tests/or.rs +++ b/src/tests/or.rs @@ -135,8 +135,11 @@ async fn layer_and_handle_error() { let one = Router::new().route("/foo", get(|| async {})); let two = Router::new() .route("/timeout", get(futures::future::pending::<()>)) - .layer(TimeoutLayer::new(Duration::from_millis(10))) - .layer(HandleErrorLayer::new(|_| StatusCode::REQUEST_TIMEOUT)); + .layer( + ServiceBuilder::new() + .layer(HandleErrorLayer::new(|_| StatusCode::REQUEST_TIMEOUT)) + .layer(TimeoutLayer::new(Duration::from_millis(10))), + ); let app = one.or(two); let client = TestClient::new(app); @@ -159,8 +162,8 @@ async fn nesting() { #[tokio::test] async fn boxed() { - let one = Router::new().route("/foo", get(|| async {})).boxed(); - let two = Router::new().route("/bar", get(|| async {})).boxed(); + let one = Router::new().route("/foo", get(|| async {})); + let two = Router::new().route("/bar", get(|| async {})); let app = one.or(two); let client = TestClient::new(app);