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}