1use flate2::{read::GzDecoder, write::GzEncoder, Compression};
4use std::io::{Read, Write};
5use thiserror::Error;
6
7#[derive(Error, Debug)]
9pub enum CompressionError {
10 #[error("Compression failed: {0}")]
11 CompressionFailed(String),
12 #[error("Decompression failed: {0}")]
13 DecompressionFailed(String),
14 #[error("IO error: {0}")]
15 IoError(#[from] std::io::Error),
16}
17
18pub struct CompressionEngine {
20 level: Compression,
21}
22
23impl Default for CompressionEngine {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl CompressionEngine {
30 pub fn new() -> Self {
32 Self {
33 level: Compression::default(),
34 }
35 }
36
37 pub fn with_level(level: u32) -> Self {
39 Self {
40 level: Compression::new(level),
41 }
42 }
43
44 pub fn compress(&self, data: &[u8]) -> Result<Vec<u8>, CompressionError> {
46 let mut encoder = GzEncoder::new(Vec::new(), self.level);
47 encoder
48 .write_all(data)
49 .map_err(|e| CompressionError::CompressionFailed(e.to_string()))?;
50
51 encoder
52 .finish()
53 .map_err(|e| CompressionError::CompressionFailed(e.to_string()))
54 }
55
56 pub fn decompress(&self, compressed_data: &[u8]) -> Result<Vec<u8>, CompressionError> {
58 let mut decoder = GzDecoder::new(compressed_data);
59 let mut decompressed = Vec::new();
60
61 decoder
62 .read_to_end(&mut decompressed)
63 .map_err(|e| CompressionError::DecompressionFailed(e.to_string()))?;
64
65 Ok(decompressed)
66 }
67
68 pub fn estimate_ratio(&self, data: &[u8]) -> Result<f64, CompressionError> {
70 let compressed = self.compress(data)?;
71 Ok(compressed.len() as f64 / data.len() as f64)
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn test_compression_decompression() {
81 let engine = CompressionEngine::new();
82 let data = b"Hello, World! This is a test message that should compress well because it has repeating patterns. Hello, World! This is a test message that should compress well because it has repeating patterns.";
83
84 let compressed = engine.compress(data).unwrap();
85 assert!(compressed.len() < data.len()); let decompressed = engine.decompress(&compressed).unwrap();
88 assert_eq!(decompressed.as_slice(), data);
89 }
90
91 #[test]
92 fn test_compression_levels() {
93 let data = b"Test data for compression level testing. ".repeat(100);
94
95 let engine_fast = CompressionEngine::with_level(1);
96 let engine_best = CompressionEngine::with_level(9);
97
98 let compressed_fast = engine_fast.compress(&data).unwrap();
99 let compressed_best = engine_best.compress(&data).unwrap();
100
101 assert!(compressed_best.len() <= compressed_fast.len());
103
104 let decompressed_fast = engine_fast.decompress(&compressed_fast).unwrap();
106 let decompressed_best = engine_best.decompress(&compressed_best).unwrap();
107
108 assert_eq!(decompressed_fast, data);
109 assert_eq!(decompressed_best, data);
110 }
111
112 #[test]
113 fn test_empty_data() {
114 let engine = CompressionEngine::new();
115 let empty_data = b"";
116
117 let compressed = engine.compress(empty_data).unwrap();
118 let decompressed = engine.decompress(&compressed).unwrap();
119
120 assert_eq!(decompressed.as_slice(), empty_data);
121 }
122
123 #[test]
124 fn test_invalid_compressed_data() {
125 let engine = CompressionEngine::new();
126 let invalid_data = b"This is not compressed data";
127
128 let result = engine.decompress(invalid_data);
129 assert!(matches!(result, Err(CompressionError::DecompressionFailed(_))));
130 }
131
132 #[test]
133 fn test_compression_ratio() {
134 let engine = CompressionEngine::new();
135
136 let repetitive_data = b"A".repeat(1000);
138 let ratio1 = engine.estimate_ratio(&repetitive_data).unwrap();
139
140 let varied_data = (0..1000).map(|i| (i % 256) as u8).collect::<Vec<_>>();
142 let ratio2 = engine.estimate_ratio(&varied_data).unwrap();
143
144 assert!(ratio1 < ratio2);
146 assert!(ratio1 < 1.0);
147 assert!(ratio2 < 1.0);
148 }
149}