Skip to content

Commit bef0ade

Browse files
authored
util: Add then combinator (#500)
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
1 parent d0fde83 commit bef0ade

4 files changed

Lines changed: 499 additions & 0 deletions

File tree

tower/src/builder/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,24 @@ impl<L> ServiceBuilder<L> {
229229
self.layer(crate::util::MapErrLayer::new(f))
230230
}
231231

232+
/// Apply a function after the service, regardless of whether the future
233+
/// succeeds or fails.
234+
///
235+
/// This is similar to the [`map_response`] and [`map_err] functions,
236+
/// except that the *same* function is invoked when the service's future
237+
/// completes, whether it completes successfully or fails. This function
238+
/// takes the `Result` returned by the service's future, and returns a
239+
/// `Result`.
240+
///
241+
/// See the documentation for the [`then` combinator] for details.
242+
///
243+
/// [`then` combinator]: crate::util::ServiceExt::then
244+
#[cfg(feature = "util")]
245+
pub fn then<F>(self, f: F) -> ServiceBuilder<Stack<crate::util::ThenLayer<F>, L>> {
246+
self.layer(crate::util::ThenLayer::new(f))
247+
}
248+
249+
232250
/// Obtains the underlying `Layer` implementation.
233251
pub fn into_inner(self) -> L {
234252
self.layer

tower/src/util/map_result.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use futures_util::FutureExt;
2+
use std::task::{Context, Poll};
3+
use tower_layer::Layer;
4+
use tower_service::Service;
5+
6+
#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411
7+
pub use futures_util::future::Map as MapResultFuture;
8+
9+
/// Service returned by the [`map_result`] combinator.
10+
///
11+
/// [`map_result`]: crate::util::ServiceExt::map_result
12+
#[derive(Clone, Debug)]
13+
pub struct MapResult<S, F> {
14+
inner: S,
15+
f: F,
16+
}
17+
18+
/// A [`Layer`] that produces a [`MapResult`] service.
19+
///
20+
/// [`Layer`]: tower_layer::Layer
21+
#[derive(Debug, Clone)]
22+
pub struct MapResultLayer<F> {
23+
f: F,
24+
}
25+
26+
impl<S, F> MapResult<S, F> {
27+
/// Creates a new `MapResult` service.
28+
pub fn new(inner: S, f: F) -> Self {
29+
MapResult { f, inner }
30+
}
31+
}
32+
33+
impl<S, F, Request, Response, Error> Service<Request> for MapResult<S, F>
34+
where
35+
S: Service<Request>,
36+
Error: From<S::Error>,
37+
F: FnOnce(Result<S::Response, S::Error>) -> Result<Response, Error> + Clone,
38+
{
39+
type Response = Response;
40+
type Error = Error;
41+
type Future = MapResultFuture<S::Future, F>;
42+
43+
#[inline]
44+
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
45+
self.inner.poll_ready(cx).map_err(Into::into)
46+
}
47+
48+
#[inline]
49+
fn call(&mut self, request: Request) -> Self::Future {
50+
self.inner.call(request).map(self.f.clone())
51+
}
52+
}
53+
54+
impl<F> MapResultLayer<F> {
55+
/// Creates a new [`MapResultLayer`] layer.
56+
pub fn new(f: F) -> Self {
57+
MapResultLayer { f }
58+
}
59+
}
60+
61+
impl<S, F> Layer<S> for MapResultLayer<F>
62+
where
63+
F: Clone,
64+
{
65+
type Service = MapResult<S, F>;
66+
67+
fn layer(&self, inner: S) -> Self::Service {
68+
MapResult {
69+
f: self.f.clone(),
70+
inner,
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)