Skip to content

Commit

Permalink
util: Add then combinator (#500)
Browse files Browse the repository at this point in the history
Currently, `ServiceExt` and `ServiceBuilder` provide combinators for
mapping successful responses to other responses, and mapping errors to
other errors, but don't provide a way to map between `Ok` and `Err`
results.

For completeness, this branch adds a new `then` combinator, which takes
a function from `Result` to `Result` and applies it when the service's
future completes. This can be used for recovering from some errors or
for rejecting some `Ok` responses. It can also be used for behaviors
that should be run when a service's future completes regardless of
whether it completed successfully or not.

Depends on #499
  • Loading branch information
hawkw authored Jan 4, 2021
1 parent d0fde83 commit bef0ade
Show file tree
Hide file tree
Showing 4 changed files with 499 additions and 0 deletions.
18 changes: 18 additions & 0 deletions tower/src/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,24 @@ impl<L> ServiceBuilder<L> {
self.layer(crate::util::MapErrLayer::new(f))
}

/// Apply a function after the service, regardless of whether the future
/// succeeds or fails.
///
/// This is similar to the [`map_response`] and [`map_err] functions,
/// except that the *same* function is invoked when the service's future
/// completes, whether it completes successfully or fails. This function
/// takes the `Result` returned by the service's future, and returns a
/// `Result`.
///
/// See the documentation for the [`then` combinator] for details.
///
/// [`then` combinator]: crate::util::ServiceExt::then
#[cfg(feature = "util")]
pub fn then<F>(self, f: F) -> ServiceBuilder<Stack<crate::util::ThenLayer<F>, L>> {
self.layer(crate::util::ThenLayer::new(f))
}


/// Obtains the underlying `Layer` implementation.
pub fn into_inner(self) -> L {
self.layer
Expand Down
73 changes: 73 additions & 0 deletions tower/src/util/map_result.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use futures_util::FutureExt;
use std::task::{Context, Poll};
use tower_layer::Layer;
use tower_service::Service;

#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411
pub use futures_util::future::Map as MapResultFuture;

/// Service returned by the [`map_result`] combinator.
///
/// [`map_result`]: crate::util::ServiceExt::map_result
#[derive(Clone, Debug)]
pub struct MapResult<S, F> {
inner: S,
f: F,
}

/// A [`Layer`] that produces a [`MapResult`] service.
///
/// [`Layer`]: tower_layer::Layer
#[derive(Debug, Clone)]
pub struct MapResultLayer<F> {
f: F,
}

impl<S, F> MapResult<S, F> {
/// Creates a new `MapResult` service.
pub fn new(inner: S, f: F) -> Self {
MapResult { f, inner }
}
}

impl<S, F, Request, Response, Error> Service<Request> for MapResult<S, F>
where
S: Service<Request>,
Error: From<S::Error>,
F: FnOnce(Result<S::Response, S::Error>) -> Result<Response, Error> + Clone,
{
type Response = Response;
type Error = Error;
type Future = MapResultFuture<S::Future, F>;

#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx).map_err(Into::into)
}

#[inline]
fn call(&mut self, request: Request) -> Self::Future {
self.inner.call(request).map(self.f.clone())
}
}

impl<F> MapResultLayer<F> {
/// Creates a new [`MapResultLayer`] layer.
pub fn new(f: F) -> Self {
MapResultLayer { f }
}
}

impl<S, F> Layer<S> for MapResultLayer<F>
where
F: Clone,
{
type Service = MapResult<S, F>;

fn layer(&self, inner: S) -> Self::Service {
MapResult {
f: self.f.clone(),
inner,
}
}
}
Loading

0 comments on commit bef0ade

Please sign in to comment.