1use crate::storage::format::FormatError;
6
7#[cfg(feature = "compression-zstd")]
8use std::io::Cursor;
9
10#[repr(u8)]
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum CompressionAlgorithm {
14 None = 0,
16 Snappy = 1,
18 Zstd = 2,
20 Lz4 = 3,
22}
23
24pub fn compress(data: &[u8], algorithm: CompressionAlgorithm) -> Result<Vec<u8>, FormatError> {
29 match algorithm {
30 CompressionAlgorithm::None => Ok(data.to_vec()),
31 CompressionAlgorithm::Snappy => {
32 snap::raw::Encoder::new().compress_vec(data).map_err(|_| {
33 FormatError::CompressionFailed {
34 algorithm: algorithm as u8,
35 }
36 })
37 }
38 CompressionAlgorithm::Zstd => {
39 #[cfg(feature = "compression-zstd")]
40 {
41 zstd::stream::encode_all(Cursor::new(data), 0).map_err(|_| {
42 FormatError::CompressionFailed {
43 algorithm: algorithm as u8,
44 }
45 })
46 }
47 #[cfg(not(feature = "compression-zstd"))]
48 {
49 Err(FormatError::UnsupportedCompression {
50 algorithm: algorithm as u8,
51 })
52 }
53 }
54 CompressionAlgorithm::Lz4 => {
55 #[cfg(feature = "compression-lz4")]
56 {
57 lz4::block::compress(data, None, true).map_err(|_| FormatError::CompressionFailed {
60 algorithm: algorithm as u8,
61 })
62 }
63 #[cfg(not(feature = "compression-lz4"))]
64 {
65 Err(FormatError::UnsupportedCompression {
66 algorithm: algorithm as u8,
67 })
68 }
69 }
70 }
71}
72
73pub fn decompress(data: &[u8], algorithm: CompressionAlgorithm) -> Result<Vec<u8>, FormatError> {
78 match algorithm {
79 CompressionAlgorithm::None => Ok(data.to_vec()),
80 CompressionAlgorithm::Snappy => {
81 snap::raw::Decoder::new().decompress_vec(data).map_err(|_| {
82 FormatError::DecompressionFailed {
83 algorithm: algorithm as u8,
84 }
85 })
86 }
87 CompressionAlgorithm::Zstd => {
88 #[cfg(feature = "compression-zstd")]
89 {
90 zstd::stream::decode_all(Cursor::new(data)).map_err(|_| {
91 FormatError::DecompressionFailed {
92 algorithm: algorithm as u8,
93 }
94 })
95 }
96 #[cfg(not(feature = "compression-zstd"))]
97 {
98 Err(FormatError::UnsupportedCompression {
99 algorithm: algorithm as u8,
100 })
101 }
102 }
103 CompressionAlgorithm::Lz4 => {
104 #[cfg(feature = "compression-lz4")]
105 {
106 lz4::block::decompress(data, None).map_err(|_| FormatError::DecompressionFailed {
107 algorithm: algorithm as u8,
108 })
109 }
110 #[cfg(not(feature = "compression-lz4"))]
111 {
112 Err(FormatError::UnsupportedCompression {
113 algorithm: algorithm as u8,
114 })
115 }
116 }
117 }
118}
119
120use serde::{Deserialize, Serialize};
125
126#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
128pub enum CompressionV2 {
129 #[default]
131 None,
132 Lz4,
134 Zstd {
136 level: i32,
138 },
139}
140
141impl CompressionV2 {
142 pub fn is_available(&self) -> bool {
144 match self {
145 CompressionV2::None => true,
146 CompressionV2::Lz4 => cfg!(feature = "compression-lz4"),
147 CompressionV2::Zstd { .. } => cfg!(feature = "compression-zstd"),
148 }
149 }
150
151 pub fn name(&self) -> &'static str {
153 match self {
154 CompressionV2::None => "none",
155 CompressionV2::Lz4 => "lz4",
156 CompressionV2::Zstd { .. } => "zstd",
157 }
158 }
159}
160
161pub trait Compressor: Send + Sync {
163 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, FormatError>;
165
166 fn decompress(&self, data: &[u8], uncompressed_size: usize) -> Result<Vec<u8>, FormatError>;
168
169 fn compression_type(&self) -> CompressionV2;
171}
172
173pub struct NoneCompressor;
175
176impl Compressor for NoneCompressor {
177 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, FormatError> {
178 Ok(data.to_vec())
179 }
180
181 fn decompress(&self, data: &[u8], _uncompressed_size: usize) -> Result<Vec<u8>, FormatError> {
182 Ok(data.to_vec())
183 }
184
185 fn compression_type(&self) -> CompressionV2 {
186 CompressionV2::None
187 }
188}
189
190#[cfg(feature = "compression-lz4")]
192pub struct Lz4Compressor;
193
194#[cfg(feature = "compression-lz4")]
195impl Compressor for Lz4Compressor {
196 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, FormatError> {
197 lz4::block::compress(data, None, true).map_err(|_| FormatError::CompressionFailed {
198 algorithm: CompressionAlgorithm::Lz4 as u8,
199 })
200 }
201
202 fn decompress(&self, data: &[u8], _uncompressed_size: usize) -> Result<Vec<u8>, FormatError> {
203 lz4::block::decompress(data, None).map_err(|_| FormatError::DecompressionFailed {
204 algorithm: CompressionAlgorithm::Lz4 as u8,
205 })
206 }
207
208 fn compression_type(&self) -> CompressionV2 {
209 CompressionV2::Lz4
210 }
211}
212
213#[cfg(feature = "compression-zstd")]
215pub struct ZstdCompressor {
216 level: i32,
217}
218
219#[cfg(feature = "compression-zstd")]
220impl ZstdCompressor {
221 pub fn new(level: i32) -> Self {
224 Self {
225 level: level.clamp(1, 22),
226 }
227 }
228}
229
230#[cfg(feature = "compression-zstd")]
231impl Compressor for ZstdCompressor {
232 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, FormatError> {
233 zstd::stream::encode_all(Cursor::new(data), self.level).map_err(|_| {
234 FormatError::CompressionFailed {
235 algorithm: CompressionAlgorithm::Zstd as u8,
236 }
237 })
238 }
239
240 fn decompress(&self, data: &[u8], _uncompressed_size: usize) -> Result<Vec<u8>, FormatError> {
241 zstd::stream::decode_all(Cursor::new(data)).map_err(|_| FormatError::DecompressionFailed {
242 algorithm: CompressionAlgorithm::Zstd as u8,
243 })
244 }
245
246 fn compression_type(&self) -> CompressionV2 {
247 CompressionV2::Zstd { level: self.level }
248 }
249}
250
251pub fn create_compressor(compression: CompressionV2) -> Result<Box<dyn Compressor>, FormatError> {
255 match compression {
256 CompressionV2::None => Ok(Box::new(NoneCompressor)),
257
258 CompressionV2::Lz4 => {
259 #[cfg(feature = "compression-lz4")]
260 {
261 Ok(Box::new(Lz4Compressor))
262 }
263 #[cfg(not(feature = "compression-lz4"))]
264 {
265 Err(FormatError::UnsupportedCompression {
266 algorithm: CompressionAlgorithm::Lz4 as u8,
267 })
268 }
269 }
270
271 CompressionV2::Zstd { level } => {
272 #[cfg(feature = "compression-zstd")]
273 {
274 Ok(Box::new(ZstdCompressor::new(level)))
275 }
276 #[cfg(not(feature = "compression-zstd"))]
277 {
278 let _ = level; Err(FormatError::UnsupportedCompression {
280 algorithm: CompressionAlgorithm::Zstd as u8,
281 })
282 }
283 }
284 }
285}
286
287#[cfg(all(test, not(target_arch = "wasm32")))]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn test_none_compressor_roundtrip() {
293 let compressor = NoneCompressor;
294 let data = b"hello, world!";
295
296 let compressed = compressor.compress(data).unwrap();
297 assert_eq!(compressed, data);
298
299 let decompressed = compressor.decompress(&compressed, data.len()).unwrap();
300 assert_eq!(decompressed, data);
301 }
302
303 #[cfg(feature = "compression-lz4")]
304 #[test]
305 fn test_lz4_compress_decompress() {
306 let compressor = Lz4Compressor;
307 let data = b"hello, world! this is a test string for lz4 compression.";
308
309 let compressed = compressor.compress(data).unwrap();
310 let decompressed = compressor.decompress(&compressed, data.len()).unwrap();
311 assert_eq!(decompressed, data);
312 }
313
314 #[cfg(feature = "compression-lz4")]
315 #[test]
316 fn test_lz4_compressor_via_factory() {
317 let compressor = create_compressor(CompressionV2::Lz4).unwrap();
318 let data = b"test data for lz4";
319
320 let compressed = compressor.compress(data).unwrap();
321 let decompressed = compressor.decompress(&compressed, data.len()).unwrap();
322 assert_eq!(decompressed, data);
323 assert_eq!(compressor.compression_type(), CompressionV2::Lz4);
324 }
325
326 #[cfg(feature = "compression-zstd")]
327 #[test]
328 fn test_zstd_compress_decompress_levels() {
329 let data = b"hello, world! this is a test string for zstd compression at various levels.";
330
331 for level in [1, 3, 9, 15, 22] {
332 let compressor = ZstdCompressor::new(level);
333 let compressed = compressor.compress(data).unwrap();
334 let decompressed = compressor.decompress(&compressed, data.len()).unwrap();
335 assert_eq!(decompressed, data, "Failed at level {}", level);
336 }
337 }
338
339 #[cfg(feature = "compression-zstd")]
340 #[test]
341 fn test_zstd_compressor_via_factory() {
342 let compressor = create_compressor(CompressionV2::Zstd { level: 3 }).unwrap();
343 let data = b"test data for zstd";
344
345 let compressed = compressor.compress(data).unwrap();
346 let decompressed = compressor.decompress(&compressed, data.len()).unwrap();
347 assert_eq!(decompressed, data);
348 assert_eq!(
349 compressor.compression_type(),
350 CompressionV2::Zstd { level: 3 }
351 );
352 }
353
354 #[cfg(feature = "compression-zstd")]
355 #[test]
356 fn test_zstd_level_clamping() {
357 let compressor = ZstdCompressor::new(-5);
359 assert_eq!(
360 compressor.compression_type(),
361 CompressionV2::Zstd { level: 1 }
362 );
363
364 let compressor = ZstdCompressor::new(100);
366 assert_eq!(
367 compressor.compression_type(),
368 CompressionV2::Zstd { level: 22 }
369 );
370 }
371
372 #[test]
373 fn test_none_compressor_via_factory() {
374 let compressor = create_compressor(CompressionV2::None).unwrap();
375 let data = b"test data";
376
377 let compressed = compressor.compress(data).unwrap();
378 let decompressed = compressor.decompress(&compressed, data.len()).unwrap();
379 assert_eq!(decompressed, data);
380 assert_eq!(compressor.compression_type(), CompressionV2::None);
381 }
382
383 #[cfg(not(feature = "compression-lz4"))]
384 #[test]
385 fn test_unsupported_lz4_compression_error() {
386 let result = create_compressor(CompressionV2::Lz4);
387 assert!(result.is_err());
388 match result {
389 Err(FormatError::UnsupportedCompression { algorithm }) => {
390 assert_eq!(algorithm, CompressionAlgorithm::Lz4 as u8);
391 }
392 _ => panic!("Expected UnsupportedCompression error"),
393 }
394 }
395
396 #[cfg(not(feature = "compression-zstd"))]
397 #[test]
398 fn test_unsupported_zstd_compression_error() {
399 let result = create_compressor(CompressionV2::Zstd { level: 3 });
400 assert!(result.is_err());
401 match result {
402 Err(FormatError::UnsupportedCompression { algorithm }) => {
403 assert_eq!(algorithm, CompressionAlgorithm::Zstd as u8);
404 }
405 _ => panic!("Expected UnsupportedCompression error"),
406 }
407 }
408
409 #[test]
410 fn test_compression_v2_is_available() {
411 assert!(CompressionV2::None.is_available());
413
414 #[cfg(feature = "compression-lz4")]
416 assert!(CompressionV2::Lz4.is_available());
417 #[cfg(not(feature = "compression-lz4"))]
418 assert!(!CompressionV2::Lz4.is_available());
419
420 #[cfg(feature = "compression-zstd")]
422 assert!(CompressionV2::Zstd { level: 3 }.is_available());
423 #[cfg(not(feature = "compression-zstd"))]
424 assert!(!CompressionV2::Zstd { level: 3 }.is_available());
425 }
426
427 #[test]
428 fn test_compression_v2_name() {
429 assert_eq!(CompressionV2::None.name(), "none");
430 assert_eq!(CompressionV2::Lz4.name(), "lz4");
431 assert_eq!(CompressionV2::Zstd { level: 5 }.name(), "zstd");
432 }
433
434 #[test]
435 fn test_compression_v2_default() {
436 assert_eq!(CompressionV2::default(), CompressionV2::None);
437 }
438}