hitbox_backend/
compressor.rs1use thiserror::Error;
14
15#[derive(Debug, Error)]
17pub enum CompressionError {
18 #[error("Compression failed: {0}")]
20 CompressionFailed(String),
21
22 #[error("Decompression failed: {0}")]
24 DecompressionFailed(String),
25}
26
27pub trait Compressor: Send + Sync + std::fmt::Debug {
33 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError>;
35
36 fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError>;
38
39 fn clone_box(&self) -> Box<dyn Compressor>;
41}
42
43impl Compressor for Box<dyn Compressor> {
45 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
46 (**self).compress(data)
47 }
48
49 fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
50 (**self).decompress(data)
51 }
52
53 fn clone_box(&self) -> Box<dyn Compressor> {
54 (**self).clone_box()
55 }
56}
57
58impl Compressor for std::sync::Arc<dyn Compressor> {
60 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
61 (**self).compress(data)
62 }
63
64 fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
65 (**self).decompress(data)
66 }
67
68 fn clone_box(&self) -> Box<dyn Compressor> {
69 (**self).clone_box()
70 }
71}
72
73#[derive(Debug, Clone, Copy, Default)]
75pub struct PassthroughCompressor;
76
77impl Compressor for PassthroughCompressor {
78 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
79 Ok(data.to_vec())
80 }
81
82 fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
83 Ok(data.to_vec())
84 }
85
86 fn clone_box(&self) -> Box<dyn Compressor> {
87 Box::new(*self)
88 }
89}
90
91#[cfg(feature = "gzip")]
93#[cfg_attr(docsrs, doc(cfg(feature = "gzip")))]
94#[derive(Debug, Clone, Copy)]
95pub struct GzipCompressor {
96 level: u32,
97}
98
99#[cfg(feature = "gzip")]
100impl GzipCompressor {
101 pub fn new() -> Self {
103 Self { level: 6 }
104 }
105
106 pub fn with_level(level: u32) -> Self {
108 Self {
109 level: level.min(9),
110 }
111 }
112}
113
114#[cfg(feature = "gzip")]
115impl Default for GzipCompressor {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121#[cfg(feature = "gzip")]
122impl Compressor for GzipCompressor {
123 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
124 use flate2::Compression;
125 use flate2::write::GzEncoder;
126 use std::io::Write;
127
128 let mut encoder = GzEncoder::new(Vec::new(), Compression::new(self.level));
129 encoder
130 .write_all(data)
131 .map_err(|e| CompressionError::CompressionFailed(e.to_string()))?;
132 encoder
133 .finish()
134 .map_err(|e| CompressionError::CompressionFailed(e.to_string()))
135 }
136
137 fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
138 use flate2::read::GzDecoder;
139 use std::io::Read;
140
141 let mut decoder = GzDecoder::new(data);
142 let mut decompressed = Vec::new();
143 decoder
144 .read_to_end(&mut decompressed)
145 .map_err(|e| CompressionError::DecompressionFailed(e.to_string()))?;
146 Ok(decompressed)
147 }
148
149 fn clone_box(&self) -> Box<dyn Compressor> {
150 Box::new(*self)
151 }
152}
153
154#[cfg(feature = "zstd")]
156#[cfg_attr(docsrs, doc(cfg(feature = "zstd")))]
157#[derive(Debug, Clone, Copy)]
158pub struct ZstdCompressor {
159 level: i32,
160}
161
162#[cfg(feature = "zstd")]
163impl ZstdCompressor {
164 pub fn new() -> Self {
166 Self { level: 3 }
167 }
168
169 pub fn with_level(level: i32) -> Self {
173 Self {
174 level: level.clamp(-7, 22),
175 }
176 }
177}
178
179#[cfg(feature = "zstd")]
180impl Default for ZstdCompressor {
181 fn default() -> Self {
182 Self::new()
183 }
184}
185
186#[cfg(feature = "zstd")]
187impl Compressor for ZstdCompressor {
188 fn compress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
189 zstd::encode_all(data, self.level)
190 .map_err(|e| CompressionError::CompressionFailed(e.to_string()))
191 }
192
193 fn decompress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
194 zstd::decode_all(data).map_err(|e| CompressionError::DecompressionFailed(e.to_string()))
195 }
196
197 fn clone_box(&self) -> Box<dyn Compressor> {
198 Box::new(*self)
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn test_passthrough_compressor() {
208 let compressor = PassthroughCompressor;
209 let data = b"Hello, World!";
210
211 let compressed = compressor.compress(data).unwrap();
212 assert_eq!(compressed, data);
213
214 let decompressed = compressor.decompress(&compressed).unwrap();
215 assert_eq!(decompressed, data);
216 }
217
218 #[cfg(feature = "gzip")]
219 #[test]
220 fn test_gzip_compressor() {
221 let compressor = GzipCompressor::new();
222 let data = b"Hello, World! This is a test of gzip compression.".repeat(10);
223
224 let compressed = compressor.compress(&data).unwrap();
225 assert!(
226 compressed.len() < data.len(),
227 "Compressed data should be smaller"
228 );
229
230 let decompressed = compressor.decompress(&compressed).unwrap();
231 assert_eq!(decompressed, data);
232 }
233
234 #[cfg(feature = "gzip")]
235 #[test]
236 fn test_gzip_compression_levels() {
237 let data = b"Hello, World! This is a test of gzip compression.".repeat(100);
238
239 let fast = GzipCompressor::with_level(1);
240 let balanced = GzipCompressor::with_level(6);
241 let max = GzipCompressor::with_level(9);
242
243 let fast_compressed = fast.compress(&data).unwrap();
244 let balanced_compressed = balanced.compress(&data).unwrap();
245 let max_compressed = max.compress(&data).unwrap();
246
247 assert!(max_compressed.len() <= balanced_compressed.len());
249 assert!(balanced_compressed.len() <= fast_compressed.len());
250
251 assert_eq!(fast.decompress(&fast_compressed).unwrap(), data);
253 assert_eq!(balanced.decompress(&balanced_compressed).unwrap(), data);
254 assert_eq!(max.decompress(&max_compressed).unwrap(), data);
255 }
256
257 #[cfg(feature = "zstd")]
258 #[test]
259 fn test_zstd_compressor() {
260 let compressor = ZstdCompressor::new();
261 let data = b"Hello, World! This is a test of zstd compression.".repeat(10);
262
263 let compressed = compressor.compress(&data).unwrap();
264 assert!(
265 compressed.len() < data.len(),
266 "Compressed data should be smaller"
267 );
268
269 let decompressed = compressor.decompress(&compressed).unwrap();
270 assert_eq!(decompressed, data);
271 }
272
273 #[cfg(feature = "zstd")]
274 #[test]
275 fn test_zstd_compression_levels() {
276 let data = b"Hello, World! This is a test of zstd compression.".repeat(100);
277
278 let fast = ZstdCompressor::with_level(-7);
279 let balanced = ZstdCompressor::with_level(3);
280 let max = ZstdCompressor::with_level(22);
281
282 let fast_compressed = fast.compress(&data).unwrap();
283 let balanced_compressed = balanced.compress(&data).unwrap();
284 let max_compressed = max.compress(&data).unwrap();
285
286 assert!(max_compressed.len() <= balanced_compressed.len());
288
289 assert_eq!(fast.decompress(&fast_compressed).unwrap(), data);
291 assert_eq!(balanced.decompress(&balanced_compressed).unwrap(), data);
292 assert_eq!(max.decompress(&max_compressed).unwrap(), data);
293 }
294}