#![warn(missing_docs)]
use derive_builder::Builder;
use error::LimitReaderError;
use flate2::read::ZlibDecoder;
use readable::MyBufReader;
use readable::Readable;
use readable::{falible::LimitReaderFallible, infalible::LimitReaderInfallible};
use std::fmt::Display;
use std::fmt::Formatter;
use std::io;
use std::io::prelude::*;
use std::io::BufReader;
use std::path::PathBuf;
use LimitReaderResult as Result;
pub(crate) mod error;
pub(crate) mod readable;
pub type LimitReaderResult<T> = std::result::Result<T, LimitReaderError>;
pub mod prelude {
pub use crate::{error::LimitReaderError, LimitReader, LimitReaderOutput, LimitReaderResult};
}
#[allow(dead_code)]
pub struct LimitReader {
buf: [u8; Self::DEFAULT_BUF_SIZE],
expected_size: u64,
decode_zlib: bool,
decode_gzip: bool,
}
impl Default for LimitReader {
fn default() -> Self {
Self::new()
}
}
impl LimitReader {
pub const DEFAULT_BUF_SIZE: usize = 1024;
#[must_use]
pub fn new() -> Self {
Self {
buf: [0; Self::DEFAULT_BUF_SIZE],
expected_size: (Self::DEFAULT_BUF_SIZE - 1) as u64,
decode_zlib: false,
decode_gzip: false,
}
}
#[must_use]
pub fn buffer(&self) -> &[u8; Self::DEFAULT_BUF_SIZE] {
&self.buf
}
pub fn limit(&mut self, limit: u64) -> &mut Self {
self.expected_size = limit;
self
}
pub fn enable_decode_zlib(&mut self) -> &mut Self {
self.decode_zlib = true;
self
}
#[allow(dead_code)]
fn enable_decode_gzip(&mut self) -> &mut Self {
self.decode_gzip = true;
self
}
pub fn read(&mut self, source: PathBuf) -> Result<usize> {
let f = std::fs::File::open(source).expect("Unable to open file");
if self.decode_zlib {
let z = ZlibDecoder::new(f);
let buf_reader = MyBufReader(z);
let reader = LimitReaderFallible::new(buf_reader, self.expected_size);
self.try_read(reader)
} else {
let buf_reader = MyBufReader(BufReader::new(f));
let reader = LimitReaderFallible::new(buf_reader, self.expected_size);
self.try_read(reader)
}
}
pub fn read_limited(&mut self, source: PathBuf) -> Result<LimitReaderOutput> {
let source_bytes = std::fs::metadata(&source)?.len();
let f = std::fs::File::open(source)?;
let bytes_read = if self.decode_zlib {
let z = ZlibDecoder::new(f);
let buf_reader = MyBufReader(z);
let reader = LimitReaderInfallible::new(buf_reader, self.expected_size);
self.try_read(reader)?
} else {
let buf_reader = MyBufReader(BufReader::new(f));
let reader = LimitReaderInfallible::new(buf_reader, self.expected_size);
self.try_read(reader)?
};
Ok(LimitReaderOutputBuilder::default()
.source_size(source_bytes)
.bytes_read(bytes_read as u64)
.build()?)
}
fn try_read(&mut self, mut reader: impl Readable) -> Result<usize> {
let try_read = reader.perform_read(&mut self.buf);
match try_read {
Ok(value) => Ok(value),
Err(err) => Err(LimitReaderError::new(error::ErrorKind::ReadError, err)),
}
}
}
#[allow(missing_docs)]
#[derive(Default, Builder)]
#[builder(setter(into))]
pub struct LimitReaderOutput {
source_size: u64,
bytes_read: u64,
}
impl LimitReaderOutput {
#[must_use]
pub fn bytes_read(&self) -> u64 {
self.bytes_read
}
#[must_use]
pub fn source_size(&self) -> u64 {
self.source_size
}
#[must_use]
pub fn bytes_remaining(&self) -> u64 {
self.source_size - self.bytes_read
}
}
impl Display for LimitReaderOutput {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{{ source_size: {}, bytes_read:{} }}",
self.source_size, self.bytes_read
)
}
}
#[cfg(test)]
mod tests {
use crate::LimitReader;
use flate2::write::ZlibEncoder;
use flate2::Compression;
use std::fs::File;
use std::io::Write;
use tempfile::tempdir;
mod falible {
use super::*;
#[test]
fn it_works() {
let dir = tempdir().unwrap();
let text = "Mike was here. Briefly.";
let file_path = dir.path().join("test_output.txt");
let mut file = File::create(&file_path).unwrap();
writeln!(file, "{}", &text).unwrap();
let mut limit_reader = LimitReader::new();
match limit_reader.read(file_path.clone()) {
Ok(read_size) => {
let persisted_text =
String::from_utf8(limit_reader.buf[..read_size].to_vec()).unwrap();
assert_eq!(persisted_text, format!("{}\n", &text).to_string());
}
Err(_) => unreachable!(),
}
let mut file = File::create(&file_path).unwrap();
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(text.as_bytes()).unwrap();
let compressed = e.finish().unwrap();
file.write_all(&compressed).unwrap();
let mut limit_reader = LimitReader::new();
limit_reader.enable_decode_zlib();
match limit_reader.read(file_path) {
Ok(read_size) => {
let persisted_text =
String::from_utf8(limit_reader.buf[..read_size].to_vec()).unwrap();
assert_eq!(persisted_text, format!("{}", &text).to_string());
}
Err(_) => unreachable!(),
};
drop(file);
dir.close().unwrap();
}
#[test]
fn panic_due_to_limit_constraint() {
let dir = tempdir().unwrap();
let text = "Mike was here. Briefly.";
let file_path = dir.path().join("test_output.txt");
let mut file = File::create(&file_path).unwrap();
writeln!(file, "{}", &text).unwrap();
let mut limit_reader = LimitReader::new();
let limit = 8_u64;
limit_reader.limit(limit);
match limit_reader.read(file_path) {
Ok(read_size) => {
assert!(read_size == limit.try_into().unwrap());
}
Err(err) => {
assert_eq!("Error: too many bytes", err.to_string());
}
}
drop(file);
dir.close().unwrap();
}
#[test]
fn panic_with_decode_zlib_due_to_limit_constraint() {
let dir = tempdir().unwrap();
let text = "Mike was here. Briefly.";
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(text.as_bytes()).unwrap();
let compressed = e.finish().unwrap();
let file_path = dir.path().join("test_output.txt");
let mut file = File::create(&file_path).unwrap();
file.write_all(&compressed).unwrap();
let mut limit_reader = LimitReader::new();
limit_reader.limit(8);
match limit_reader.read(file_path) {
Ok(read_size) => {
let persisted_text =
String::from_utf8(limit_reader.buf[..read_size].to_vec()).unwrap();
assert_eq!(persisted_text, format!("{}", &text).to_string());
}
Err(err) => assert_eq!("Error: too many bytes", err.to_string()),
};
drop(file);
dir.close().unwrap();
}
#[test]
fn panic_decode_zlib_error_on_corrupt_deflate_stream() {
let dir = tempdir().unwrap();
let text = "Mike was here. Briefly.";
let file_path = dir.path().join("test_output.txt");
let mut file = File::create(&file_path).unwrap();
writeln!(file, "{}", &text).unwrap();
let mut limit_reader = LimitReader::new();
limit_reader.enable_decode_zlib();
match limit_reader.read(file_path) {
Ok(_) => unreachable!(),
Err(err) => assert_eq!("Error: corrupt deflate stream", err.to_string()),
};
drop(file);
dir.close().unwrap();
}
}
mod infalible {
use super::*;
#[test]
fn it_works() {
let dir = tempdir().unwrap();
let text = "Mike was here. Briefly.";
let file_path = dir.path().join("test_output.txt");
let mut file = File::create(&file_path).unwrap();
writeln!(file, "{}", &text).unwrap();
let mut limit_reader = LimitReader::new();
let limit = 8_u64;
limit_reader.limit(limit);
match limit_reader.read_limited(file_path.clone()) {
Ok(reader_output) => {
let bytes_read = reader_output.bytes_read();
assert!(bytes_read == limit)
}
Err(_) => unreachable!(),
}
let mut file = File::create(&file_path).unwrap();
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
e.write_all(text.as_bytes()).unwrap();
let compressed = e.finish().unwrap();
file.write_all(&compressed).unwrap();
let mut limit_reader = LimitReader::new();
limit_reader.limit(limit).enable_decode_zlib();
match limit_reader.read_limited(file_path.clone()) {
Ok(reader_output) => {
let bytes_read = reader_output.bytes_read();
let persisted_text =
String::from_utf8(limit_reader.buf[..(bytes_read as usize)].to_vec())
.unwrap();
assert_eq!(
persisted_text,
format!("{}", &text[..(bytes_read as usize)]).to_string()
);
}
Err(_) => unreachable!(),
};
drop(file);
dir.close().unwrap();
}
#[test]
fn panic_decode_zlib_error_on_corrupt_deflate_stream() {
let dir = tempdir().unwrap();
let text = "Mike was here. Briefly.";
let file_path = dir.path().join("test_output.txt");
let mut file = File::create(&file_path).unwrap();
writeln!(file, "{}", &text).unwrap();
let mut limit_reader = LimitReader::new();
let limit = 8_u64;
limit_reader
.limit(limit)
.enable_decode_zlib();
match limit_reader.read(file_path) {
Ok(_) => unreachable!(),
Err(err) => assert_eq!("Error: corrupt deflate stream", err.to_string()),
};
drop(file);
dir.close().unwrap();
}
}
}