use crate::error::{Error, Result};
use crate::types::Compression;
pub fn compress(data: &[u8], compression: Compression) -> Result<Vec<u8>> {
match compression {
Compression::None => Ok(data.to_vec()),
Compression::Zstd => compress_zstd(data),
}
}
pub fn decompress(data: &[u8], compression: Compression) -> Result<Vec<u8>> {
match compression {
Compression::None => Ok(data.to_vec()),
Compression::Zstd => decompress_zstd(data),
}
}
pub const ZSTD_LEVEL: i32 = 3;
pub const ZSTD_LEVEL_MAX: i32 = 22;
fn compress_zstd(data: &[u8]) -> Result<Vec<u8>> {
zstd::stream::encode_all(data, ZSTD_LEVEL).map_err(|e| Error::Compression(e.to_string()))
}
fn decompress_zstd(data: &[u8]) -> Result<Vec<u8>> {
zstd::stream::decode_all(data).map_err(|e| Error::Decompression(e.to_string()))
}
pub fn compress_zstd_with_dict(data: &[u8], dict: Option<&[u8]>) -> Result<Vec<u8>> {
compress_zstd_with_dict_level(data, dict, ZSTD_LEVEL)
}
pub fn compress_zstd_with_dict_level(
data: &[u8],
dict: Option<&[u8]>,
level: i32,
) -> Result<Vec<u8>> {
let level = level.clamp(1, ZSTD_LEVEL_MAX);
let Some(dict) = dict else {
return zstd::stream::encode_all(data, level).map_err(|e| Error::Compression(e.to_string()));
};
let mut out = Vec::with_capacity(data.len() / 2 + 64);
let mut encoder = zstd::stream::Encoder::with_dictionary(&mut out, level, dict)
.map_err(|e| Error::Compression(e.to_string()))?;
use std::io::Write;
encoder
.write_all(data)
.map_err(|e| Error::Compression(e.to_string()))?;
encoder
.finish()
.map_err(|e| Error::Compression(e.to_string()))?;
Ok(out)
}
pub fn decompress_zstd_with_dict(data: &[u8], dict: Option<&[u8]>) -> Result<Vec<u8>> {
let Some(dict) = dict else {
return decompress_zstd(data);
};
let mut decoder = zstd::stream::Decoder::with_dictionary(data, dict)
.map_err(|e| Error::Decompression(e.to_string()))?;
let mut out = Vec::new();
use std::io::Read as _;
decoder
.read_to_end(&mut out)
.map_err(|e| Error::Decompression(e.to_string()))?;
Ok(out)
}
pub fn train_zstd_dictionary(samples: &[Vec<u8>], max_size: usize) -> Option<Vec<u8>> {
let slices: Vec<&[u8]> = samples.iter().map(|s| s.as_slice()).collect();
train_zstd_dictionary_from_slices(&slices, max_size)
}
pub fn train_zstd_dictionary_from_slices(samples: &[&[u8]], max_size: usize) -> Option<Vec<u8>> {
if samples.is_empty() {
return None;
}
match zstd::dict::from_samples(samples, max_size) {
Ok(dict) if !dict.is_empty() => Some(dict),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zstd_roundtrip() {
let data = b"Hello, world! This is a test string that should compress well."
.repeat(50);
let compressed = compress(&data, Compression::Zstd).unwrap();
assert!(compressed.len() < data.len());
let decompressed = decompress(&compressed, Compression::Zstd).unwrap();
assert_eq!(decompressed, data);
}
#[test]
fn test_none_roundtrip() {
let data = b"No compression";
let compressed = compress(data, Compression::None).unwrap();
assert_eq!(&compressed, data);
let decompressed = decompress(&compressed, Compression::None).unwrap();
assert_eq!(&decompressed, data);
}
}