use cratestack_core::CoolError;
use serde::de::DeserializeOwned;
use crate::error::ClientError;
pub struct CborSeqChunkDecoder {
buffer: Vec<u8>,
}
impl CborSeqChunkDecoder {
pub fn new() -> Self {
Self {
buffer: Vec::new(),
}
}
pub fn feed_chunk(&mut self, chunk: &[u8]) -> Result<Vec<Vec<u8>>, CoolError> {
self.buffer.extend_from_slice(chunk);
let mut items: Vec<Vec<u8>> = Vec::new();
let mut consumed = 0;
loop {
let remaining = &self.buffer[consumed..];
if remaining.is_empty() {
break;
}
let mut decoder = minicbor::decode::Decoder::new(remaining);
match decoder.skip() {
Ok(()) => {
let item_len = decoder.position();
if item_len == 0 {
return Err(CoolError::Codec(
"cbor-seq decoder made no progress".to_owned(),
));
}
items.push(remaining[..item_len].to_vec());
consumed += item_len;
}
Err(error) if error.is_end_of_input() => {
break;
}
Err(error) => {
return Err(CoolError::Codec(format!(
"cbor-seq decode failed: {error}",
)));
}
}
}
if consumed > 0 {
self.buffer.drain(..consumed);
}
Ok(items)
}
pub fn pending_len(&self) -> usize {
self.buffer.len()
}
}
impl Default for CborSeqChunkDecoder {
fn default() -> Self {
Self::new()
}
}
pub(crate) async fn pump_streamed_response_typed<T, E, F>(
response: reqwest::Response,
tx: tokio::sync::mpsc::Sender<Result<T, E>>,
convert_error: F,
) where
T: DeserializeOwned + Send + 'static,
E: Send + 'static,
F: Fn(ClientError) -> E + Send + 'static,
{
use futures_util::StreamExt;
let mut byte_stream = response.bytes_stream();
let mut decoder = CborSeqChunkDecoder::new();
while let Some(chunk_result) = byte_stream.next().await {
let chunk = match chunk_result {
Ok(c) => c,
Err(error) => {
let _ = tx
.send(Err(convert_error(ClientError::Transport(error))))
.await;
return;
}
};
let items = match decoder.feed_chunk(&chunk) {
Ok(items) => items,
Err(error) => {
let _ = tx.send(Err(convert_error(ClientError::Codec(error)))).await;
return;
}
};
for item_bytes in items {
let decoded: Result<T, E> = minicbor_serde::from_slice(&item_bytes).map_err(|error| {
convert_error(ClientError::Codec(CoolError::Codec(format!(
"decode cbor-seq item: {error}",
))))
});
if tx.send(decoded).await.is_err() {
return;
}
}
}
if decoder.pending_len() > 0 {
let _ = tx
.send(Err(convert_error(ClientError::InvalidResponse(format!(
"stream ended with {} bytes buffered (incomplete final item)",
decoder.pending_len(),
)))))
.await;
}
}