dora_node_api/event_stream/merged.rs
1//! Merge external stream into an [`EventStream`][super::EventStream].
2//!
3//! Sometimes nodes need to listen to external events, in addition to Dora events.
4//! This module provides support for that by providing the [`MergeExternal`] trait.
5
6use futures::{Stream, StreamExt};
7use futures_concurrency::stream::Merge;
8
9/// A Dora event or an event from an external source.
10#[derive(Debug)]
11#[allow(clippy::large_enum_variant)]
12pub enum MergedEvent<E> {
13 /// A Dora event
14 Dora(super::Event),
15 /// An external event
16 ///
17 /// Yielded by the stream that was merged into the Dora [`EventStream`][super::EventStream].
18 External(E),
19}
20
21/// A general enum to represent a value of two possible types.
22pub enum Either<A, B> {
23 /// Value is of the first type, type `A`.
24 First(A),
25 /// Value is of the second type, type `B`.
26 Second(B),
27}
28
29impl<A> Either<A, A> {
30 /// Unwraps an `Either` instance where both types are identical.
31 pub fn flatten(self) -> A {
32 match self {
33 Either::First(a) => a,
34 Either::Second(a) => a,
35 }
36 }
37}
38
39/// Allows merging an external event stream into an existing event stream.
40// TODO: use impl trait return type once stable
41pub trait MergeExternal<'a, E> {
42 /// The item type yielded from the merged stream.
43 type Item;
44
45 /// Merge the given stream into an existing event stream.
46 ///
47 /// Returns a new event stream that yields items from both streams.
48 /// The ordering between the two streams is not guaranteed. Each item is
49 /// wrapped in [`MergedEvent`] so the consumer can tell a dora [`Event`] from
50 /// an external one; note that a dora [`Event::Stop`] still has to be handled
51 /// explicitly to leave the loop.
52 ///
53 /// [`Event`]: super::Event
54 /// [`Event::Stop`]: super::Event::Stop
55 ///
56 /// ```no_run
57 /// use dora_node_api::{DoraNode, Event};
58 /// use dora_node_api::merged::{MergeExternal, MergedEvent};
59 /// use futures::StreamExt;
60 ///
61 /// let (_node, events) = DoraNode::init_from_env().unwrap();
62 ///
63 /// // Any `Stream` can be merged in; here a trivial external source.
64 /// let external = futures::stream::iter(["external-tick"]);
65 /// let mut merged = events.merge_external(external);
66 ///
67 /// futures::executor::block_on(async {
68 /// while let Some(event) = merged.next().await {
69 /// match event {
70 /// MergedEvent::Dora(Event::Stop(_)) => break,
71 /// MergedEvent::Dora(_dora_event) => { /* handle dora input, etc. */ }
72 /// MergedEvent::External(_tick) => { /* handle external event */ }
73 /// }
74 /// }
75 /// });
76 /// ```
77 fn merge_external(
78 self,
79 external_events: impl Stream<Item = E> + Unpin + 'a,
80 ) -> Box<dyn Stream<Item = Self::Item> + Unpin + 'a>;
81}
82
83/// Allows merging a sendable external event stream into an existing (sendable) event stream.
84///
85/// By implementing [`Send`], the streams can be sent to different threads.
86pub trait MergeExternalSend<'a, E> {
87 /// The item type yielded from the merged stream.
88 type Item;
89
90 /// Merge the given stream into an existing event stream.
91 ///
92 /// Returns a new event stream that yields items from both streams.
93 /// The ordering between the two streams is not guaranteed.
94 fn merge_external_send(
95 self,
96 external_events: impl Stream<Item = E> + Unpin + Send + Sync + 'a,
97 ) -> Box<dyn Stream<Item = Self::Item> + Unpin + Send + Sync + 'a>;
98}
99
100impl<'a, E> MergeExternal<'a, E> for super::EventStream
101where
102 E: 'static,
103{
104 type Item = MergedEvent<E>;
105
106 fn merge_external(
107 self,
108 external_events: impl Stream<Item = E> + Unpin + 'a,
109 ) -> Box<dyn Stream<Item = Self::Item> + Unpin + 'a> {
110 let dora = self.map(MergedEvent::Dora);
111 let external = external_events.map(MergedEvent::External);
112 Box::new((dora, external).merge())
113 }
114}
115
116impl<'a, E> MergeExternalSend<'a, E> for super::EventStream
117where
118 E: 'static,
119{
120 type Item = MergedEvent<E>;
121
122 fn merge_external_send(
123 self,
124 external_events: impl Stream<Item = E> + Unpin + Send + Sync + 'a,
125 ) -> Box<dyn Stream<Item = Self::Item> + Unpin + Send + Sync + 'a> {
126 let dora = self.map(MergedEvent::Dora);
127 let external = external_events.map(MergedEvent::External);
128 Box::new((dora, external).merge())
129 }
130}
131
132impl<'a, E, F, S> MergeExternal<'a, F> for S
133where
134 S: Stream<Item = MergedEvent<E>> + Unpin + 'a,
135 E: 'a,
136 F: 'a,
137{
138 type Item = MergedEvent<Either<E, F>>;
139
140 fn merge_external(
141 self,
142 external_events: impl Stream<Item = F> + Unpin + 'a,
143 ) -> Box<dyn Stream<Item = Self::Item> + Unpin + 'a> {
144 let first = self.map(|e| match e {
145 MergedEvent::Dora(d) => MergedEvent::Dora(d),
146 MergedEvent::External(e) => MergedEvent::External(Either::First(e)),
147 });
148 let second = external_events.map(|e| MergedEvent::External(Either::Second(e)));
149 Box::new((first, second).merge())
150 }
151}
152
153impl<'a, E, F, S> MergeExternalSend<'a, F> for S
154where
155 S: Stream<Item = MergedEvent<E>> + Unpin + Send + Sync + 'a,
156 E: 'a,
157 F: 'a,
158{
159 type Item = MergedEvent<Either<E, F>>;
160
161 fn merge_external_send(
162 self,
163 external_events: impl Stream<Item = F> + Unpin + Send + Sync + 'a,
164 ) -> Box<dyn Stream<Item = Self::Item> + Unpin + Send + Sync + 'a> {
165 let first = self.map(|e| match e {
166 MergedEvent::Dora(d) => MergedEvent::Dora(d),
167 MergedEvent::External(e) => MergedEvent::External(Either::First(e)),
168 });
169 let second = external_events.map(|e| MergedEvent::External(Either::Second(e)));
170 Box::new((first, second).merge())
171 }
172}