builder,util: add convenience methods for boxing services (#616)

* builder,util: add convenience methods for boxing services

This adds a couple of new methods to `ServiceBuilder` and `ServiceExt`:

- `ServiceBuilder::boxed`
- `ServiceExt::boxed`
- `ServiceBuilder::clone_boxed`
- `ServiceExt::clone_boxed`

They apply `BoxService::layer` and `CloneBoxService::layer`
respectively.

* fix doc links

* add missing `cfg`s

* Update tower/CHANGELOG.md

Co-authored-by: Eliza Weisman <eliza@buoyant.io>

* Apply suggestions from code review

Co-authored-by: Eliza Weisman <eliza@buoyant.io>

* not sure why rustdoc cannot infer these

* line breaks

* trailing whitespace

* make docs a bit more consistent

* fix doc links

* update tokio

* don't pull in old version of tower

* Don't run `cargo deny check bans` as it hangs

Co-authored-by: Eliza Weisman <eliza@buoyant.io>
This commit is contained in:
David Pedersen 2021-11-18 13:56:19 +01:00 committed by GitHub
parent 48f8ae90a4
commit 4d80f7ed90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 226 additions and 3 deletions

View File

@ -96,3 +96,5 @@ jobs:
steps:
- uses: actions/checkout@v1
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check advisories licenses sources

View File

@ -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"

View File

@ -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" }

View File

@ -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`.

View File

@ -688,6 +688,126 @@ impl<L> ServiceBuilder<L> {
{
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<Request, Response, BoxError> = 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<S, R>(svc: S) -> S
/// # where S: Service<R> { svc }
/// ```
///
/// [`BoxService::layer()`]: crate::util::BoxService::layer()
#[cfg(feature = "util")]
#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
pub fn boxed<S, R>(
self,
) -> ServiceBuilder<
Stack<
tower_layer::LayerFn<
fn(
L::Service,
) -> crate::util::BoxService<
R,
<L::Service as Service<R>>::Response,
<L::Service as Service<R>>::Error,
>,
>,
L,
>,
>
where
L: Layer<S>,
L::Service: Service<R> + Send + 'static,
<L::Service as Service<R>>::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<Request, Response, BoxError> = 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<S, R>(svc: S) -> S
/// # where S: Service<R> { 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<S, R>(
self,
) -> ServiceBuilder<
Stack<
tower_layer::LayerFn<
fn(
L::Service,
) -> crate::util::CloneBoxService<
R,
<L::Service as Service<R>>::Response,
<L::Service as Service<R>>::Error,
>,
>,
L,
>,
>
where
L: Layer<S>,
L::Service: Service<R> + Clone + Send + 'static,
<L::Service as Service<R>>::Future: Send + 'static,
{
self.layer(crate::util::CloneBoxService::layer())
}
}
impl<L: fmt::Debug> fmt::Debug for ServiceBuilder<L> {

View File

@ -953,6 +953,103 @@ pub trait ServiceExt<Request>: tower_service::Service<Request> {
{
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<Request, Response, BoxError> = service
/// .map_request(|req| {
/// println!("received request");
/// req
/// })
/// .map_response(|res| {
/// println!("response produced");
/// res
/// })
/// .boxed();
/// # let service = assert_service(service);
/// # fn assert_service<S, R>(svc: S) -> S
/// # where S: Service<R> { svc }
/// ```
///
/// [`Service`]: crate::Service
/// [`clone_boxed`]: Self::clone_boxed
fn boxed(self) -> BoxService<Request, Self::Response, Self::Error>
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<Request, Response, BoxError> = 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<S, R>(svc: S) -> S
/// # where S: Service<R> { svc }
/// ```
///
/// [`Service`]: crate::Service
/// [`boxed`]: Self::boxed
fn clone_boxed(self) -> CloneBoxService<Request, Self::Response, Self::Error>
where
Self: Clone + Sized + Send + 'static,
Self::Future: Send + 'static,
{
CloneBoxService::new(self)
}
}
impl<T: ?Sized, Request> ServiceExt<Request> for T where T: tower_service::Service<Request> {}