sentinel_wal/compression/
mod.rs1pub 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#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17pub enum CompressionAlgorithm {
18 Zstd,
23 Lz4,
28 Brotli,
33 Deflate,
38 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#[async_trait::async_trait]
74pub trait CompressionTrait {
75 async fn compress(&self, data: &[u8]) -> crate::Result<Vec<u8>>;
77 async fn decompress(&self, data: &[u8]) -> crate::Result<Vec<u8>>;
79}
80
81pub 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 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 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 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}