diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 3c211dcc..e869c646 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -96,3 +96,5 @@ jobs: steps: - uses: actions/checkout@v1 - uses: EmbarkStudios/cargo-deny-action@v1 + with: + command: check advisories licenses sources diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 2a039a5e..a39ddb9c 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" [dev-dependencies] tower = { version = "0.4", path = "../tower", features = ["full"] } tower-service = "0.3" -tokio = { version = "0.3", features = ["full"] } +tokio = { version = "1.0", features = ["full"] } rand = "0.8" pin-project = "1.0" futures = "0.3" diff --git a/tower-layer/Cargo.toml b/tower-layer/Cargo.toml index 416c8506..dfe3dee9 100644 --- a/tower-layer/Cargo.toml +++ b/tower-layer/Cargo.toml @@ -24,5 +24,5 @@ edition = "2018" [dependencies] [dev-dependencies] -tower-service = { version = "0.3.0" } -tower = { version = "0.3" } +tower-service = { version = "0.3.0", path = "../tower-service" } +tower = { version = "0.4", path = "../tower" } diff --git a/tower/CHANGELOG.md b/tower/CHANGELOG.md index 1b655569..634641c5 100644 --- a/tower/CHANGELOG.md +++ b/tower/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased - **util**: Add `CloneBoxService` which is a `Clone + Send` boxed `Service`. +- **util:** Add `ServiceExt::boxed` and `ServiceExt::clone_boxed` for applying the + `BoxService` and `CloneBoxService` middleware. +- **builder:** Add `ServiceBuilder::boxed` and `ServiceBuilder::clone_boxed` for + applying `BoxService` and `CloneBoxService` layers. - **util**: Remove unnecessary `Debug` bounds from `impl Debug for BoxService`. - **util**: Remove unnecessary `Debug` bounds from `impl Debug for UnsyncBoxService`. diff --git a/tower/src/builder/mod.rs b/tower/src/builder/mod.rs index a29983c8..5d24ac70 100644 --- a/tower/src/builder/mod.rs +++ b/tower/src/builder/mod.rs @@ -688,6 +688,126 @@ impl ServiceBuilder { { self } + + /// This wraps the inner service with the [`Layer`] returned by [`BoxService::layer()`]. + /// + /// See that method for more details. + /// + /// # Example + /// + /// ``` + /// use tower::{Service, ServiceBuilder, BoxError, util::BoxService}; + /// use std::time::Duration; + /// # + /// # struct Request; + /// # struct Response; + /// # impl Response { + /// # fn new() -> Self { Self } + /// # } + /// + /// let service: BoxService = ServiceBuilder::new() + /// .boxed() + /// .load_shed() + /// .concurrency_limit(64) + /// .timeout(Duration::from_secs(10)) + /// .service_fn(|req: Request| async { + /// Ok::<_, BoxError>(Response::new()) + /// }); + /// # let service = assert_service(service); + /// # fn assert_service(svc: S) -> S + /// # where S: Service { svc } + /// ``` + /// + /// [`BoxService::layer()`]: crate::util::BoxService::layer() + #[cfg(feature = "util")] + #[cfg_attr(docsrs, doc(cfg(feature = "util")))] + pub fn boxed( + self, + ) -> ServiceBuilder< + Stack< + tower_layer::LayerFn< + fn( + L::Service, + ) -> crate::util::BoxService< + R, + >::Response, + >::Error, + >, + >, + L, + >, + > + where + L: Layer, + L::Service: Service + Send + 'static, + >::Future: Send + 'static, + { + self.layer(crate::util::BoxService::layer()) + } + + /// This wraps the inner service with the [`Layer`] returned by [`CloneBoxService::layer()`]. + /// + /// This is similar to the [`boxed`] method, but it requires that `Self` implement + /// [`Clone`], and the returned boxed service implements [`Clone`]. + /// + /// See [`CloneBoxService`] for more details. + /// + /// # Example + /// + /// ``` + /// use tower::{Service, ServiceBuilder, BoxError, util::CloneBoxService}; + /// use std::time::Duration; + /// # + /// # struct Request; + /// # struct Response; + /// # impl Response { + /// # fn new() -> Self { Self } + /// # } + /// + /// let service: CloneBoxService = ServiceBuilder::new() + /// .clone_boxed() + /// .load_shed() + /// .concurrency_limit(64) + /// .timeout(Duration::from_secs(10)) + /// .service_fn(|req: Request| async { + /// Ok::<_, BoxError>(Response::new()) + /// }); + /// # let service = assert_service(service); + /// + /// // The boxed service can still be cloned. + /// service.clone(); + /// # fn assert_service(svc: S) -> S + /// # where S: Service { svc } + /// ``` + /// + /// [`CloneBoxService::layer()`]: crate::util::CloneBoxService::layer() + /// [`CloneBoxService`]: crate::util::CloneBoxService + /// [`boxed`]: Self::boxed + #[cfg(feature = "util")] + #[cfg_attr(docsrs, doc(cfg(feature = "util")))] + pub fn clone_boxed( + self, + ) -> ServiceBuilder< + Stack< + tower_layer::LayerFn< + fn( + L::Service, + ) -> crate::util::CloneBoxService< + R, + >::Response, + >::Error, + >, + >, + L, + >, + > + where + L: Layer, + L::Service: Service + Clone + Send + 'static, + >::Future: Send + 'static, + { + self.layer(crate::util::CloneBoxService::layer()) + } } impl fmt::Debug for ServiceBuilder { diff --git a/tower/src/util/mod.rs b/tower/src/util/mod.rs index 669a6e9e..446cacc4 100644 --- a/tower/src/util/mod.rs +++ b/tower/src/util/mod.rs @@ -953,6 +953,103 @@ pub trait ServiceExt: tower_service::Service { { MapFuture::new(self, f) } + + /// Convert the service into a [`Service`] + [`Send`] trait object. + /// + /// See [`BoxService`] for more details. + /// + /// If `Self` implements the [`Clone`] trait, the [`clone_boxed`] method + /// can be used instead, to produce a boxed service which will also + /// implement [`Clone`]. + /// + /// # Example + /// + /// ``` + /// use tower::{Service, ServiceExt, BoxError, service_fn, util::BoxService}; + /// # + /// # struct Request; + /// # struct Response; + /// # impl Response { + /// # fn new() -> Self { Self } + /// # } + /// + /// let service = service_fn(|req: Request| async { + /// Ok::<_, BoxError>(Response::new()) + /// }); + /// + /// let service: BoxService = service + /// .map_request(|req| { + /// println!("received request"); + /// req + /// }) + /// .map_response(|res| { + /// println!("response produced"); + /// res + /// }) + /// .boxed(); + /// # let service = assert_service(service); + /// # fn assert_service(svc: S) -> S + /// # where S: Service { svc } + /// ``` + /// + /// [`Service`]: crate::Service + /// [`clone_boxed`]: Self::clone_boxed + fn boxed(self) -> BoxService + where + Self: Sized + Send + 'static, + Self::Future: Send + 'static, + { + BoxService::new(self) + } + + /// Convert the service into a [`Service`] + [`Clone`] + [`Send`] trait object. + /// + /// This is similar to the [`boxed`] method, but it requires that `Self` implement + /// [`Clone`], and the returned boxed service implements [`Clone`]. + /// See [`CloneBoxService`] for more details. + /// + /// # Example + /// + /// ``` + /// use tower::{Service, ServiceExt, BoxError, service_fn, util::CloneBoxService}; + /// # + /// # struct Request; + /// # struct Response; + /// # impl Response { + /// # fn new() -> Self { Self } + /// # } + /// + /// let service = service_fn(|req: Request| async { + /// Ok::<_, BoxError>(Response::new()) + /// }); + /// + /// let service: CloneBoxService = service + /// .map_request(|req| { + /// println!("received request"); + /// req + /// }) + /// .map_response(|res| { + /// println!("response produced"); + /// res + /// }) + /// .clone_boxed(); + /// + /// // The boxed service can still be cloned. + /// service.clone(); + /// # let service = assert_service(service); + /// # fn assert_service(svc: S) -> S + /// # where S: Service { svc } + /// ``` + /// + /// [`Service`]: crate::Service + /// [`boxed`]: Self::boxed + fn clone_boxed(self) -> CloneBoxService + where + Self: Clone + Sized + Send + 'static, + Self::Future: Send + 'static, + { + CloneBoxService::new(self) + } } impl ServiceExt for T where T: tower_service::Service {}