use crate::error::{CompressionError, CompressionResult};
use crate::{CompressionLevel, Compressor};
use std::fmt;
#[derive(Clone)]
pub struct ZstdCompressor {
level: CompressionLevel,
}
impl ZstdCompressor {
pub fn new(level: CompressionLevel) -> Self {
ZstdCompressor { level }
}
pub fn fast() -> Self {
ZstdCompressor::new(CompressionLevel::Fast)
}
pub fn default_level() -> Self {
ZstdCompressor::new(CompressionLevel::Default)
}
pub fn best() -> Self {
ZstdCompressor::new(CompressionLevel::Best)
}
}
impl fmt::Debug for ZstdCompressor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ZstdCompressor")
.field("level", &self.level)
.finish()
}
}
impl Compressor for ZstdCompressor {
fn compress(&self, data: &[u8]) -> CompressionResult<Vec<u8>> {
let level = self.level.to_zstd_level();
if data.is_empty() {
return Ok(Vec::new());
}
match zstd::encode_all(data, level) {
Ok(compressed) => {
Ok(compressed)
}
Err(e) => Err(CompressionError::zstd_error(format!(
"zstd compression failed: {}",
e
))),
}
}
fn decompress(&self, data: &[u8]) -> CompressionResult<Vec<u8>> {
if data.is_empty() {
return Ok(Vec::new());
}
if data.len() >= 4 && data.starts_with(b"\x28\xb5\x2f\xfd") {
match zstd::decode_all(data) {
Ok(decompressed) => Ok(decompressed),
Err(e) => Err(CompressionError::decompression_failed(format!(
"zstd decompression failed: {}",
e
))),
}
} else {
Ok(data.to_vec())
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_zstd_compress_decompress() {
let compressor = ZstdCompressor::new(CompressionLevel::Default);
let original = b"Hello, World! This is a test of zstd compression.";
let compressed = compressor.compress(original).unwrap();
let decompressed = compressor.decompress(&compressed).unwrap();
assert_eq!(original, &decompressed[..]);
}
#[test]
fn test_zstd_compress_empty() {
let compressor = ZstdCompressor::new(CompressionLevel::Default);
let compressed = compressor.compress(b"").unwrap();
assert_eq!(compressed.len(), 0);
}
#[test]
fn test_zstd_decompress_empty() {
let compressor = ZstdCompressor::new(CompressionLevel::Default);
let decompressed = compressor.decompress(b"").unwrap();
assert_eq!(decompressed.len(), 0);
}
#[test]
fn test_zstd_decompress_uncompressed_data() {
let compressor = ZstdCompressor::new(CompressionLevel::Default);
let data = b"This is not compressed";
let result = compressor.decompress(data).unwrap();
assert_eq!(result, data);
}
#[test]
fn test_zstd_compression_levels() {
let data = b"This is test data that should compress. ".repeat(100);
let fast = ZstdCompressor::fast().compress(&data).unwrap();
let default = ZstdCompressor::default_level().compress(&data).unwrap();
let best = ZstdCompressor::best().compress(&data).unwrap();
assert!(best.len() <= default.len());
assert!(default.len() <= fast.len() || fast.len() < 100);
let decompressor = ZstdCompressor::default_level();
let fast_dec = decompressor.decompress(&fast).unwrap();
let default_dec = decompressor.decompress(&default).unwrap();
let best_dec = decompressor.decompress(&best).unwrap();
assert_eq!(fast_dec, data);
assert_eq!(default_dec, data);
assert_eq!(best_dec, data);
}
#[test]
fn test_zstd_large_data() {
let compressor = ZstdCompressor::new(CompressionLevel::Default);
let original = vec![0x42u8; 1024 * 1024];
let compressed = compressor.compress(&original).unwrap();
let decompressed = compressor.decompress(&compressed).unwrap();
assert_eq!(original, decompressed);
assert!(compressed.len() < original.len() / 100);
}
#[test]
fn test_zstd_random_data() {
let compressor = ZstdCompressor::new(CompressionLevel::Default);
let original = (0..1000).map(|i| (i ^ 0xAA) as u8).collect::<Vec<_>>();
let compressed = compressor.compress(&original).unwrap();
let decompressed = compressor.decompress(&compressed).unwrap();
assert_eq!(original, decompressed);
assert!(compressed.len() <= original.len() + 1000);
}
#[test]
fn test_zstd_debug_format() {
let compressor = ZstdCompressor::new(CompressionLevel::Default);
let debug_str = format!("{:?}", compressor);
assert!(debug_str.contains("ZstdCompressor"));
assert!(debug_str.contains("Default"));
}
}