use crate::block::CodecId;
use crate::error::{Error, Result};
pub trait CompressionCodec: Send + Sync {
fn id(&self) -> CodecId;
fn compress(&self, data: &[u8]) -> Result<Vec<u8>>;
fn decompress(&self, data: &[u8], uncompressed_size: usize) -> Result<Vec<u8>>;
}
#[derive(Debug, Clone, Copy)]
pub struct NoCompression;
impl CompressionCodec for NoCompression {
fn id(&self) -> CodecId {
CodecId::None
}
fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
Ok(data.to_vec())
}
fn decompress(&self, data: &[u8], _uncompressed_size: usize) -> Result<Vec<u8>> {
Ok(data.to_vec())
}
}
#[derive(Debug, Clone)]
pub struct ZstdCodec {
pub level: i32,
}
impl ZstdCodec {
pub fn new(level: i32) -> Self {
Self { level }
}
}
impl Default for ZstdCodec {
fn default() -> Self {
Self { level: 3 }
}
}
impl CompressionCodec for ZstdCodec {
fn id(&self) -> CodecId {
CodecId::Named("zstd".into())
}
fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
zstd::bulk::compress(data, self.level).map_err(|e| Error::Codec(e.to_string()))
}
fn decompress(&self, data: &[u8], uncompressed_size: usize) -> Result<Vec<u8>> {
zstd::bulk::decompress(data, uncompressed_size).map_err(|e| Error::Codec(e.to_string()))
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Lz4Codec;
impl CompressionCodec for Lz4Codec {
fn id(&self) -> CodecId {
CodecId::Named("lz4".into())
}
fn compress(&self, data: &[u8]) -> Result<Vec<u8>> {
Ok(lz4_flex::compress_prepend_size(data))
}
fn decompress(&self, data: &[u8], _uncompressed_size: usize) -> Result<Vec<u8>> {
lz4_flex::decompress_size_prepended(data).map_err(|e| Error::Codec(e.to_string()))
}
}
pub fn decompress_by_id(
codec_id: &CodecId,
data: &[u8],
uncompressed_size: usize,
) -> Result<Vec<u8>> {
match codec_id {
CodecId::None => NoCompression.decompress(data, uncompressed_size),
CodecId::Named(name) => match name.as_str() {
"zstd" => ZstdCodec::default().decompress(data, uncompressed_size),
"lz4" => Lz4Codec.decompress(data, uncompressed_size),
other => Err(Error::Codec(format!("unknown codec: {other}"))),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_compression_roundtrip() {
let codec = NoCompression;
let data = b"hello world, this is a test payload";
let compressed = codec.compress(data).unwrap();
let decompressed = codec.decompress(&compressed, data.len()).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn no_compression_id() {
assert_eq!(NoCompression.id(), CodecId::None);
}
#[test]
fn codec_is_object_safe() {
let codec: Box<dyn CompressionCodec> = Box::new(NoCompression);
assert_eq!(codec.id(), CodecId::None);
}
#[test]
fn zstd_roundtrip() {
let codec = ZstdCodec::default();
let data = b"aaabbbccc repeated data for compression aaabbbccc";
let compressed = codec.compress(data).unwrap();
let decompressed = codec.decompress(&compressed, data.len()).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn zstd_id() {
assert_eq!(ZstdCodec::default().id(), CodecId::Named("zstd".into()));
}
#[test]
fn lz4_roundtrip() {
let codec = Lz4Codec;
let data = b"aaabbbccc repeated data for compression aaabbbccc";
let compressed = codec.compress(data).unwrap();
let decompressed = codec.decompress(&compressed, data.len()).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn lz4_id() {
assert_eq!(Lz4Codec.id(), CodecId::Named("lz4".into()));
}
}