feat: Make Endpoint::node_addr
watchable and add trait Watcher
& combinators
#3045
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Description
Goal of this PR was: make
Endpoint::node_addr
return something that can be watched, so you can useep.node_addr()?.initialized().await?
orep.node_addr()?.get()?
orep.node_addr()?.updated().await?
, etc.Unfortunately, there's no
Watchable<Option<NodeAddr>>
anywhere, instead, aNodeAddr
technically consists of aWatchable<Option<BTreeSet<DirectAddr>>>
and aWatchable<Option<RelayUrl>>
.So this PR abstracts all the
initialized
,updated
, etc. functionality ofWatcher
into atrait Watcher
, and renamesWatcher
intoDirectWatcher
.On top of this we can build the usual combinators like
MapWatcher
that transforms aWatcher<Value = A>
into aWatcher<Value = B>
using a functionA -> B
, or anOrWatcher
that takes two watchersWatcher<Value = A>
and aWatcher<Value = B>
and creates aWatcher<Value = (A, B)>
.Breaking Changes
TODO
Notes & open questions
This PR would subsume #2732
You're probably wondering:
Why is
Watcher
a trait?Two reasons:
MapWatcher
orOrWatcher
are doing. The alternative is to have a concrete type for special cases per-api, like e.g. a customNodeAddrWatcher
that implements essentially theMapWatcher
andOrWatcher
functionality inside. This works, but I'd argue is less comprehensible ("What is the difference betweenNodeAddrWatcher
andDirectWatcher
? They seem to have the same API?") and also less composable "What if I want to combine the Watchers further?".DirectWatcher
,MapWatcher
, etc. we can re-use the same default implementations of theWatcher
trait, instead of having to repeat these implementations for all watchers, potentially allowing them to get out of sync or behave differently from one another.What is the difference between
Stream
andWatcher
?The main difference is that
Watcher
combines a poll function for the next item with a functionget(&self) -> Result<Self::Value, Disconnected>
that allows you to always try to get the current value.This allows the
poll_updated
function (which is very similar toStream::poll_next
) to gain some superpowers, e.g. theOrWatcher
combinator canget
the value of the otherWatcher
when one of its watchers has an update.Change checklist