use futures::prelude::*;
pub enum ChunkResult<Chunk, State> {
Continue(
Option<Chunk>,
State,
),
Yield(
Option<Chunk>,
State,
Chunk,
),
}
pub fn apply_chunker<T, Chunk, State, F, Fut>(
chunker: F,
stream: impl Stream<Item = T> + Unpin,
initial_chunk: Option<Chunk>,
initial_state: State,
) -> impl Stream<Item = Chunk> + Unpin
where
F: Fn(T, Option<Chunk>, State) -> Fut,
Fut: Future<Output = ChunkResult<Chunk, State>>,
{
let complete = false;
Box::pin(stream::unfold(
(initial_chunk, initial_state, stream, chunker, complete),
|(mut current_chunk, mut current_state, mut stream, chunker, complete)| async move {
if complete {
return None;
}
while let Some(item) = stream.next().await {
match chunker(item, current_chunk, current_state).await {
ChunkResult::Continue(chunk, state) => {
current_chunk = chunk;
current_state = state;
}
ChunkResult::Yield(chunk, state, complete_chunk) => {
return Some((complete_chunk, (chunk, state, stream, chunker, false)));
}
}
}
#[allow(clippy::manual_map)]
match current_chunk {
Some(chunk) => {
Some((chunk, (None, current_state, stream, chunker, true)))
}
None => None,
}
},
))
}
pub fn try_apply_chunker<T, E, Chunk, State, F, Fut>(
chunker: F,
stream: impl TryStream<Ok = T, Error = E> + Unpin,
initial_chunk: Option<Chunk>,
initial_state: State,
) -> impl TryStream<Ok = Chunk, Error = E> + Unpin
where
F: Fn(T, Option<Chunk>, State) -> Fut,
Fut: Future<Output = ChunkResult<Chunk, State>>,
{
let complete = false;
Box::pin(stream::unfold(
(initial_chunk, initial_state, stream, chunker, complete),
|(mut current_chunk, mut current_state, mut stream, chunker, complete)| async move {
if complete {
return None;
}
loop {
match stream.try_next().await {
Err(e) => {
return Some((
Err(e),
(current_chunk, current_state, stream, chunker, complete),
));
}
Ok(None) => {
#[allow(clippy::manual_map)]
match current_chunk {
Some(chunk) => {
return Some((
Ok(chunk),
(None, current_state, stream, chunker, true),
));
}
None => return None,
}
}
Ok(Some(item)) => match chunker(item, current_chunk, current_state).await {
ChunkResult::Continue(chunk, state) => {
current_chunk = chunk;
current_state = state;
}
ChunkResult::Yield(chunk, state, complete_chunk) => {
return Some((
Ok(complete_chunk),
(chunk, state, stream, chunker, false),
));
}
},
}
}
},
))
}
#[cfg(test)]
mod test_apply_chunker {
use super::*;
use futures::stream::empty;
async fn identity_chunker<T>(item: T, _: Option<T>, _: ()) -> ChunkResult<T, ()> {
ChunkResult::Yield(None, (), item)
}
#[tokio::test]
async fn test_identity() {
let x = apply_chunker(identity_chunker, empty::<()>(), None, ())
.collect::<Vec<_>>()
.await;
assert_eq!(x, vec![]);
let stream = stream::iter(vec![1, 2, 3, 4]);
let x = apply_chunker(identity_chunker, stream, None, ())
.collect::<Vec<_>>()
.await;
assert_eq!(x, vec![1, 2, 3, 4]);
}
async fn null_chunker<T>(_: T, _: Option<T>, _: ()) -> ChunkResult<T, ()> {
ChunkResult::Continue(None, ())
}
#[tokio::test]
async fn test_null() {
let x = apply_chunker(null_chunker, empty::<()>(), None, ())
.collect::<Vec<_>>()
.await;
assert_eq!(x, vec![]);
let stream = stream::iter(vec![1, 2, 3, 4]);
let x = apply_chunker(null_chunker, stream, None, ())
.collect::<Vec<_>>()
.await;
assert_eq!(x, vec![]);
}
async fn pair_chunker<T>(
item: T,
_: Option<(T, T)>,
state: Option<T>,
) -> ChunkResult<(T, T), Option<T>> {
match state {
Some(first) => ChunkResult::Yield(None, None, (first, item)),
None => ChunkResult::Continue(None, Some(item)),
}
}
#[tokio::test]
async fn test_pairs() {
let x = apply_chunker(pair_chunker, empty::<()>(), None, None)
.collect::<Vec<_>>()
.await;
assert_eq!(x, vec![]);
let stream = stream::iter(vec![1, 2, 3, 4, 5]);
let x = apply_chunker(pair_chunker, stream, None, None)
.collect::<Vec<_>>()
.await;
assert_eq!(x, vec![(1, 2), (3, 4)]);
}
async fn ten_chunker(
item: u64,
chunk: Option<Vec<u64>>,
state: u64,
) -> ChunkResult<Vec<u64>, u64> {
if state + item > 10 {
ChunkResult::Yield(Some(vec![item]), item, chunk.unwrap())
} else {
let mut chunk = chunk.unwrap_or_default();
chunk.push(item);
ChunkResult::Continue(Some(chunk), state + item)
}
}
#[tokio::test]
async fn test_ten_chunker() {
let x = apply_chunker(ten_chunker, empty::<u64>(), None, 0)
.collect::<Vec<Vec<u64>>>()
.await;
let y: Vec<Vec<_>> = vec![];
assert_eq!(x, y);
let stream = stream::iter(vec![1, 2, 3, 4, 5, 6, 3, 13, 4, 5]);
let x = apply_chunker(ten_chunker, stream, None, 0)
.collect::<Vec<_>>()
.await;
assert_eq!(
x,
vec![vec![1, 2, 3, 4], vec![5], vec![6, 3], vec![13], vec![4, 5]]
);
}
}
#[cfg(test)]
mod test_try_apply_chunker {
use super::*;
use anyhow::Result;
use futures::stream::empty;
async fn identity_chunker<T>(item: T, _: Option<T>, _: ()) -> ChunkResult<T, ()> {
ChunkResult::Yield(None, (), item)
}
#[tokio::test]
async fn test_identity() -> Result<()> {
let x = try_apply_chunker(identity_chunker, empty::<Result<()>>(), None, ())
.try_collect::<Vec<_>>()
.await?;
assert_eq!(x, vec![]);
let stream = stream::iter(vec![Ok::<_, anyhow::Error>(1), Ok(2), Ok(3), Ok(4)]);
let x = try_apply_chunker(identity_chunker, stream, None, ())
.try_collect::<Vec<_>>()
.await?;
assert_eq!(x, vec![1, 2, 3, 4]);
let stream = stream::iter(vec![
Ok(1),
Err(anyhow::anyhow!("ERR")),
Ok(2),
Ok(3),
Ok(4),
Err(anyhow::anyhow!("ERR")),
]);
let x = try_apply_chunker(identity_chunker, stream, None, ())
.into_stream()
.collect::<Vec<_>>()
.await;
assert_eq!(*x[0].as_ref().unwrap(), 1);
assert!(x[1].is_err());
assert_eq!(*x[2].as_ref().unwrap(), 2);
assert_eq!(*x[3].as_ref().unwrap(), 3);
assert_eq!(*x[4].as_ref().unwrap(), 4);
assert!(x[5].is_err());
Ok(())
}
async fn null_chunker<T>(_: T, _: Option<T>, _: ()) -> ChunkResult<T, ()> {
ChunkResult::Continue(None, ())
}
#[tokio::test]
async fn test_null() -> Result<()> {
let x = try_apply_chunker(null_chunker, empty::<Result<()>>(), None, ())
.try_collect::<Vec<_>>()
.await?;
assert_eq!(x, vec![]);
let stream = stream::iter(vec![Ok::<_, anyhow::Error>(1), Ok(2), Ok(3), Ok(4)]);
let x = try_apply_chunker(null_chunker, stream, None, ())
.try_collect::<Vec<_>>()
.await?;
assert_eq!(x, vec![]);
let stream = stream::iter(vec![
Ok(1),
Err(anyhow::anyhow!("ERR")),
Ok(2),
Ok(3),
Ok(4),
Err(anyhow::anyhow!("ERR")),
]);
let x = try_apply_chunker(null_chunker, stream, None, ())
.into_stream()
.collect::<Vec<_>>()
.await;
assert!(x[0].is_err());
assert!(x[1].is_err());
Ok(())
}
async fn pair_chunker<T>(
item: T,
_: Option<(T, T)>,
state: Option<T>,
) -> ChunkResult<(T, T), Option<T>> {
match state {
Some(first) => ChunkResult::Yield(None, None, (first, item)),
None => ChunkResult::Continue(None, Some(item)),
}
}
#[tokio::test]
async fn test_pairs() -> Result<()> {
let x = try_apply_chunker(pair_chunker, empty::<Result<()>>(), None, None)
.try_collect::<Vec<_>>()
.await?;
assert_eq!(x, vec![]);
let stream = stream::iter(vec![Ok::<_, anyhow::Error>(1), Ok(2), Ok(3), Ok(4), Ok(5)]);
let x = try_apply_chunker(pair_chunker, stream, None, None)
.try_collect::<Vec<_>>()
.await?;
assert_eq!(x, vec![(1, 2), (3, 4)]);
let stream = stream::iter(vec![
Ok(1),
Err(anyhow::anyhow!("ERR")),
Ok(2),
Ok(3),
Ok(4),
Err(anyhow::anyhow!("ERR")),
]);
let x = try_apply_chunker(pair_chunker, stream, None, None)
.into_stream()
.collect::<Vec<_>>()
.await;
assert!(x[0].is_err());
assert_eq!(*x[1].as_ref().unwrap(), (1, 2));
assert_eq!(*x[2].as_ref().unwrap(), (3, 4));
assert!(x[3].is_err());
Ok(())
}
async fn ten_chunker(
item: u64,
chunk: Option<Vec<u64>>,
state: u64,
) -> ChunkResult<Vec<u64>, u64> {
if state + item > 10 {
ChunkResult::Yield(Some(vec![item]), item, chunk.unwrap())
} else {
let mut chunk = chunk.unwrap_or_default();
chunk.push(item);
ChunkResult::Continue(Some(chunk), state + item)
}
}
#[tokio::test]
async fn test_ten_chunker() -> Result<()> {
let x = try_apply_chunker(ten_chunker, empty::<Result<u64>>(), None, 0)
.try_collect::<Vec<Vec<u64>>>()
.await?;
let y: Vec<Vec<_>> = vec![];
assert_eq!(x, y);
let stream = stream::iter(vec![
Ok::<_, anyhow::Error>(1),
Ok(2),
Ok(3),
Ok(4),
Ok(5),
Ok(6),
Ok(3),
Ok(13),
Ok(4),
Ok(5),
]);
let x = try_apply_chunker(ten_chunker, stream, None, 0)
.try_collect::<Vec<_>>()
.await?;
assert_eq!(
x,
vec![vec![1, 2, 3, 4], vec![5], vec![6, 3], vec![13], vec![4, 5],]
);
let stream = stream::iter(vec![
Ok::<_, anyhow::Error>(1),
Ok(2),
Err(anyhow::anyhow!("ERR")),
Ok(3),
Ok(4),
Ok(5),
Ok(6),
Err(anyhow::anyhow!("ERR")),
Ok(3),
Ok(13),
Ok(4),
Ok(5),
]);
let x = try_apply_chunker(ten_chunker, stream, None, 0)
.into_stream()
.collect::<Vec<_>>()
.await;
assert!(x[0].is_err());
assert_eq!(*x[1].as_ref().unwrap(), vec![1, 2, 3, 4]);
assert_eq!(*x[2].as_ref().unwrap(), vec![5]);
assert!(x[3].is_err());
assert_eq!(*x[4].as_ref().unwrap(), vec![6, 3]);
assert_eq!(*x[5].as_ref().unwrap(), vec![13]);
assert_eq!(*x[6].as_ref().unwrap(), vec![4, 5]);
Ok(())
}
}