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}