use crate::error::{ParxError, Result};
use crate::format::Compression;
use std::io::{Read, Write};
pub fn compress(data: &[u8], algorithm: Compression) -> Result<Vec<u8>> {
match algorithm {
Compression::Zstd => compress_zstd(data),
Compression::Lz4 => compress_lz4(data),
Compression::Gzip => compress_gzip(data),
}
}
pub fn decompress(data: &[u8], algorithm: Compression, size_hint: usize) -> Result<Vec<u8>> {
match algorithm {
Compression::Zstd => decompress_zstd(data, size_hint),
Compression::Lz4 => decompress_lz4(data),
Compression::Gzip => decompress_gzip(data, size_hint),
}
}
const ZSTD_LEVEL: i32 = 3;
fn compress_zstd(data: &[u8]) -> Result<Vec<u8>> {
zstd::encode_all(std::io::Cursor::new(data), ZSTD_LEVEL)
.map_err(|e| ParxError::CompressionError(format!("zstd compression failed: {e}")))
}
fn decompress_zstd(data: &[u8], size_hint: usize) -> Result<Vec<u8>> {
let mut output = Vec::with_capacity(size_hint);
let mut decoder = zstd::Decoder::new(std::io::Cursor::new(data))
.map_err(|e| ParxError::CompressionError(format!("zstd decoder init failed: {e}")))?;
decoder
.read_to_end(&mut output)
.map_err(|e| ParxError::CompressionError(format!("zstd decompression failed: {e}")))?;
Ok(output)
}
fn compress_lz4(data: &[u8]) -> Result<Vec<u8>> {
let compressed = lz4_flex::compress_prepend_size(data);
if compressed.is_empty() && !data.is_empty() {
return Err(ParxError::CompressionError(
"lz4 compression produced empty output".to_string(),
));
}
Ok(compressed)
}
fn decompress_lz4(data: &[u8]) -> Result<Vec<u8>> {
lz4_flex::decompress_size_prepended(data)
.map_err(|e| ParxError::CompressionError(format!("lz4 decompression failed: {e}")))
}
fn compress_gzip(data: &[u8]) -> Result<Vec<u8>> {
use flate2::write::GzEncoder;
use flate2::Compression;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder
.write_all(data)
.map_err(|e| ParxError::CompressionError(format!("gzip compression failed: {e}")))?;
encoder
.finish()
.map_err(|e| ParxError::CompressionError(format!("gzip finish failed: {e}")))
}
fn decompress_gzip(data: &[u8], size_hint: usize) -> Result<Vec<u8>> {
use flate2::read::GzDecoder;
let mut decoder = GzDecoder::new(data);
let mut output = Vec::with_capacity(size_hint);
decoder
.read_to_end(&mut output)
.map_err(|e| ParxError::CompressionError(format!("gzip decompression failed: {e}")))?;
Ok(output)
}
pub const AUTO_COMPRESS_THRESHOLD: usize = 10_000;
#[inline]
pub const fn should_auto_compress(data_len: usize) -> bool {
data_len > AUTO_COMPRESS_THRESHOLD
}
#[cfg(test)]
mod tests {
use super::*;
fn test_roundtrip(algorithm: Compression) {
let data = b"The quick brown fox jumps over the lazy dog. ".repeat(100);
let compressed = compress(&data, algorithm).expect("compression failed");
let decompressed =
decompress(&compressed, algorithm, data.len()).expect("decompression failed");
assert_eq!(data.as_slice(), decompressed.as_slice());
}
#[test]
fn test_zstd_roundtrip() {
test_roundtrip(Compression::Zstd);
}
#[test]
fn test_lz4_roundtrip() {
test_roundtrip(Compression::Lz4);
}
#[test]
fn test_gzip_roundtrip() {
test_roundtrip(Compression::Gzip);
}
#[test]
fn test_compression_reduces_size() {
let data = b"AAAAAAAAAA".repeat(1000);
for algorithm in [Compression::Zstd, Compression::Lz4, Compression::Gzip] {
let compressed = compress(&data, algorithm).expect("compression failed");
assert!(
compressed.len() < data.len(),
"{} did not reduce size: {} vs {}",
algorithm,
compressed.len(),
data.len()
);
}
}
#[test]
fn test_empty_data() {
let data = b"";
for algorithm in [Compression::Zstd, Compression::Lz4, Compression::Gzip] {
let compressed = compress(data, algorithm).expect("compression failed");
let decompressed = decompress(&compressed, algorithm, 0).expect("decompression failed");
assert_eq!(data.as_slice(), decompressed.as_slice());
}
}
#[test]
fn test_auto_compress_threshold() {
assert!(!should_auto_compress(1000));
assert!(!should_auto_compress(10_000));
assert!(should_auto_compress(10_001));
assert!(should_auto_compress(100_000));
}
}