mediagit_compression/
brotli_compressor.rs1use crate::error::{CompressionError, CompressionResult};
20use crate::{CompressionLevel, Compressor};
21use std::fmt;
22use std::io::Write;
23
24#[derive(Clone)]
29pub struct BrotliCompressor {
30 level: CompressionLevel,
31}
32
33impl BrotliCompressor {
34 pub fn new(level: CompressionLevel) -> Self {
36 BrotliCompressor { level }
37 }
38
39 pub fn fast() -> Self {
41 BrotliCompressor::new(CompressionLevel::Fast)
42 }
43
44 pub fn default_level() -> Self {
46 BrotliCompressor::new(CompressionLevel::Default)
47 }
48
49 pub fn best() -> Self {
51 BrotliCompressor::new(CompressionLevel::Best)
52 }
53}
54
55impl fmt::Debug for BrotliCompressor {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 f.debug_struct("BrotliCompressor")
58 .field("level", &self.level)
59 .finish()
60 }
61}
62
63impl Compressor for BrotliCompressor {
64 fn compress(&self, data: &[u8]) -> CompressionResult<Vec<u8>> {
65 if data.is_empty() {
66 return Ok(Vec::new());
67 }
68
69 let level = self.level.to_brotli_level();
70 let mut output = Vec::with_capacity(data.len() / 2);
71
72 output.extend_from_slice(b"BRT\x01");
74
75 {
77 let mut compressor = brotli::CompressorWriter::new(
78 &mut output,
79 4096, level,
81 22, );
83
84 if let Err(e) = compressor.write_all(data) {
85 return Err(CompressionError::brotli_error(format!(
86 "brotli compression failed: {}",
87 e
88 )));
89 }
90
91 if let Err(e) = compressor.flush() {
92 return Err(CompressionError::brotli_error(format!(
93 "brotli flush failed: {}",
94 e
95 )));
96 }
97 } Ok(output)
100 }
101
102 fn decompress(&self, data: &[u8]) -> CompressionResult<Vec<u8>> {
103 if data.is_empty() {
104 return Ok(Vec::new());
105 }
106
107 if data.len() >= 4 && data.starts_with(b"BRT\x01") {
109 let compressed_data = &data[4..];
111 let mut output = Vec::with_capacity(data.len() * 2);
112
113 match brotli::BrotliDecompress(&mut std::io::Cursor::new(compressed_data), &mut output)
114 {
115 Ok(_) => Ok(output),
116 Err(e) => Err(CompressionError::decompression_failed(format!(
117 "brotli decompression failed: {}",
118 e
119 ))),
120 }
121 } else {
122 Ok(data.to_vec())
124 }
125 }
126}
127
128#[cfg(test)]
129#[allow(clippy::unwrap_used)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_brotli_compress_decompress() {
135 let compressor = BrotliCompressor::new(CompressionLevel::Default);
136 let original = b"Hello, World! This is a test of brotli compression.";
137
138 let compressed = compressor.compress(original).unwrap();
139 let decompressed = compressor.decompress(&compressed).unwrap();
140
141 assert_eq!(original, &decompressed[..]);
142 }
143
144 #[test]
145 fn test_brotli_has_marker() {
146 let compressor = BrotliCompressor::new(CompressionLevel::Default);
147 let original = b"Test data for brotli";
148
149 let compressed = compressor.compress(original).unwrap();
150 assert!(compressed.starts_with(b"BRT\x01"));
151 }
152
153 #[test]
154 fn test_brotli_compress_empty() {
155 let compressor = BrotliCompressor::new(CompressionLevel::Default);
156 let compressed = compressor.compress(b"").unwrap();
157 assert_eq!(compressed.len(), 0);
158 }
159
160 #[test]
161 fn test_brotli_decompress_empty() {
162 let compressor = BrotliCompressor::new(CompressionLevel::Default);
163 let decompressed = compressor.decompress(b"").unwrap();
164 assert_eq!(decompressed.len(), 0);
165 }
166
167 #[test]
168 fn test_brotli_decompress_uncompressed_data() {
169 let compressor = BrotliCompressor::new(CompressionLevel::Default);
170 let data = b"This is not brotli compressed";
171 let result = compressor.decompress(data).unwrap();
172 assert_eq!(result, data);
174 }
175
176 #[test]
177 fn test_brotli_compression_levels() {
178 let data = b"This is test data that should compress. ".repeat(100);
179
180 let fast = BrotliCompressor::fast().compress(&data).unwrap();
181 let default = BrotliCompressor::default_level().compress(&data).unwrap();
182 let best = BrotliCompressor::best().compress(&data).unwrap();
183
184 assert!(best.len() <= default.len() || best.len() < 100);
187 assert!(default.len() <= fast.len() || default.len() < 100);
188
189 let decompressor = BrotliCompressor::default_level();
191 let fast_dec = decompressor.decompress(&fast).unwrap();
192 let default_dec = decompressor.decompress(&default).unwrap();
193 let best_dec = decompressor.decompress(&best).unwrap();
194
195 assert_eq!(fast_dec, data);
196 assert_eq!(default_dec, data);
197 assert_eq!(best_dec, data);
198 }
199
200 #[test]
201 fn test_brotli_large_data() {
202 let compressor = BrotliCompressor::new(CompressionLevel::Default);
203 let original = vec![0x42u8; 1024 * 100]; let compressed = compressor.compress(&original).unwrap();
206 let decompressed = compressor.decompress(&compressed).unwrap();
207
208 assert_eq!(original, decompressed);
209 assert!(compressed.len() < original.len() / 50);
211 }
212
213 #[test]
214 fn test_brotli_debug_format() {
215 let compressor = BrotliCompressor::new(CompressionLevel::Default);
216 let debug_str = format!("{:?}", compressor);
217 assert!(debug_str.contains("BrotliCompressor"));
218 assert!(debug_str.contains("Default"));
219 }
220}