use std::collections::VecDeque;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures_core::Stream;
use serde::de::DeserializeOwned;
use super::ByteStream;
use crate::error::{Error, Result};
pub struct JsonStream<T> {
inner: ByteStream,
buf: Vec<u8>,
ready: VecDeque<Result<T>>,
done: bool,
}
impl<T> JsonStream<T> {
pub(super) fn new(inner: ByteStream) -> Self {
Self {
inner,
buf: Vec::new(),
ready: VecDeque::new(),
done: false,
}
}
}
impl<T: DeserializeOwned> JsonStream<T> {
fn drain_values(&mut self) {
let mut iter = serde_json::Deserializer::from_slice(&self.buf).into_iter::<T>();
loop {
match iter.next() {
Some(Ok(value)) => self.ready.push_back(Ok(value)),
Some(Err(e)) if e.is_eof() => break,
Some(Err(e)) => {
self.ready
.push_back(Err(Error::decode("invalid json value", e)));
let consumed = iter.byte_offset();
self.buf.drain(..consumed);
return;
}
None => break,
}
}
let consumed = iter.byte_offset();
self.buf.drain(..consumed);
}
}
impl<T: DeserializeOwned + Unpin> Stream for JsonStream<T> {
type Item = Result<T>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
loop {
if let Some(value) = this.ready.pop_front() {
return Poll::Ready(Some(value));
}
if this.done {
if this.buf.iter().any(|b| !b.is_ascii_whitespace()) {
this.buf.clear();
return Poll::Ready(Some(Err(Error::decode_message(
"trailing incomplete json value",
))));
}
return Poll::Ready(None);
}
match Pin::new(&mut this.inner).poll_next(cx) {
Poll::Ready(Some(Ok(chunk))) => {
this.buf.extend_from_slice(&chunk);
this.drain_values();
}
Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))),
Poll::Ready(None) => this.done = true,
Poll::Pending => return Poll::Pending,
}
}
}
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use serde::Deserialize;
use tokio_stream::StreamExt;
use super::*;
#[derive(Debug, Deserialize, PartialEq, Eq)]
struct Chunk {
i: u32,
}
fn json_stream(chunks: &[&[u8]]) -> JsonStream<Chunk> {
let owned: Vec<reqwest::Result<Bytes>> = chunks
.iter()
.map(|c| Ok(Bytes::copy_from_slice(c)))
.collect();
ByteStream::new(tokio_stream::iter(owned)).json::<Chunk>()
}
async fn collect(mut stream: JsonStream<Chunk>) -> Vec<Result<Chunk>> {
let mut out = Vec::new();
while let Some(item) = stream.next().await {
out.push(item);
}
out
}
#[tokio::test]
async fn parses_concatenated_objects_split_across_chunks() {
let stream = json_stream(&[br#"{"i":0}{"i"#, br#"":1}{"i":2}"#]);
let got: Vec<u32> = collect(stream)
.await
.into_iter()
.map(|r| r.unwrap().i)
.collect();
assert_eq!(got, vec![0, 1, 2]);
}
#[tokio::test]
async fn trailing_incomplete_object_is_an_error() {
let out = collect(json_stream(&[br#"{"i":7}{"i":"#])).await;
assert_eq!(out[0].as_ref().unwrap(), &Chunk { i: 7 });
assert!(out[1].is_err());
}
}