-
Notifications
You must be signed in to change notification settings - Fork 283
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
Add Future-like combinators #396
Conversation
This looks like a fantastic start! I would like to review this once we get the 0.3 release out since we can always add more items in follow up patches. |
Has there been any decisions made with respect to this? I noticed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this, I'd definitely like to get this in!
I had some suggestions, including a few style nits & API design questions to think about.
Also, I think a lot of this code has moved around on master --- would you mind rebasing and moving the new code to track that? I don't think there are any other differences that should effect this PR besides code moving around.
F: FnOnce(S::Error) -> Error, | ||
F: Clone, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using FnOnce + Clone
here seemed a little weird at first, but then I realized that the requirement that the function be moved into the MapErr
future and cloned in Layer::layer
kind of drives this. So if we're going to have to require Clone
, it makes sense to use FnOnce
.
tower-util/src/combinators/with.rs
Outdated
use tower_layer::Layer; | ||
use tower_service::Service; | ||
|
||
pub struct With<S, F> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tower-http
contains a tower-request-modifier
crate that does a bunch of HTTP-specific request mutations...I wonder if that could be reimplemented on top of this API, eventually? It would be nice to keep that in mind and make sure this is possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll give it a fork and try it out when I get some spare time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I departed from the previous API by a fair bit but I think it turned out ok.
320b2f5
to
c11f11b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks really good to me!
I noticed a couple things that would be good to address:
- CI will warn us unless we add doc comments to all the new public types introduced here --- let's do that. Since the main way users will interact with these types will be via the combinators, it's okay if the types' docs are pretty minimal, as long as they link to the combinator that creates them. This is essentially what
std
's docs do for iterator combinators etc. - Looks like the examples don't actually compile on CI --- let's fix that, please!
Once those issues are addressed, I would be happy to merge this, unless @LucioFranco or @jonhoo have any thoughts on naming.
Quick note about the documentation: I've used intra-doc links here which are only supported on nightly (and the docs.rs compiler). The reason for this is that the of The same problem exists for the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super sorry for the delay on reviewing this!!!!!
Overall, looks fantastic, I have a couple comments inline. Let me know if you have any questions. The Map
layer that exists already is probably a good reference point and we should try and match that.
impl<S, F, Request, Error> Service<Request> for MapErr<S, F> | ||
where | ||
S: Service<Request>, | ||
F: FnOnce(S::Error) -> Error + Clone, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why clone? Why not Fn(S::Error) -> Error
? I believe Fn
is semantically similar to FnOnce + Clone
while being less restrictive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@LucioFranco I don't think that's the case. Fn
can be called with an &
reference. However, since the function is called after the inner service's future, the function needs to be moved into the future that the MapErr
service returns. Therefore, it cannot be borrowed without constraining the future's lifetime --- so it must be Clone
d. And if it has to be Clone
, it may as well be FnOnce
, since each clone will only be called a single time.
If we wanted to use Fn
, we would need to wrap it in an Arc
internally, and clone the Arc
into each future. That would also work, but in some cases (such as function pointers), the Arc
isn't needed, since little-f fn
can be freely cloned already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hawkw is right, we should then change the bounds on the map
combinators as well.
{ | ||
type Response = S::Response; | ||
type Error = Error; | ||
type Future = MapErrFut<S::Future, F>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been using MapErrFuture
instead of just Fut
. See the map combinator.
tower/src/util/combinators/map_ok.rs
Outdated
{ | ||
type Response = Response; | ||
type Error = S::Error; | ||
type Future = MapOkFut<S::Future, F>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
use tower_layer::Layer; | ||
use tower_service::Service; | ||
|
||
/// Service returned by the [`try_with`] combinator. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we get a bit more in the docs here? The name isn't clear enough that we probably want to add some more docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also link to the docs in the extension trait.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should link to the combinator that creates it, which is the authoritative source for the docs. It's also what users will probably see first. Since the combinator docs are linked, I don't think the type really needs any additional documentation --- it would just add the burden of having to maintain two separate versions of essentially the same documentation, keep them in sync, and so on.
This is basically what std does for iterator combinators etc.
tower/src/util/combinators/with.rs
Outdated
use tower_layer::Layer; | ||
use tower_service::Service; | ||
|
||
/// Service returned by the [`with`] combinator. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
0c18eb9
to
e1e7a68
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Thanks @hlb8122 a ton! |
/// Composes a function *in front of* the service. | ||
/// | ||
/// This adapter produces a new service that passes each value through the | ||
/// given function `f` before sending it to `self`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this should reference the futures::SinkExt::with
method as a comparison? Not a blocker.
/// | ||
/// This adapter produces a new service that passes each value through the | ||
/// given function `f` before sending it to `self`. | ||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, maybe worth linking futures::SinkExt::try_with
here? Not a big deal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great to me --- only a handful of docs issues.
I'm not sure if the links without a reference work correctly or not. It might be worth running cargo docs --open -p tower
and making sure that all the links are rendered correctly and don't 404 when clicked on.
If I'm wrong, and RustDoc can handle this correctly, please disregard my comments.
Motivation
It is often the case that one wishes to take a service and modify it's output or input in some way without changing the behavior of
poll_ready()
. For example, turning a service returningResult<(Parts, Body), _>
into a service returningResult<Request<Body>, _>
or modifying the error type.Currently, there is no ergonomic way to apply such changes without a fair amount of boilerplate.
Proposal
Extend
ServiceExt
trait to add various methods mirroring the combinators fromTryFutureExt
and various services and layers to accommodate this.Requirements
Because combinators could be heavily chained together it's probably wise to trim all the fat off here and if possible make it zero-cost when compared to simply applying the combinators inside of the inner service's call.
Result
Having such methods should allow a more expressive algebra of tools to construct and combine services.
Current Implementation
At the time of writing I've introduced four combinators:
and an associated
Service
andLayer
for each.TODO