use super::chunk::ChunkHeader;
#[cfg(feature = "alloc")]
use super::MAX_CHUNK_SIZE;
use super::{StreamingConfig, StreamingProgress};
#[cfg(feature = "alloc")]
use crate::config::Config;
use crate::enc::{Encode, EncoderImpl, VecWriter};
use crate::{config, Result};
#[cfg(feature = "std")]
use super::ProgressCallback;
#[cfg(feature = "alloc")]
use crate::Error;
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
use std::io::Write;
#[cfg(feature = "std")]
pub struct StreamingEncoder<W: Write, C: Config = config::Configuration> {
writer: W,
streaming_config: StreamingConfig,
codec_config: C,
buffer: alloc::vec::Vec<u8>,
items_in_buffer: u32,
progress: StreamingProgress,
progress_callback: Option<ProgressCallback>,
}
#[cfg(feature = "std")]
impl<W: Write> StreamingEncoder<W> {
pub fn new(writer: W) -> Self {
Self::new_with_configs(writer, StreamingConfig::default(), config::standard())
}
pub fn with_config(writer: W, streaming_config: StreamingConfig) -> Self {
Self::new_with_configs(writer, streaming_config, config::standard())
}
}
#[cfg(feature = "std")]
impl<W: Write, C: Config> StreamingEncoder<W, C> {
pub fn new_with_config(writer: W, codec_config: C) -> Self {
Self::new_with_configs(writer, StreamingConfig::default(), codec_config)
}
pub fn new_with_configs(writer: W, streaming_config: StreamingConfig, codec_config: C) -> Self {
StreamingEncoder {
writer,
streaming_config,
codec_config,
buffer: alloc::vec::Vec::new(),
items_in_buffer: 0,
progress: StreamingProgress::default(),
progress_callback: None,
}
}
pub fn with_progress_callback(mut self, callback: ProgressCallback) -> Self {
self.progress_callback = Some(callback);
self
}
pub fn set_estimated_total(&mut self, total: u64) {
self.progress.estimated_total = Some(total);
}
pub fn write_item<T: Encode>(&mut self, item: &T) -> Result<()> {
let item_writer = VecWriter::new();
let mut encoder = EncoderImpl::new(item_writer, self.codec_config);
item.encode(&mut encoder)?;
let item_bytes = encoder.into_writer().into_vec();
if item_bytes.len() > MAX_CHUNK_SIZE {
return Err(Error::LimitExceeded {
limit: MAX_CHUNK_SIZE as u64,
found: item_bytes.len() as u64,
});
}
let threshold = self
.streaming_config
.chunk_size
.min(self.streaming_config.max_buffer_size);
if !self.buffer.is_empty() && self.buffer.len() + item_bytes.len() > threshold {
self.flush_chunk()?;
}
if self.items_in_buffer == u32::MAX {
self.flush_chunk()?;
}
self.buffer.extend_from_slice(&item_bytes);
self.items_in_buffer += 1;
if self.streaming_config.flush_per_item {
self.flush_chunk()?;
}
Ok(())
}
pub fn write_all<T: Encode, I: IntoIterator<Item = T>>(&mut self, items: I) -> Result<()> {
for item in items {
self.write_item(&item)?;
}
Ok(())
}
fn flush_chunk(&mut self) -> Result<()> {
if self.items_in_buffer == 0 {
return Ok(());
}
let payload_len = u32::try_from(self.buffer.len()).map_err(|_| Error::LimitExceeded {
limit: MAX_CHUNK_SIZE as u64,
found: self.buffer.len() as u64,
})?;
let header = ChunkHeader::data(payload_len, self.items_in_buffer);
self.writer
.write_all(&header.to_bytes())
.map_err(|e| Error::Io {
kind: e.kind(),
message: e.to_string(),
})?;
self.writer.write_all(&self.buffer).map_err(|e| Error::Io {
kind: e.kind(),
message: e.to_string(),
})?;
self.progress.items_processed += self.items_in_buffer as u64;
self.progress.bytes_processed += self.buffer.len() as u64;
self.progress.chunks_processed += 1;
if let Some(ref mut callback) = self.progress_callback {
callback(&self.progress);
}
self.buffer.clear();
self.items_in_buffer = 0;
Ok(())
}
pub fn finish(mut self) -> Result<W> {
self.flush_chunk()?;
let end_header = ChunkHeader::end();
self.writer
.write_all(&end_header.to_bytes())
.map_err(|e| Error::Io {
kind: e.kind(),
message: e.to_string(),
})?;
Ok(self.writer)
}
pub fn progress(&self) -> &StreamingProgress {
&self.progress
}
pub fn get_ref(&self) -> &W {
&self.writer
}
}
#[cfg(feature = "alloc")]
pub struct BufferStreamingEncoder<C: Config = config::Configuration> {
buffer: alloc::vec::Vec<u8>,
config: StreamingConfig,
codec_config: C,
chunk_buffer: alloc::vec::Vec<u8>,
items_in_chunk: u32,
progress: StreamingProgress,
}
#[cfg(feature = "alloc")]
impl BufferStreamingEncoder<config::Configuration> {
pub fn new() -> Self {
Self::with_config(StreamingConfig::default())
}
pub fn with_config(config: StreamingConfig) -> Self {
Self::new_with_configs(config, config::standard())
}
}
#[cfg(feature = "alloc")]
impl<C: Config> BufferStreamingEncoder<C> {
pub fn new_with_config(codec_config: C) -> Self {
Self::new_with_configs(StreamingConfig::default(), codec_config)
}
pub fn new_with_configs(config: StreamingConfig, codec_config: C) -> Self {
Self {
buffer: alloc::vec::Vec::new(),
config,
codec_config,
chunk_buffer: alloc::vec::Vec::new(),
items_in_chunk: 0,
progress: StreamingProgress::default(),
}
}
pub fn write_item<T: Encode>(&mut self, item: &T) -> Result<()> {
let item_writer = VecWriter::new();
let mut encoder = EncoderImpl::new(item_writer, self.codec_config);
item.encode(&mut encoder)?;
let item_bytes = encoder.into_writer().into_vec();
if item_bytes.len() > MAX_CHUNK_SIZE {
return Err(Error::LimitExceeded {
limit: MAX_CHUNK_SIZE as u64,
found: item_bytes.len() as u64,
});
}
let threshold = self.config.chunk_size.min(self.config.max_buffer_size);
if !self.chunk_buffer.is_empty() && self.chunk_buffer.len() + item_bytes.len() > threshold {
self.flush_chunk();
}
if self.items_in_chunk == u32::MAX {
self.flush_chunk();
}
self.chunk_buffer.extend_from_slice(&item_bytes);
self.items_in_chunk += 1;
Ok(())
}
fn flush_chunk(&mut self) {
if self.items_in_chunk == 0 {
return;
}
let payload_len = self.chunk_buffer.len() as u32;
let header = ChunkHeader::data(payload_len, self.items_in_chunk);
self.buffer.extend_from_slice(&header.to_bytes());
self.buffer.extend_from_slice(&self.chunk_buffer);
self.progress.items_processed += self.items_in_chunk as u64;
self.progress.bytes_processed += self.chunk_buffer.len() as u64;
self.progress.chunks_processed += 1;
self.chunk_buffer.clear();
self.items_in_chunk = 0;
}
pub fn finish(mut self) -> alloc::vec::Vec<u8> {
self.flush_chunk();
let end_header = ChunkHeader::end();
self.buffer.extend_from_slice(&end_header.to_bytes());
self.buffer
}
pub fn progress(&self) -> &StreamingProgress {
&self.progress
}
}
#[cfg(feature = "alloc")]
impl Default for BufferStreamingEncoder<config::Configuration> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "alloc")]
#[test]
fn test_buffer_encoder_basic() {
let mut encoder = BufferStreamingEncoder::new();
encoder.write_item(&42u32).expect("write failed");
encoder.write_item(&100u32).expect("write failed");
encoder.write_item(&255u32).expect("write failed");
let data = encoder.finish();
assert!(!data.is_empty());
assert!(data.len() > ChunkHeader::SIZE * 2);
}
#[cfg(feature = "std")]
#[test]
fn test_streaming_encoder_io() {
let mut buffer = alloc::vec::Vec::new();
{
let mut encoder = StreamingEncoder::new(&mut buffer);
for i in 0..100u32 {
encoder.write_item(&i).expect("write failed");
}
encoder.finish().expect("finish failed");
}
assert!(!buffer.is_empty());
}
#[cfg(feature = "alloc")]
#[test]
fn test_chunking() {
let config = StreamingConfig::new().with_chunk_size(1024);
let mut encoder = BufferStreamingEncoder::with_config(config);
for i in 0..1000u32 {
encoder.write_item(&i).expect("write failed");
}
let progress = encoder.progress().clone();
let _data = encoder.finish();
assert!(progress.chunks_processed >= 1);
}
#[cfg(feature = "std")]
#[test]
fn test_streaming_encoder_with_fixed_int_config() {
use super::super::decoder::StreamingDecoder;
use std::io::Cursor;
let codec = crate::config::standard().with_fixed_int_encoding();
let mut buffer = alloc::vec::Vec::new();
{
let mut encoder = StreamingEncoder::new_with_config(&mut buffer, codec);
for i in 0u32..30 {
encoder.write_item(&i).expect("write failed");
}
encoder.finish().expect("finish failed");
}
assert!(!buffer.is_empty(), "encoded buffer must not be empty");
let cursor = Cursor::new(buffer);
let mut decoder = StreamingDecoder::new_with_config(cursor, codec);
let decoded: alloc::vec::Vec<u32> = decoder.read_all().expect("read_all failed");
let expected: alloc::vec::Vec<u32> = (0..30).collect();
assert_eq!(expected, decoded, "fixed-int encoder roundtrip mismatch");
}
}