Skip to main content

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}