Skip to main content

try_stream_transpose/
lib.rs

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