Skip to main content

sentinel_wal/compression/
mod.rs

1//! Compression algorithms for WAL file rotation
2
3pub mod brotli;
4pub mod deflate;
5pub mod gzip;
6pub mod lz4;
7pub mod zstd;
8
9pub use zstd::ZstdCompressor;
10pub use lz4::Lz4Compressor;
11pub use brotli::BrotliCompressor;
12pub use deflate::DeflateCompressor;
13pub use gzip::GzipCompressor;
14
15/// Compression algorithms available for WAL file rotation
16#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17pub enum CompressionAlgorithm {
18    /// Zstandard compression: Best overall choice for WAL files.
19    /// Provides excellent compression ratio (better than gzip) with fast compression/decompression
20    /// speeds. Ideal for production environments where storage space is important but
21    /// performance is critical. Recommended for most use cases.
22    Zstd,
23    /// LZ4 compression: Fastest compression and decompression.
24    /// Lower compression ratio than Zstd but very fast.
25    /// Suitable for high-throughput environments where speed is more important than compression
26    /// ratio. Good for real-time systems with limited CPU resources.
27    Lz4,
28    /// Brotli compression: Highest compression ratio.
29    /// Slower than Zstd but achieves better compression.
30    /// Best for archival or low-frequency rotation scenarios where maximum compression is desired.
31    /// Use when storage space is at a premium and compression time is not critical.
32    Brotli,
33    /// DEFLATE compression: Standard compression algorithm.
34    /// Balanced performance with good compatibility.
35    /// Suitable for environments requiring standard compression formats.
36    /// Good default for general-purpose use.
37    Deflate,
38    /// GZIP compression: DEFLATE with gzip header.
39    /// Widely compatible and standard for many systems.
40    /// Slightly slower than DEFLATE due to header overhead.
41    /// Use when compatibility with existing gzip tools is required.
42    Gzip,
43}
44
45impl std::str::FromStr for CompressionAlgorithm {
46    type Err = String;
47
48    fn from_str(s: &str) -> Result<Self, Self::Err> {
49        match s.to_lowercase().as_str() {
50            "zstd" => Ok(Self::Zstd),
51            "lz4" => Ok(Self::Lz4),
52            "brotli" => Ok(Self::Brotli),
53            "deflate" => Ok(Self::Deflate),
54            "gzip" => Ok(Self::Gzip),
55            _ => Err(format!("Invalid compression algorithm: {}", s)),
56        }
57    }
58}
59
60impl std::fmt::Display for CompressionAlgorithm {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        match *self {
63            Self::Zstd => write!(f, "zstd"),
64            Self::Lz4 => write!(f, "lz4"),
65            Self::Brotli => write!(f, "brotli"),
66            Self::Deflate => write!(f, "deflate"),
67            Self::Gzip => write!(f, "gzip"),
68        }
69    }
70}
71
72/// Trait for compression implementations
73#[async_trait::async_trait]
74pub trait CompressionTrait {
75    /// Compress data
76    async fn compress(&self, data: &[u8]) -> crate::Result<Vec<u8>>;
77    /// Decompress data
78    async fn decompress(&self, data: &[u8]) -> crate::Result<Vec<u8>>;
79}
80
81/// Get a compressor instance for the given algorithm
82pub fn get_compressor(algorithm: CompressionAlgorithm) -> Box<dyn CompressionTrait + Send + Sync> {
83    match algorithm {
84        CompressionAlgorithm::Zstd => Box::new(ZstdCompressor),
85        CompressionAlgorithm::Lz4 => Box::new(Lz4Compressor),
86        CompressionAlgorithm::Brotli => Box::new(BrotliCompressor),
87        CompressionAlgorithm::Deflate => Box::new(DeflateCompressor),
88        CompressionAlgorithm::Gzip => Box::new(GzipCompressor),
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use std::str::FromStr;
95
96    use super::*;
97
98    #[test]
99    fn test_compression_algorithm_from_str_valid() {
100        assert_eq!(
101            CompressionAlgorithm::from_str("zstd").unwrap(),
102            CompressionAlgorithm::Zstd
103        );
104        assert_eq!(
105            CompressionAlgorithm::from_str("lz4").unwrap(),
106            CompressionAlgorithm::Lz4
107        );
108        assert_eq!(
109            CompressionAlgorithm::from_str("brotli").unwrap(),
110            CompressionAlgorithm::Brotli
111        );
112        assert_eq!(
113            CompressionAlgorithm::from_str("deflate").unwrap(),
114            CompressionAlgorithm::Deflate
115        );
116        assert_eq!(
117            CompressionAlgorithm::from_str("gzip").unwrap(),
118            CompressionAlgorithm::Gzip
119        );
120    }
121
122    #[test]
123    fn test_compression_algorithm_from_str_case_insensitive() {
124        assert_eq!(
125            CompressionAlgorithm::from_str("ZSTD").unwrap(),
126            CompressionAlgorithm::Zstd
127        );
128        assert_eq!(
129            CompressionAlgorithm::from_str("Lz4").unwrap(),
130            CompressionAlgorithm::Lz4
131        );
132        assert_eq!(
133            CompressionAlgorithm::from_str("BrOtLi").unwrap(),
134            CompressionAlgorithm::Brotli
135        );
136    }
137
138    #[test]
139    fn test_compression_algorithm_from_str_invalid() {
140        assert!(CompressionAlgorithm::from_str("invalid").is_err());
141        assert!(CompressionAlgorithm::from_str("foobar").is_err());
142        assert!(CompressionAlgorithm::from_str("").is_err());
143    }
144
145    #[test]
146    fn test_compression_algorithm_display() {
147        assert_eq!(CompressionAlgorithm::Zstd.to_string(), "zstd");
148        assert_eq!(CompressionAlgorithm::Lz4.to_string(), "lz4");
149        assert_eq!(CompressionAlgorithm::Brotli.to_string(), "brotli");
150        assert_eq!(CompressionAlgorithm::Deflate.to_string(), "deflate");
151        assert_eq!(CompressionAlgorithm::Gzip.to_string(), "gzip");
152    }
153
154    #[test]
155    fn test_compression_algorithm_debug() {
156        let algo = CompressionAlgorithm::Zstd;
157        let debug_str = format!("{:?}", algo);
158        assert!(debug_str.contains("Zstd"));
159    }
160
161    #[test]
162    fn test_compression_algorithm_serialization() {
163        let algorithms = vec![
164            CompressionAlgorithm::Zstd,
165            CompressionAlgorithm::Lz4,
166            CompressionAlgorithm::Brotli,
167            CompressionAlgorithm::Deflate,
168            CompressionAlgorithm::Gzip,
169        ];
170
171        for algo in algorithms {
172            let serialized = serde_json::to_string(&algo).unwrap();
173            let deserialized: CompressionAlgorithm = serde_json::from_str(&serialized).unwrap();
174            assert_eq!(algo, deserialized);
175        }
176    }
177
178    #[test]
179    fn test_compression_algorithm_equality() {
180        assert_eq!(CompressionAlgorithm::Zstd, CompressionAlgorithm::Zstd);
181        assert_ne!(CompressionAlgorithm::Zstd, CompressionAlgorithm::Lz4);
182    }
183
184    #[test]
185    fn test_compression_algorithm_clone() {
186        let original = CompressionAlgorithm::Zstd;
187        let cloned = original.clone();
188        assert_eq!(original, cloned);
189    }
190
191    #[test]
192    fn test_get_compressor_zstd() {
193        let _compressor = get_compressor(CompressionAlgorithm::Zstd);
194        // Verify it returns a valid compressor trait object
195        let _trait_obj: &dyn CompressionTrait = &*_compressor;
196    }
197
198    #[test]
199    fn test_get_compressor_lz4() {
200        let _compressor = get_compressor(CompressionAlgorithm::Lz4);
201        let _trait_obj: &dyn CompressionTrait = &*_compressor;
202    }
203
204    #[test]
205    fn test_get_compressor_brotli() {
206        let _compressor = get_compressor(CompressionAlgorithm::Brotli);
207        let _trait_obj: &dyn CompressionTrait = &*_compressor;
208    }
209
210    #[test]
211    fn test_get_compressor_deflate() {
212        let _compressor = get_compressor(CompressionAlgorithm::Deflate);
213        let _trait_obj: &dyn CompressionTrait = &*_compressor;
214    }
215
216    #[test]
217    fn test_get_compressor_gzip() {
218        let _compressor = get_compressor(CompressionAlgorithm::Gzip);
219        let _trait_obj: &dyn CompressionTrait = &*_compressor;
220    }
221
222    #[tokio::test]
223    async fn test_compression_roundtrip_zstd() {
224        let compressor = get_compressor(CompressionAlgorithm::Zstd);
225        let original_data = b"Hello, World! This is test data for compression roundtrip testing.";
226
227        let compressed = compressor.compress(original_data).await.unwrap();
228        assert!(compressed.len() > 0);
229
230        let decompressed = compressor.decompress(&compressed).await.unwrap();
231        assert_eq!(decompressed, original_data);
232    }
233
234    #[tokio::test]
235    async fn test_compression_roundtrip_lz4() {
236        let compressor = get_compressor(CompressionAlgorithm::Lz4);
237        let original_data = b"Hello, World! This is test data for compression roundtrip testing.";
238
239        let compressed = compressor.compress(original_data).await.unwrap();
240        let decompressed = compressor.decompress(&compressed).await.unwrap();
241        assert_eq!(decompressed, original_data);
242    }
243
244    #[tokio::test]
245    async fn test_compression_roundtrip_brotli() {
246        let compressor = get_compressor(CompressionAlgorithm::Brotli);
247        let original_data = b"Hello, World! This is test data for compression roundtrip testing.";
248
249        let compressed = compressor.compress(original_data).await.unwrap();
250        let decompressed = compressor.decompress(&compressed).await.unwrap();
251        assert_eq!(decompressed, original_data);
252    }
253
254    #[tokio::test]
255    async fn test_compression_roundtrip_deflate() {
256        let compressor = get_compressor(CompressionAlgorithm::Deflate);
257        let original_data = b"Hello, World! This is test data for compression roundtrip testing.";
258
259        let compressed = compressor.compress(original_data).await.unwrap();
260        let decompressed = compressor.decompress(&compressed).await.unwrap();
261        assert_eq!(decompressed, original_data);
262    }
263
264    #[tokio::test]
265    async fn test_compression_roundtrip_gzip() {
266        let compressor = get_compressor(CompressionAlgorithm::Gzip);
267        let original_data = b"Hello, World! This is test data for compression roundtrip testing.";
268
269        let compressed = compressor.compress(original_data).await.unwrap();
270        let decompressed = compressor.decompress(&compressed).await.unwrap();
271        assert_eq!(decompressed, original_data);
272    }
273
274    #[tokio::test]
275    async fn test_compression_empty_data() {
276        let algorithms = vec![
277            CompressionAlgorithm::Zstd,
278            CompressionAlgorithm::Lz4,
279            CompressionAlgorithm::Brotli,
280            CompressionAlgorithm::Deflate,
281            CompressionAlgorithm::Gzip,
282        ];
283
284        for algo in algorithms {
285            let compressor = get_compressor(algo);
286            let empty_data = b"";
287
288            let compressed = compressor.compress(empty_data).await.unwrap();
289            let decompressed = compressor.decompress(&compressed).await.unwrap();
290            assert_eq!(decompressed, empty_data);
291        }
292    }
293
294    #[tokio::test]
295    async fn test_compression_large_data() {
296        let compressor = get_compressor(CompressionAlgorithm::Zstd);
297        let original_data: Vec<u8> = (0 .. 10000).map(|i| (i % 256) as u8).collect();
298
299        let compressed = compressor.compress(&original_data).await.unwrap();
300        // Compression should reduce size for repetitive data
301        assert!(compressed.len() < original_data.len());
302
303        let decompressed = compressor.decompress(&compressed).await.unwrap();
304        assert_eq!(decompressed, original_data);
305    }
306
307    #[test]
308    fn test_compression_algorithm_all_variants_covered() {
309        // Ensure all enum variants are covered
310        let algorithms = vec![
311            CompressionAlgorithm::Zstd,
312            CompressionAlgorithm::Lz4,
313            CompressionAlgorithm::Brotli,
314            CompressionAlgorithm::Deflate,
315            CompressionAlgorithm::Gzip,
316        ];
317
318        for algo in algorithms {
319            match algo {
320                CompressionAlgorithm::Zstd => assert_eq!(algo.to_string(), "zstd"),
321                CompressionAlgorithm::Lz4 => assert_eq!(algo.to_string(), "lz4"),
322                CompressionAlgorithm::Brotli => assert_eq!(algo.to_string(), "brotli"),
323                CompressionAlgorithm::Deflate => assert_eq!(algo.to_string(), "deflate"),
324                CompressionAlgorithm::Gzip => assert_eq!(algo.to_string(), "gzip"),
325            }
326        }
327    }
328}