Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

util: unify and rename combinators #499

Merged
merged 6 commits into from
Jan 4, 2021
Merged

util: unify and rename combinators #499

merged 6 commits into from
Jan 4, 2021

Conversation

hawkw
Copy link
Member

@hawkw hawkw commented Dec 28, 2020

Currently, the ServiceExt trait has with and try_with methods for
composing a Service with functions that modify the request type, and
map_err and map_ok methods for composing a service with functions
that modify the response type. Meanwhile, ServiceBuilder has
map_request for composing a service with a request mapping function,
and map_response for composing a service with a response-mapping
function. These combinators are very similar in purpose, but are
implemented using different middleware types that essentially duplicate
the same behavior.

Before releasing 0.4, we should probably unify these APIs. In
particular, it would be good to de-duplicate the middleware service
types, and to unify the naming.

This commit makes the following changes:

  • Rename the ServiceExt::with and ServiceExt::try_with combinators
    to map_request and try_map_request
    • Rename the ServiceExt::map_ok combinator to map_response
  • Unify the ServiceBuilder::map_request and ServiceExt::map_request
    combinators to use the same Service type
  • Unify the ServiceBuilder::map_response and
    ServiceExt::map_response combinators to use the same Service type
  • Unify the ServiceBuilder::map_err and ServiceExt::map_err
    combinators to use the same Service type
  • Only take FnOnce + Clone when in response/err combinators, which
    require cloning into the future type. MapRequest and TryMapRequest
    now take FnMut(Request)s and don't clone them every time they're
    called
  • Reexport future types for combinators where it makes sense.
  • Add a try_map_request method to ServiceBuilder

Closes #498

Currently, the `ServiceExt` trait has `with` and `try_with` methods for
composing a `Service` with functions that modify the request type, and
`map_err` and `map_ok` methods for composing a service with functions
that modify the response type. Meanwhile, `ServiceBuilder` has
`map_request` for composing a service with a request mapping function,
and `map_response` for composing a service with a response-mapping
function. These combinators are very similar in purpose, but are
implemented using different middleware types that essentially duplicate
the same behavior.

Before releasing 0.4, we should probably unify these APIs. In
particular, it would be good to de-duplicate the middleware service
types, and to unify the naming.

This commit makes the following changes:
- Rename the `ServiceExt::with` and `ServiceExt::try_with` combinators
  to `map_request` and `try_map_request`
  - Rename the `ServiceExt::map_ok` combinator to `map_response`
- Unify the `ServiceBuilder::map_request` and `ServiceExt::map_request`
  combinators to use the same `Service` type
- Unify the `ServiceBuilder::map_response` and
  `ServiceExt::map_response` combinators to use the same `Service` type
- Unify the `ServiceBuilder::map_err` and `ServiceExt::map_err`
  combinators to use the same `Service` type
- Only take `FnOnce + Clone` when in response/err combinators, which
  require cloning into the future type. `MapRequest` and `TryMapRequest`
  now take `FnMut(Request)`s and don't clone them every time they're
  called
- Reexport future types for combinators where it makes sense.
- Add a `try_map_request` method to `ServiceBuilder`

Closes #498
@hawkw
Copy link
Member Author

hawkw commented Dec 28, 2020

cc @hlb8122 --- sorry for hacking up your code, but i think this should improve the overall API surface a bit!

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this pull request Dec 28, 2020
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
@hawkw hawkw mentioned this pull request Dec 28, 2020
@hawkw hawkw self-assigned this Dec 28, 2020
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
hawkw added a commit that referenced this pull request Dec 28, 2020
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
tower/src/builder/mod.rs Show resolved Hide resolved
tower/src/builder/mod.rs Show resolved Hide resolved
@hawkw hawkw merged commit 878b10f into master Jan 4, 2021
@hawkw hawkw deleted the eliza/unify-combinators branch January 4, 2021 18:27
hawkw added a commit that referenced this pull request Jan 4, 2021
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
hawkw added a commit that referenced this pull request Jan 4, 2021
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
@hawkw hawkw mentioned this pull request Jan 6, 2021
hawkw added a commit that referenced this pull request Jan 6, 2021
## Motivation

It was pointed out that there is currently some overlap between the
`try_with` `Service` combinator and `tower::filter` middleware (see #499 (comment) ).
`try_with` synchronously maps from a `Request` ->
`Result<DifferentRequest, Error>`, while `tower::filter`
_asynchronously_ maps from a `&Request` to a `Result<(), Error>`. The
key differences are: - `try_with` takes a request by value, and allows
the predicate to return a *different* request value - `try_with` also
permits changing the _type_ of the request - `try_with` is synchronous,
while `tower::filter` is asynchronous - `tower::filter` has a
`Predicate` trait, which can be implemented by more than just functions.
For example, a struct with a `HashSet` could implement `Predicate` by
failing requests that match the values in the hashset.

It definitely seems like there's demand for both synchronous and
asynchronous request filtering. However, the APIs we have currently
differ pretty significantly. It would be nice to make them more
consistent with each other.

As an aside, `tower::filter` [does not seem all that widely used][1].


Meanwhile, `linkerd2-proxy` defines its own `RequestFilter` middleware,
using a [predicate trait][2] that's essentially in between `tower::filter` and
`ServiceExt::try_with`: - it's synchronous, like `try_with` - it allows
modifying the type of the request, like `try_with` - it uses a trait for
predicates, rather than a `Fn`, like `tower::filter` - it uses a similar
naming scheme to `tower::filter` ("filtering" rather than "with"/"map").

[1]: https://github.com/search?l=&p=1&q=%22tower%3A%3Afilter%22+extension%3Ars&ref=advsearch&type=Code
[2]: https://github.com/linkerd/linkerd2-proxy/blob/24bee8cbc5413b4587a14bea1e2714ce1f1f919a/linkerd/stack/src/request_filter.rs#L8-L12

## Solution

This branch rewrites `tower::filter` to make the following changes:

* Predicates are synchronous by default. A separate `AsyncFilter` type
  and an `AsyncPredicate` trait are available for predicates returning
  futures.
* Predicates may now return a new `Request` type, allowing `Filter` and
  `AsyncFilter` to subsume `try_map_request`.
* Predicates may now return any error type, and errors are now converted
  into `BoxError`s.

Closes #502

Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

combinator service unification
4 participants