#![doc = include_str!("../README.md")]
use core::fmt::{self, Debug, Display};
use futures_util::{FutureExt, Stream, StreamExt, TryStream, future};
use tokio::{
sync::mpsc::{self, error::SendError},
task::JoinError,
};
use tokio_stream::wrappers::ReceiverStream;
pub trait TryStreamTranspose: TryStream {
fn transpose_with<Fut, F, T, E>(
self,
buf_size: usize,
mut f: F,
) -> impl Future<Output = Result<T, E>>
where
F: FnMut(ReceiverStream<Self::Ok>) -> Fut,
Fut: Future<Output = Result<T, E>>,
Self: Send
+ Sized
+ Stream<Item = Result<Self::Ok, Self::Error>>
+ 'static,
Self::Ok: Send,
Self::Error: Send,
E: Send + From<Self::Error> + From<Error<Self::Ok>> + 'static,
{
let (sender, recver) = mpsc::channel(buf_size);
let send_handle = tokio::spawn(async move {
tokio::pin! {
let stream = self;
}
while let Some(line) = stream.next().await {
sender.send(line?).await.map_err(Error::from)?;
}
Ok(())
});
let recv_handle = f(ReceiverStream::new(recver));
future::join(send_handle, recv_handle).map(|result| match result {
(Err(err), _) => Err(Error::from(err).into()),
(Ok(Err(err)), _) => Err(err),
(_, result) => result,
})
}
}
#[derive(Debug)]
pub struct Error<Item>(ErrorRepr<Item>);
#[derive(Debug)]
pub(crate) enum ErrorRepr<Item> {
Join(JoinError),
Send(SendError<Item>),
}
impl<Item> Display for Error<Item> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt("unable to transpose item", f)
}
}
impl<Item> core::error::Error for Error<Item>
where
Item: Debug + 'static,
{
#[inline]
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match &self.0 {
ErrorRepr::Join(err) => Some(err),
ErrorRepr::Send(err) => Some(err),
}
}
}
impl<S: TryStream> TryStreamTranspose for S {}
impl<Item> From<JoinError> for Error<Item> {
#[inline]
fn from(value: JoinError) -> Self {
Self(ErrorRepr::Join(value))
}
}
impl<Item> From<SendError<Item>> for Error<Item> {
#[inline]
fn from(value: SendError<Item>) -> Self {
Self(ErrorRepr::Send(value))
}
}