use std::collections::TryReserveError;
use crate::error::FsError;
use crate::error::FsErrorKind;
use crate::error::FsOperation;
use crate::error::FsResult;
pub(crate) struct ReadBuffer {
bytes: Vec<u8>,
maximum: usize,
}
impl ReadBuffer {
pub(crate) const fn new(maximum: usize) -> Self {
Self {
bytes: Vec::new(),
maximum,
}
}
pub(crate) fn len(&self) -> usize {
self.bytes.len()
}
pub(crate) fn try_append(&mut self, chunk: &[u8]) -> FsResult<()> {
self.try_append_with(chunk, Vec::try_reserve_exact)
}
pub(crate) fn into_vec(self) -> Vec<u8> {
self.bytes
}
fn try_append_with(
&mut self,
chunk: &[u8],
reserve: impl FnOnce(&mut Vec<u8>, usize) -> Result<(), TryReserveError>,
) -> FsResult<()> {
let needed = self
.bytes
.len()
.checked_add(chunk.len())
.filter(|needed| *needed <= self.maximum)
.ok_or_else(|| {
FsError::new(
FsErrorKind::ResourceLimitExceeded,
FsOperation::Read,
"read exceeds maximum byte count",
)
})?;
if needed > self.bytes.capacity() {
let grown = self.bytes.capacity().saturating_mul(2).max(8192);
let target = grown.max(needed).min(self.maximum);
let additional = target - self.bytes.len();
reserve(&mut self.bytes, additional).map_err(|error| {
FsError::with_source(
FsErrorKind::ResourceLimitExceeded,
FsOperation::Read,
"read buffer allocation exceeds available capacity",
error,
)
})?;
}
self.bytes.extend_from_slice(chunk);
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::error::Error;
use super::ReadBuffer;
use crate::error::FsErrorKind;
use crate::error::FsOperation;
#[test]
fn test_initial_reserve_failure_preserves_source_and_bytes() {
let mut buffer = ReadBuffer::new(20_000);
let error = buffer
.try_append_with(b"payload", |_, _| Vec::<u8>::new().try_reserve_exact(usize::MAX))
.expect_err("deterministic capacity overflow");
assert_eq!(error.kind(), FsErrorKind::ResourceLimitExceeded);
assert_eq!(error.operation(), FsOperation::Read);
assert!(error.source().is_some());
assert_eq!(buffer.len(), 0);
assert_eq!(buffer.bytes.capacity(), 0);
}
#[test]
fn test_later_reserve_failure_preserves_prior_bytes() {
let mut buffer = ReadBuffer::new(20_000);
buffer.try_append(b"saved").expect("initial reserve");
let chunk = vec![9; buffer.bytes.capacity()];
let error = buffer
.try_append_with(&chunk, |_, _| Vec::<u8>::new().try_reserve_exact(usize::MAX))
.expect_err("growth fails");
assert!(error.source().is_some());
assert_eq!(buffer.into_vec(), b"saved");
}
#[test]
fn test_growth_is_geometric_and_requested_capacity_is_bounded() {
let mut buffer = ReadBuffer::new(100_000);
let mut reservations = 0;
for _ in 0..100 {
buffer
.try_append_with(&[1; 1000], |bytes, additional| {
reservations += 1;
assert!(bytes.len() + additional <= 100_000);
bytes.try_reserve_exact(additional)
})
.expect("append within limit");
}
assert!(reservations <= 5, "growth must not reallocate for each chunk");
assert_eq!(buffer.len(), 100_000);
assert_eq!(
buffer.try_append(b"x").expect_err("over limit").kind(),
FsErrorKind::ResourceLimitExceeded
);
}
#[test]
fn test_empty_chunk_does_not_allocate() {
let mut buffer = ReadBuffer::new(0);
buffer
.try_append_with(b"", |_, _| panic!("no allocation"))
.expect("empty append");
assert!(buffer.into_vec().is_empty());
}
}