1use crate::error::{Error, Result};
6use crate::types::Compression;
7
8pub fn compress(data: &[u8], compression: Compression) -> Result<Vec<u8>> {
10 match compression {
11 Compression::None => Ok(data.to_vec()),
12 Compression::Zstd => compress_zstd(data),
13 }
14}
15
16pub fn decompress(data: &[u8], compression: Compression) -> Result<Vec<u8>> {
18 match compression {
19 Compression::None => Ok(data.to_vec()),
20 Compression::Zstd => decompress_zstd(data),
21 }
22}
23
24pub const ZSTD_LEVEL: i32 = 3;
31
32pub const ZSTD_LEVEL_MAX: i32 = 22;
35
36fn compress_zstd(data: &[u8]) -> Result<Vec<u8>> {
37 zstd::stream::encode_all(data, ZSTD_LEVEL).map_err(|e| Error::Compression(e.to_string()))
38}
39
40fn decompress_zstd(data: &[u8]) -> Result<Vec<u8>> {
41 zstd::stream::decode_all(data).map_err(|e| Error::Decompression(e.to_string()))
42}
43
44pub fn compress_zstd_with_dict(data: &[u8], dict: Option<&[u8]>) -> Result<Vec<u8>> {
49 compress_zstd_with_dict_level(data, dict, ZSTD_LEVEL)
50}
51
52pub fn compress_zstd_with_dict_level(
62 data: &[u8],
63 dict: Option<&[u8]>,
64 level: i32,
65) -> Result<Vec<u8>> {
66 let level = level.clamp(1, ZSTD_LEVEL_MAX);
67 let Some(dict) = dict else {
68 return zstd::stream::encode_all(data, level).map_err(|e| Error::Compression(e.to_string()));
69 };
70 let mut out = Vec::with_capacity(data.len() / 2 + 64);
71 let mut encoder = zstd::stream::Encoder::with_dictionary(&mut out, level, dict)
72 .map_err(|e| Error::Compression(e.to_string()))?;
73 use std::io::Write;
74 encoder
75 .write_all(data)
76 .map_err(|e| Error::Compression(e.to_string()))?;
77 encoder
78 .finish()
79 .map_err(|e| Error::Compression(e.to_string()))?;
80 Ok(out)
81}
82
83pub fn decompress_zstd_with_dict(data: &[u8], dict: Option<&[u8]>) -> Result<Vec<u8>> {
85 let Some(dict) = dict else {
86 return decompress_zstd(data);
87 };
88 let mut decoder = zstd::stream::Decoder::with_dictionary(data, dict)
89 .map_err(|e| Error::Decompression(e.to_string()))?;
90 let mut out = Vec::new();
91 use std::io::Read as _;
92 decoder
93 .read_to_end(&mut out)
94 .map_err(|e| Error::Decompression(e.to_string()))?;
95 Ok(out)
96}
97
98pub fn train_zstd_dictionary(samples: &[Vec<u8>], max_size: usize) -> Option<Vec<u8>> {
104 let slices: Vec<&[u8]> = samples.iter().map(|s| s.as_slice()).collect();
105 train_zstd_dictionary_from_slices(&slices, max_size)
106}
107
108pub fn train_zstd_dictionary_from_slices(samples: &[&[u8]], max_size: usize) -> Option<Vec<u8>> {
114 if samples.is_empty() {
115 return None;
116 }
117 match zstd::dict::from_samples(samples, max_size) {
118 Ok(dict) if !dict.is_empty() => Some(dict),
119 _ => None,
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_zstd_roundtrip() {
129 let data = b"Hello, world! This is a test string that should compress well."
130 .repeat(50);
131 let compressed = compress(&data, Compression::Zstd).unwrap();
132 assert!(compressed.len() < data.len());
133
134 let decompressed = decompress(&compressed, Compression::Zstd).unwrap();
135 assert_eq!(decompressed, data);
136 }
137
138 #[test]
139 fn test_none_roundtrip() {
140 let data = b"No compression";
141 let compressed = compress(data, Compression::None).unwrap();
142 assert_eq!(&compressed, data);
143
144 let decompressed = decompress(&compressed, Compression::None).unwrap();
145 assert_eq!(&decompressed, data);
146 }
147}