llm_cost_ops/compression/
config.rs

1// Compression configuration
2
3use super::{CompressionAlgorithm, CompressionLevel};
4use serde::{Deserialize, Serialize};
5
6/// Compression configuration
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct CompressionConfig {
9    /// Enable compression
10    pub enabled: bool,
11
12    /// Default compression level
13    pub level: CompressionLevel,
14
15    /// Supported algorithms (in order of preference)
16    pub algorithms: Vec<CompressionAlgorithm>,
17
18    /// Minimum size in bytes to compress (don't compress small responses)
19    pub min_size: usize,
20
21    /// Maximum size in bytes to compress (avoid compressing huge responses)
22    pub max_size: Option<usize>,
23
24    /// MIME types to compress
25    pub mime_types: Vec<String>,
26
27    /// Enable compression for requests
28    pub compress_requests: bool,
29
30    /// Enable compression for responses
31    pub compress_responses: bool,
32
33    /// Buffer size for streaming compression
34    pub buffer_size: usize,
35}
36
37impl Default for CompressionConfig {
38    fn default() -> Self {
39        Self {
40            enabled: true,
41            level: CompressionLevel::Default,
42            algorithms: vec![
43                CompressionAlgorithm::Brotli,
44                CompressionAlgorithm::Gzip,
45                CompressionAlgorithm::Deflate,
46            ],
47            min_size: 1024, // 1 KB
48            max_size: Some(10 * 1024 * 1024), // 10 MB
49            mime_types: vec![
50                "text/*".to_string(),
51                "application/json".to_string(),
52                "application/xml".to_string(),
53                "application/javascript".to_string(),
54                "application/x-www-form-urlencoded".to_string(),
55            ],
56            compress_requests: false,
57            compress_responses: true,
58            buffer_size: 8192, // 8 KB
59        }
60    }
61}
62
63impl CompressionConfig {
64    /// Create a production configuration
65    pub fn production() -> Self {
66        Self {
67            enabled: true,
68            level: CompressionLevel::Default,
69            algorithms: vec![
70                CompressionAlgorithm::Brotli,
71                CompressionAlgorithm::Gzip,
72            ],
73            min_size: 1024, // 1 KB
74            max_size: Some(50 * 1024 * 1024), // 50 MB
75            mime_types: vec![
76                "text/*".to_string(),
77                "application/json".to_string(),
78                "application/xml".to_string(),
79                "application/javascript".to_string(),
80            ],
81            compress_requests: false,
82            compress_responses: true,
83            buffer_size: 16384, // 16 KB
84        }
85    }
86
87    /// Create a development configuration
88    pub fn development() -> Self {
89        Self {
90            enabled: true,
91            level: CompressionLevel::Fastest,
92            algorithms: vec![CompressionAlgorithm::Gzip],
93            min_size: 512, // 512 bytes
94            max_size: Some(5 * 1024 * 1024), // 5 MB
95            mime_types: vec!["text/*".to_string(), "application/json".to_string()],
96            compress_requests: false,
97            compress_responses: true,
98            buffer_size: 4096, // 4 KB
99        }
100    }
101
102    /// Create a disabled configuration
103    pub fn disabled() -> Self {
104        Self {
105            enabled: false,
106            ..Default::default()
107        }
108    }
109
110    /// Create a high compression configuration
111    pub fn high_compression() -> Self {
112        Self {
113            enabled: true,
114            level: CompressionLevel::Best,
115            algorithms: vec![CompressionAlgorithm::Brotli],
116            min_size: 256, // 256 bytes
117            max_size: None, // No limit
118            mime_types: vec![
119                "text/*".to_string(),
120                "application/json".to_string(),
121                "application/xml".to_string(),
122                "application/javascript".to_string(),
123                "application/octet-stream".to_string(),
124            ],
125            compress_requests: true,
126            compress_responses: true,
127            buffer_size: 32768, // 32 KB
128        }
129    }
130
131    /// Validate configuration
132    pub fn validate(&self) -> Result<(), String> {
133        if self.algorithms.is_empty() {
134            return Err("At least one compression algorithm must be specified".to_string());
135        }
136
137        if self.min_size == 0 {
138            return Err("Minimum size must be greater than 0".to_string());
139        }
140
141        if let Some(max_size) = self.max_size {
142            if max_size < self.min_size {
143                return Err("Maximum size must be greater than minimum size".to_string());
144            }
145        }
146
147        if self.buffer_size == 0 {
148            return Err("Buffer size must be greater than 0".to_string());
149        }
150
151        Ok(())
152    }
153
154    /// Check if content type should be compressed
155    pub fn should_compress_mime_type(&self, content_type: &str) -> bool {
156        if !self.enabled {
157            return false;
158        }
159
160        for pattern in &self.mime_types {
161            if pattern.ends_with("/*") {
162                let prefix = pattern.trim_end_matches("/*");
163                if content_type.starts_with(prefix) {
164                    return true;
165                }
166            } else if content_type.contains(pattern) {
167                return true;
168            }
169        }
170
171        false
172    }
173
174    /// Check if size should be compressed
175    pub fn should_compress_size(&self, size: usize) -> bool {
176        if !self.enabled {
177            return false;
178        }
179
180        if size < self.min_size {
181            return false;
182        }
183
184        if let Some(max_size) = self.max_size {
185            if size > max_size {
186                return false;
187            }
188        }
189
190        true
191    }
192
193    /// Select best algorithm from Accept-Encoding header
194    pub fn select_algorithm(&self, accept_encoding: Option<&str>) -> Option<CompressionAlgorithm> {
195        if !self.enabled {
196            return None;
197        }
198
199        let accept_encoding = accept_encoding?;
200
201        super::types::ContentEncoding::select_best(accept_encoding, &self.algorithms)
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn test_default_config() {
211        let config = CompressionConfig::default();
212        assert!(config.enabled);
213        assert_eq!(config.min_size, 1024);
214        assert!(config.validate().is_ok());
215    }
216
217    #[test]
218    fn test_production_config() {
219        let config = CompressionConfig::production();
220        assert!(config.enabled);
221        assert_eq!(config.algorithms.len(), 2);
222        assert!(config.validate().is_ok());
223    }
224
225    #[test]
226    fn test_development_config() {
227        let config = CompressionConfig::development();
228        assert!(config.enabled);
229        assert_eq!(config.level, CompressionLevel::Fastest);
230        assert!(config.validate().is_ok());
231    }
232
233    #[test]
234    fn test_disabled_config() {
235        let config = CompressionConfig::disabled();
236        assert!(!config.enabled);
237    }
238
239    #[test]
240    fn test_high_compression_config() {
241        let config = CompressionConfig::high_compression();
242        assert_eq!(config.level, CompressionLevel::Best);
243        assert!(config.compress_requests);
244        assert!(config.compress_responses);
245    }
246
247    #[test]
248    fn test_should_compress_mime_type() {
249        let config = CompressionConfig::default();
250
251        assert!(config.should_compress_mime_type("text/html"));
252        assert!(config.should_compress_mime_type("text/plain"));
253        assert!(config.should_compress_mime_type("application/json"));
254        assert!(!config.should_compress_mime_type("image/png"));
255        assert!(!config.should_compress_mime_type("video/mp4"));
256    }
257
258    #[test]
259    fn test_should_compress_size() {
260        let config = CompressionConfig::default();
261
262        assert!(!config.should_compress_size(512)); // Too small
263        assert!(config.should_compress_size(2048)); // Just right
264        assert!(!config.should_compress_size(20 * 1024 * 1024)); // Too large
265    }
266
267    #[test]
268    fn test_select_algorithm() {
269        let config = CompressionConfig::default();
270
271        let algo = config.select_algorithm(Some("gzip, deflate, br"));
272        assert_eq!(algo, Some(CompressionAlgorithm::Brotli));
273
274        let algo = config.select_algorithm(Some("gzip"));
275        assert_eq!(algo, Some(CompressionAlgorithm::Gzip));
276
277        let algo = config.select_algorithm(None);
278        assert_eq!(algo, None);
279    }
280
281    #[test]
282    fn test_validate_config() {
283        let mut config = CompressionConfig::default();
284
285        // Valid config
286        assert!(config.validate().is_ok());
287
288        // Empty algorithms
289        config.algorithms.clear();
290        assert!(config.validate().is_err());
291
292        // Reset
293        config = CompressionConfig::default();
294
295        // min_size = 0
296        config.min_size = 0;
297        assert!(config.validate().is_err());
298
299        // Reset
300        config = CompressionConfig::default();
301
302        // max_size < min_size
303        config.max_size = Some(100);
304        config.min_size = 1000;
305        assert!(config.validate().is_err());
306    }
307}