Skip to content

Commit

Permalink
builder,util: add convenience methods for boxing services (#616)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Apply suggestions from code review

Co-authored-by: Eliza Weisman <[email protected]>

* 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 <[email protected]>
  • Loading branch information
davidpdrsn and hawkw authored Nov 18, 2021
1 parent 48f8ae9 commit 4d80f7e
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,5 @@ jobs:
steps:
- uses: actions/checkout@v1
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check advisories licenses sources
2 changes: 1 addition & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions tower-layer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
4 changes: 4 additions & 0 deletions tower/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
120 changes: 120 additions & 0 deletions tower/src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
97 changes: 97 additions & 0 deletions tower/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {}
Expand Down

0 comments on commit 4d80f7e

Please sign in to comment.