Skip to main content

try_stream_transpose/
lib.rs

1#![doc = include_str!("../README.md")]
2use core::fmt::{self, Debug, Display};
3use futures_util::{FutureExt, Stream, StreamExt, TryStream, future};
4use tokio::{
5    sync::mpsc::{self, error::SendError},
6    task::JoinError,
7};
8use tokio_stream::wrappers::ReceiverStream;
9
10/// Convenience for `TryStream`, when you want to work on a stream of `Ok`s as
11/// a stream of the values inside the `Ok`s.
12pub trait TryStreamTranspose: TryStream {
13    /// Transposes a [`TryStream`], in the sense that it passes a stream of its
14    /// unwrapped items to the closure `f`.
15    ///
16    /// If, while executing the closure, one of the items turns out an `Err`
17    /// value, the in-closure stream stops yielding and this function returns
18    /// said error. If neither the stream nor this function, while executing,
19    /// produce an error, the closure's result is returned.
20    ///
21    /// Error types returned by this function must implement conversions from
22    /// [`Error`], in addition to the conversion from the items's error type.
23    /// Additionally, the `buf_size` parameter controls the size of the internal
24    /// buffer.
25    /// ```
26    /// # async fn asd() {
27    /// use futures_util::{FutureExt, StreamExt, TryStreamExt, stream};
28    /// use try_stream_transpose::TryStreamTranspose;
29    ///
30    /// let nums: [Result<i32, Error>; 4] = [Ok(4), Ok(5), Ok(6), Ok(7)];
31    /// core::assert_matches!(
32    ///     stream::iter(nums)
33    ///         .map_ok(|x| x + 1)
34    ///         .transpose_with(1024, |s| {
35    ///             s.fold(1, async |a, b| a * b).map(|x| Ok(x / 40))
36    ///         })
37    ///         .await,
38    ///     Ok::<i32, Error>(42)
39    /// );
40    /// let argh = [Ok(2), Err("3"), Ok(7)];
41    /// core::assert_matches!(
42    ///     stream::iter(argh)
43    ///         .map_ok(|x| x + 1)
44    ///         .transpose_with(1024, |s| {
45    ///             s.fold(1, async |a, b| a * b).map(|x| Ok(x / 8))
46    ///         })
47    ///         .await,
48    ///     Err(Error::Message("3"))
49    /// );
50    ///
51    /// // The error type must support conversion from `Error`. Conversion from
52    /// // `&'static str` is implemented to support our code above.
53    /// #[derive(Debug, thiserror::Error)]
54    /// #[error(transparent)]
55    /// enum Error {
56    ///     #[error("{0}")]
57    ///     Message(&'static str),
58    ///     Internal(#[from] try_stream_transpose::Error<i32>),
59    /// }
60    ///
61    /// impl From<&'static str> for Error {
62    ///     fn from(msg: &'static str) -> Self {
63    ///         Self::Message(msg)
64    ///     }
65    /// }
66    /// # }
67    /// # tokio::runtime::Runtime::new().unwrap().block_on(asd())
68    /// ```
69    fn transpose_with<Fut, F, T, E>(
70        self,
71        buf_size: usize,
72        mut f: F,
73    ) -> impl Future<Output = Result<T, E>>
74    where
75        F: FnMut(ReceiverStream<Self::Ok>) -> Fut,
76        Fut: Future<Output = Result<T, E>>,
77        Self: Send
78            + Sized
79            + Stream<Item = Result<Self::Ok, Self::Error>>
80            + 'static,
81        Self::Ok: Send,
82        Self::Error: Send,
83        E: Send + From<Self::Error> + From<Error<Self::Ok>> + 'static,
84    {
85        // To unwrap the items from their `Result`s and not evaluate the entire
86        // stream at once, we're sending the items through a channel and
87        // returning errors early once we're encountering them.
88        let (sender, recver) = mpsc::channel(buf_size);
89
90        // Create a separate task where we're sending the stream's items through
91        // the channel and either return errors, or `()` on success.
92        let send_handle = tokio::spawn(async move {
93            tokio::pin! {
94                let stream = self;
95            }
96            while let Some(line) = stream.next().await {
97                sender.send(line?).await.map_err(InternalSendError::Send)?;
98            }
99            Ok(())
100        });
101        // Wrap the channel's receiver in a `ReceiverStream` and pass that to
102        // the closure.
103        let recv_handle = f(ReceiverStream::new(recver));
104
105        // Join the futures for the sender and the receiver task.
106        future::join(send_handle, recv_handle).map(|result| match result {
107            // Joining the sender failed.
108            (Err(err), _) => Err(Error::from(err).into()),
109            // Joining the sender succeeded, but a stream item was an error.
110            (Ok(Err(InternalSendError::Item(err))), _) => Err(err.into()),
111            // Processing the stream returned an error.
112            (_, Err(err)) => Err(err),
113            // Joining the sender succeeded, but sending failed somewhere.
114            (Ok(Err(InternalSendError::Send(err))), _) => {
115                Err(Error::from(err).into())
116            }
117            // Return the successful computation result.
118            (_, ok) => ok,
119        })
120    }
121}
122
123/// Abstract the errors encountered internally.
124#[derive(Debug)]
125pub struct Error<Item>(ErrorRepr<Item>);
126
127#[derive(Debug)]
128pub(crate) enum ErrorRepr<Item> {
129    Join(JoinError),
130    Send(SendError<Item>),
131}
132
133enum InternalSendError<I, E> {
134    Item(E),
135    Send(SendError<I>),
136}
137
138impl<Item> Display for Error<Item> {
139    #[inline]
140    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141        Display::fmt("unable to transpose item", f)
142    }
143}
144
145impl<Item> core::error::Error for Error<Item>
146where
147    Item: Debug + 'static,
148{
149    #[inline]
150    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
151        match &self.0 {
152            ErrorRepr::Join(err) => Some(err),
153            ErrorRepr::Send(err) => Some(err),
154        }
155    }
156}
157
158impl<S: TryStream> TryStreamTranspose for S {}
159
160impl<Item> From<JoinError> for Error<Item> {
161    #[inline]
162    fn from(value: JoinError) -> Self {
163        Self(ErrorRepr::Join(value))
164    }
165}
166
167impl<Item> From<SendError<Item>> for Error<Item> {
168    #[inline]
169    fn from(value: SendError<Item>) -> Self {
170        Self(ErrorRepr::Send(value))
171    }
172}
173
174impl<I, E> From<E> for InternalSendError<I, E> {
175    #[inline]
176    fn from(err: E) -> Self {
177        Self::Item(err)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use futures_util::stream;
185
186    #[tokio::test]
187    async fn error_propagation() {
188        #[derive(Debug, thiserror::Error)]
189        #[error(transparent)]
190        enum TestError {
191            #[error("{0}")]
192            Message(&'static str),
193            Internal(#[from] Error<i32>),
194        }
195        let nums: [Result<i32, TestError>; 4] = [Ok(4), Ok(5), Ok(6), Ok(7)];
196        core::assert_matches!(
197            stream::iter(nums)
198                .transpose_with(1024, |_| {
199                    future::err(TestError::Message("stream err"))
200                })
201                .await,
202            Err::<i32, TestError>(TestError::Message("stream err")),
203            "error propagation from inside the closure"
204        );
205    }
206}