armature_compression/
config.rs1use crate::CompressionAlgorithm;
4
5#[derive(Debug, Clone)]
7pub struct CompressionConfig {
8 pub algorithm: CompressionAlgorithm,
10
11 pub level: u32,
13
14 pub min_size: usize,
17
18 pub compressible_types: Vec<String>,
20
21 pub compress_encoded: bool,
23}
24
25impl Default for CompressionConfig {
26 fn default() -> Self {
27 Self {
28 algorithm: CompressionAlgorithm::Auto,
29 level: 0, min_size: 860, compressible_types: default_compressible_types(),
32 compress_encoded: false,
33 }
34 }
35}
36
37impl CompressionConfig {
38 pub fn new() -> Self {
40 Self::default()
41 }
42
43 pub fn builder() -> CompressionConfigBuilder {
45 CompressionConfigBuilder::new()
46 }
47
48 pub fn effective_level(&self) -> u32 {
50 if self.level == 0 {
51 self.algorithm.default_level()
52 } else {
53 self.level
54 .clamp(self.algorithm.min_level(), self.algorithm.max_level())
55 }
56 }
57
58 pub fn should_compress_content_type(&self, content_type: &str) -> bool {
60 let ct_lower = content_type.to_lowercase();
61 let ct_base = ct_lower.split(';').next().unwrap_or(&ct_lower).trim();
62
63 self.compressible_types.iter().any(|pattern| {
64 if pattern.ends_with("/*") {
65 let prefix = &pattern[..pattern.len() - 1];
67 ct_base.starts_with(prefix)
68 } else {
69 ct_base == pattern
70 }
71 })
72 }
73
74 pub fn should_compress_size(&self, size: usize) -> bool {
76 size >= self.min_size
77 }
78}
79
80#[derive(Debug, Clone, Default)]
82pub struct CompressionConfigBuilder {
83 config: CompressionConfig,
84}
85
86impl CompressionConfigBuilder {
87 pub fn new() -> Self {
89 Self {
90 config: CompressionConfig::default(),
91 }
92 }
93
94 pub fn algorithm(mut self, algorithm: CompressionAlgorithm) -> Self {
96 self.config.algorithm = algorithm;
97 self
98 }
99
100 pub fn level(mut self, level: u32) -> Self {
102 self.config.level = level;
103 self
104 }
105
106 pub fn min_size(mut self, min_size: usize) -> Self {
108 self.config.min_size = min_size;
109 self
110 }
111
112 pub fn compressible_types(mut self, types: Vec<String>) -> Self {
114 self.config.compressible_types = types;
115 self
116 }
117
118 pub fn add_compressible_type(mut self, content_type: impl Into<String>) -> Self {
120 self.config.compressible_types.push(content_type.into());
121 self
122 }
123
124 pub fn compress_encoded(mut self, compress: bool) -> Self {
126 self.config.compress_encoded = compress;
127 self
128 }
129
130 #[cfg(feature = "gzip")]
132 pub fn gzip(mut self) -> Self {
133 self.config.algorithm = CompressionAlgorithm::Gzip;
134 self
135 }
136
137 #[cfg(feature = "brotli")]
139 pub fn brotli(mut self) -> Self {
140 self.config.algorithm = CompressionAlgorithm::Brotli;
141 self
142 }
143
144 #[cfg(feature = "zstd")]
146 pub fn zstd(mut self) -> Self {
147 self.config.algorithm = CompressionAlgorithm::Zstd;
148 self
149 }
150
151 pub fn no_compression(mut self) -> Self {
153 self.config.algorithm = CompressionAlgorithm::None;
154 self
155 }
156
157 pub fn build(self) -> CompressionConfig {
159 self.config
160 }
161}
162
163fn default_compressible_types() -> Vec<String> {
165 vec![
166 "text/*".to_string(),
168 "application/json".to_string(),
170 "application/ld+json".to_string(),
171 "application/javascript".to_string(),
173 "application/x-javascript".to_string(),
174 "application/xml".to_string(),
176 "application/xhtml+xml".to_string(),
177 "application/rss+xml".to_string(),
178 "application/atom+xml".to_string(),
179 "image/svg+xml".to_string(),
181 "font/ttf".to_string(),
183 "font/otf".to_string(),
184 "application/vnd.ms-fontobject".to_string(),
185 "application/wasm".to_string(),
187 "application/manifest+json".to_string(),
188 ]
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn test_default_config() {
197 let config = CompressionConfig::default();
198 assert_eq!(config.algorithm, CompressionAlgorithm::Auto);
199 assert_eq!(config.min_size, 860);
200 assert!(!config.compress_encoded);
201 }
202
203 #[test]
204 fn test_builder() {
205 let config = CompressionConfig::builder().min_size(1024).level(6).build();
206
207 assert_eq!(config.min_size, 1024);
208 assert_eq!(config.level, 6);
209 }
210
211 #[test]
212 fn test_should_compress_content_type() {
213 let config = CompressionConfig::default();
214
215 assert!(config.should_compress_content_type("text/html"));
217 assert!(config.should_compress_content_type("text/css"));
218 assert!(config.should_compress_content_type("text/plain; charset=utf-8"));
219 assert!(config.should_compress_content_type("application/json"));
220 assert!(config.should_compress_content_type("application/javascript"));
221 assert!(config.should_compress_content_type("image/svg+xml"));
222
223 assert!(!config.should_compress_content_type("image/png"));
225 assert!(!config.should_compress_content_type("image/jpeg"));
226 assert!(!config.should_compress_content_type("video/mp4"));
227 assert!(!config.should_compress_content_type("application/octet-stream"));
228 }
229
230 #[test]
231 fn test_should_compress_size() {
232 let config = CompressionConfig::builder().min_size(1024).build();
233
234 assert!(!config.should_compress_size(100));
235 assert!(!config.should_compress_size(1023));
236 assert!(config.should_compress_size(1024));
237 assert!(config.should_compress_size(10000));
238 }
239
240 #[cfg(feature = "gzip")]
241 #[test]
242 fn test_effective_level_gzip() {
243 let config = CompressionConfig::builder().gzip().build();
244 assert_eq!(config.effective_level(), 6); let config = CompressionConfig::builder().gzip().level(9).build();
247 assert_eq!(config.effective_level(), 9);
248
249 let config = CompressionConfig::builder().gzip().level(100).build();
251 assert_eq!(config.effective_level(), 9); }
253
254 #[cfg(feature = "brotli")]
255 #[test]
256 fn test_builder_brotli() {
257 let config = CompressionConfig::builder().brotli().build();
258 assert_eq!(config.algorithm, CompressionAlgorithm::Brotli);
259 }
260}