1use crate::error::{Error, Result};
31use bytes::Bytes;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
35pub enum CompressionAlgorithm {
36 #[default]
38 None,
39 Zstd,
41 Lz4,
43}
44
45impl CompressionAlgorithm {
46 #[inline]
48 pub fn name(&self) -> &'static str {
49 match self {
50 CompressionAlgorithm::None => "none",
51 CompressionAlgorithm::Zstd => "zstd",
52 CompressionAlgorithm::Lz4 => "lz4",
53 }
54 }
55
56 #[inline]
58 pub fn is_compressed(&self) -> bool {
59 !matches!(self, CompressionAlgorithm::None)
60 }
61
62 pub fn all() -> &'static [CompressionAlgorithm] {
64 &[
65 CompressionAlgorithm::None,
66 CompressionAlgorithm::Zstd,
67 CompressionAlgorithm::Lz4,
68 ]
69 }
70}
71
72impl std::fmt::Display for CompressionAlgorithm {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 write!(f, "{}", self.name())
75 }
76}
77
78pub fn compress(data: &Bytes, algorithm: CompressionAlgorithm, level: u8) -> Result<Bytes> {
100 if level > 9 {
101 return Err(Error::InvalidInput(format!(
102 "Invalid compression level {}, must be 0-9",
103 level
104 )));
105 }
106
107 match algorithm {
108 CompressionAlgorithm::None => Ok(data.clone()),
109 CompressionAlgorithm::Zstd => compress_zstd(data, level),
110 CompressionAlgorithm::Lz4 => compress_lz4(data, level),
111 }
112}
113
114pub fn decompress(data: &Bytes, algorithm: CompressionAlgorithm) -> Result<Bytes> {
137 match algorithm {
138 CompressionAlgorithm::None => Ok(data.clone()),
139 CompressionAlgorithm::Zstd => decompress_zstd(data),
140 CompressionAlgorithm::Lz4 => decompress_lz4(data),
141 }
142}
143
144fn compress_zstd(data: &Bytes, level: u8) -> Result<Bytes> {
146 let zstd_level = if level == 0 {
149 1
150 } else {
151 1 + (level as i32 * 21 / 9)
152 };
153
154 let compressed = oxiarc_zstd::compress_with_level(data, zstd_level)
155 .map_err(|e| Error::Internal(format!("Zstd compression failed: {}", e)))?;
156 Ok(Bytes::from(compressed))
157}
158
159fn decompress_zstd(data: &Bytes) -> Result<Bytes> {
161 let decompressed = oxiarc_zstd::decompress(data)
163 .map_err(|e| Error::Internal(format!("Zstd decompression failed: {}", e)))?;
164 Ok(Bytes::from(decompressed))
165}
166
167fn compress_lz4(data: &Bytes, _level: u8) -> Result<Bytes> {
173 let payload = oxiarc_lz4::compress(data)
175 .map_err(|e| Error::Internal(format!("LZ4 compression failed: {}", e)))?;
176 let orig_len = data.len() as u32;
177 let mut out = Vec::with_capacity(4 + payload.len());
178 out.extend_from_slice(&orig_len.to_le_bytes());
179 out.extend_from_slice(&payload);
180 Ok(Bytes::from(out))
181}
182
183fn decompress_lz4(data: &Bytes) -> Result<Bytes> {
185 if data.len() < 4 {
186 return Err(Error::Internal(
187 "LZ4 block too short (missing original size header)".to_string(),
188 ));
189 }
190 let orig_size = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
191 let max_output = orig_size.saturating_mul(2).max(orig_size + 64);
193 let decompressed = oxiarc_lz4::decompress(&data[4..], max_output)
194 .map_err(|e| Error::Internal(format!("LZ4 decompression failed: {}", e)))?;
195 Ok(Bytes::from(decompressed))
196}
197
198pub fn compression_ratio(data: &Bytes, algorithm: CompressionAlgorithm, level: u8) -> Result<f64> {
214 if data.is_empty() {
215 return Ok(0.0);
216 }
217
218 let compressed = compress(data, algorithm, level)?;
219 Ok(compressed.len() as f64 / data.len() as f64)
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_compression_none() {
228 let data = Bytes::from_static(b"Hello, World!");
229 let compressed = compress(&data, CompressionAlgorithm::None, 5).unwrap();
230 assert_eq!(data, compressed);
231
232 let decompressed = decompress(&compressed, CompressionAlgorithm::None).unwrap();
233 assert_eq!(data, decompressed);
234 }
235
236 #[test]
237 fn test_compression_zstd() {
238 let data = Bytes::from("Hello, World! ".repeat(100));
240 let compressed = compress(&data, CompressionAlgorithm::Zstd, 5).unwrap();
241
242 assert!(compressed.len() < data.len());
244
245 let decompressed = decompress(&compressed, CompressionAlgorithm::Zstd).unwrap();
246 assert_eq!(data, decompressed);
247 }
248
249 #[test]
250 fn test_compression_lz4() {
251 let data = Bytes::from("Hello, World! ".repeat(100));
253 let compressed = compress(&data, CompressionAlgorithm::Lz4, 5).unwrap();
254
255 assert!(compressed.len() < data.len());
257
258 let decompressed = decompress(&compressed, CompressionAlgorithm::Lz4).unwrap();
259 assert_eq!(data, decompressed);
260 }
261
262 #[test]
263 fn test_compression_levels() {
264 let data = Bytes::from_static(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); let compressed_0 = compress(&data, CompressionAlgorithm::Zstd, 0).unwrap();
268
269 let compressed_9 = compress(&data, CompressionAlgorithm::Zstd, 9).unwrap();
271
272 let decompressed_0 = decompress(&compressed_0, CompressionAlgorithm::Zstd).unwrap();
274 let decompressed_9 = decompress(&compressed_9, CompressionAlgorithm::Zstd).unwrap();
275
276 assert_eq!(data, decompressed_0);
277 assert_eq!(data, decompressed_9);
278
279 assert!(compressed_9.len() <= compressed_0.len());
281 }
282
283 #[test]
284 fn test_invalid_compression_level() {
285 let data = Bytes::from_static(b"Hello");
286 let result = compress(&data, CompressionAlgorithm::Zstd, 10);
287 assert!(result.is_err());
288 }
289
290 #[test]
291 fn test_compression_ratio() {
292 let data = Bytes::from("a".repeat(1000));
293 let ratio = compression_ratio(&data, CompressionAlgorithm::Zstd, 5).unwrap();
294 assert!(ratio < 0.1); }
296
297 #[test]
298 fn test_compression_algorithm_name() {
299 assert_eq!(CompressionAlgorithm::None.name(), "none");
300 assert_eq!(CompressionAlgorithm::Zstd.name(), "zstd");
301 assert_eq!(CompressionAlgorithm::Lz4.name(), "lz4");
302 }
303
304 #[test]
305 fn test_compression_algorithm_is_compressed() {
306 assert!(!CompressionAlgorithm::None.is_compressed());
307 assert!(CompressionAlgorithm::Zstd.is_compressed());
308 assert!(CompressionAlgorithm::Lz4.is_compressed());
309 }
310
311 #[test]
312 fn test_compression_algorithm_all() {
313 let all = CompressionAlgorithm::all();
314 assert_eq!(all.len(), 3);
315 assert!(all.contains(&CompressionAlgorithm::None));
316 assert!(all.contains(&CompressionAlgorithm::Zstd));
317 assert!(all.contains(&CompressionAlgorithm::Lz4));
318 }
319
320 #[test]
321 fn test_empty_data() {
322 let data = Bytes::new();
323 let compressed = compress(&data, CompressionAlgorithm::Zstd, 5).unwrap();
324 let decompressed = decompress(&compressed, CompressionAlgorithm::Zstd).unwrap();
325 assert_eq!(data, decompressed);
326 }
327
328 #[test]
329 fn test_large_data() {
330 let data = Bytes::from(vec![42u8; 1_000_000]); let compressed = compress(&data, CompressionAlgorithm::Zstd, 5).unwrap();
332
333 assert!(compressed.len() < data.len() / 100);
335
336 let decompressed = decompress(&compressed, CompressionAlgorithm::Zstd).unwrap();
337 assert_eq!(data, decompressed);
338 }
339
340 #[test]
341 fn test_algorithm_display() {
342 assert_eq!(CompressionAlgorithm::None.to_string(), "none");
343 assert_eq!(CompressionAlgorithm::Zstd.to_string(), "zstd");
344 assert_eq!(CompressionAlgorithm::Lz4.to_string(), "lz4");
345 }
346
347 #[test]
348 fn test_all_algorithms_roundtrip() {
349 let data = Bytes::from_static(b"The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.");
350
351 for algorithm in CompressionAlgorithm::all() {
352 for level in 0..=9 {
353 let compressed = compress(&data, *algorithm, level).unwrap();
354 let decompressed = decompress(&compressed, *algorithm).unwrap();
355 assert_eq!(
356 data, decompressed,
357 "Failed for {:?} at level {}",
358 algorithm, level
359 );
360 }
361 }
362 }
363}