use crate::future::pagination_stream::collect::sealed::Collectable;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
pub mod collect;
pub mod fn_stream;
use fn_stream::FnStream;
#[derive(Debug)]
pub struct PaginationStream<Item>(FnStream<Item>);
impl<Item> PaginationStream<Item> {
pub fn new(stream: FnStream<Item>) -> Self {
Self(stream)
}
pub async fn next(&mut self) -> Option<Item> {
self.0.next().await
}
pub fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Item>> {
Pin::new(&mut self.0).poll_next(cx)
}
pub async fn collect<T: Collectable<Item>>(self) -> T {
self.0.collect().await
}
}
impl<T, E> PaginationStream<Result<T, E>> {
pub async fn try_next(&mut self) -> Result<Option<T>, E> {
self.next().await.transpose()
}
pub async fn try_collect(self) -> Result<Vec<T>, E> {
self.collect::<Result<Vec<T>, E>>().await
}
}
#[derive(Debug)]
pub struct TryFlatMap<Page, Err>(PaginationStream<Result<Page, Err>>);
impl<Page, Err> TryFlatMap<Page, Err> {
pub fn new(stream: PaginationStream<Result<Page, Err>>) -> Self {
Self(stream)
}
pub fn flat_map<M, Item, Iter>(mut self, map: M) -> PaginationStream<Result<Item, Err>>
where
Page: Send + 'static,
Err: Send + 'static,
M: Fn(Page) -> Iter + Send + 'static,
Item: Send + 'static,
Iter: IntoIterator<Item = Item> + Send,
<Iter as IntoIterator>::IntoIter: Send,
{
PaginationStream::new(FnStream::new(|tx| {
Box::pin(async move {
while let Some(page) = self.0.next().await {
match page {
Ok(page) => {
let mapped = map(page);
for item in mapped.into_iter() {
let _ = tx.send(Ok(item)).await;
}
}
Err(e) => {
let _ = tx.send(Err(e)).await;
break;
}
}
}
}) as Pin<Box<dyn Future<Output = ()> + Send>>
}))
}
}
#[cfg(test)]
mod test {
use crate::future::pagination_stream::{FnStream, PaginationStream, TryFlatMap};
use std::sync::{Arc, Mutex};
use std::time::Duration;
#[tokio::test]
async fn fn_stream_returns_results() {
tokio::time::pause();
let mut stream = FnStream::new(|tx| {
Box::pin(async move {
tx.send("1").await.expect("failed to send");
tokio::time::sleep(Duration::from_secs(1)).await;
tokio::time::sleep(Duration::from_secs(1)).await;
tx.send("2").await.expect("failed to send");
tokio::time::sleep(Duration::from_secs(1)).await;
tx.send("3").await.expect("failed to send");
})
});
let mut out = vec![];
while let Some(value) = stream.next().await {
out.push(value);
}
assert_eq!(vec!["1", "2", "3"], out);
}
#[tokio::test]
async fn fn_stream_try_next() {
tokio::time::pause();
let mut stream = FnStream::new(|tx| {
Box::pin(async move {
tx.send(Ok(1)).await.unwrap();
tx.send(Ok(2)).await.unwrap();
tx.send(Err("err")).await.unwrap();
})
});
let mut out = vec![];
while let Ok(value) = stream.try_next().await {
out.push(value);
}
assert_eq!(vec![Some(1), Some(2)], out);
}
#[tokio::test]
async fn fn_stream_doesnt_poll_after_done() {
let mut stream = FnStream::new(|tx| {
Box::pin(async move {
assert!(tx.send("blah").await.is_ok());
Box::leak(Box::new(tx));
})
});
assert_eq!(Some("blah"), stream.next().await);
let mut test_stream = tokio_test::task::spawn(stream);
test_stream.enter(|ctx, pin| {
let polled = pin.poll_next(ctx);
assert!(polled.is_pending());
});
test_stream.enter(|ctx, pin| {
let polled = pin.poll_next(ctx);
assert!(polled.is_pending());
});
}
#[tokio::test]
async fn waits_for_reader() {
let progress = Arc::new(Mutex::new(0));
let mut stream = FnStream::new(|tx| {
let progress = progress.clone();
Box::pin(async move {
*progress.lock().unwrap() = 1;
tx.send("1").await.expect("failed to send");
*progress.lock().unwrap() = 2;
tx.send("2").await.expect("failed to send");
*progress.lock().unwrap() = 3;
tx.send("3").await.expect("failed to send");
*progress.lock().unwrap() = 4;
})
});
assert_eq!(*progress.lock().unwrap(), 0);
stream.next().await.expect("ready");
assert_eq!(*progress.lock().unwrap(), 1);
assert_eq!("2", stream.next().await.expect("ready"));
assert_eq!(2, *progress.lock().unwrap());
let _ = stream.next().await.expect("ready");
assert_eq!(3, *progress.lock().unwrap());
assert_eq!(None, stream.next().await);
assert_eq!(4, *progress.lock().unwrap());
}
#[tokio::test]
async fn generator_with_errors() {
let mut stream = FnStream::new(|tx| {
Box::pin(async move {
for i in 0..5 {
if i != 2 {
if tx.send(Ok(i)).await.is_err() {
return;
}
} else {
tx.send(Err(i)).await.unwrap();
return;
}
}
})
});
let mut out = vec![];
while let Some(Ok(value)) = stream.next().await {
out.push(value);
}
assert_eq!(vec![0, 1], out);
}
#[tokio::test]
async fn flatten_items_ok() {
#[derive(Debug)]
struct Output {
items: Vec<u8>,
}
let stream: FnStream<Result<_, &str>> = FnStream::new(|tx| {
Box::pin(async move {
tx.send(Ok(Output {
items: vec![1, 2, 3],
}))
.await
.unwrap();
tx.send(Ok(Output {
items: vec![4, 5, 6],
}))
.await
.unwrap();
})
});
assert_eq!(
Ok(vec![1, 2, 3, 4, 5, 6]),
TryFlatMap::new(PaginationStream::new(stream))
.flat_map(|output| output.items.into_iter())
.try_collect()
.await,
);
}
#[tokio::test]
async fn flatten_items_error() {
#[derive(Debug)]
struct Output {
items: Vec<u8>,
}
let stream = FnStream::new(|tx| {
Box::pin(async move {
tx.send(Ok(Output {
items: vec![1, 2, 3],
}))
.await
.unwrap();
tx.send(Err("bummer")).await.unwrap();
})
});
assert_eq!(
Err("bummer"),
TryFlatMap::new(PaginationStream::new(stream))
.flat_map(|output| output.items.into_iter())
.try_collect()
.await
)
}
}