1use serde::Deserialize;
19use std::fmt::Display;
20use std::str::FromStr;
21
22#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
24pub enum CompressionAlgorithm {
25 #[serde(rename = "gz")]
27 Gzip,
28 #[serde(rename = "zz")]
30 Deflate,
31 #[serde(rename = "z")]
33 Compress,
34 #[serde(rename = "br")]
36 Brotli,
37 #[serde(rename = "zst")]
39 Zstandard,
40}
41
42impl CompressionAlgorithm {
43 pub fn ext(&self) -> &'static str {
45 match self {
46 Self::Gzip => "gz",
47 Self::Deflate => "zz",
48 Self::Compress => "z",
49 Self::Brotli => "br",
50 Self::Zstandard => "zst",
51 }
52 }
53
54 pub fn from_ext(ext: &str) -> Option<Self> {
56 match ext {
57 "gz" => Some(Self::Gzip),
58 "zz" => Some(Self::Deflate),
59 "z" => Some(Self::Compress),
60 "br" => Some(Self::Brotli),
61 "zst" => Some(Self::Zstandard),
62 _ => None,
63 }
64 }
65
66 pub fn name(&self) -> &'static str {
68 match self {
69 Self::Gzip => "gzip",
70 Self::Deflate => "deflate",
71 Self::Compress => "compress",
72 Self::Brotli => "br",
73 Self::Zstandard => "zstd",
74 }
75 }
76
77 pub fn from_name(name: &str) -> Option<Self> {
79 match name {
80 "gzip" => Some(Self::Gzip),
81 "deflate" => Some(Self::Deflate),
82 "compress" => Some(Self::Compress),
83 "br" => Some(Self::Brotli),
84 "zstd" => Some(Self::Zstandard),
85 _ => None,
86 }
87 }
88}
89
90impl FromStr for CompressionAlgorithm {
91 type Err = UnsupportedCompressionAlgorithm;
92
93 fn from_str(s: &str) -> Result<Self, Self::Err> {
95 CompressionAlgorithm::from_ext(s).ok_or(UnsupportedCompressionAlgorithm(s.to_owned()))
96 }
97}
98
99impl Display for CompressionAlgorithm {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
101 write!(f, "{}", self.name())
102 }
103}
104
105#[derive(Debug, PartialEq, Eq)]
107pub struct UnsupportedCompressionAlgorithm(String);
108
109impl Display for UnsupportedCompressionAlgorithm {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
111 write!(f, "Unsupported compression algorithm: {}", self.0)
112 }
113}
114
115fn parse_encoding(encoding: &str) -> Option<(&str, u16)> {
118 let mut params = encoding.split(';');
119 let algorithm = params.next()?.trim();
120 let mut quality = 1000;
121 for param in params {
122 if let Some((name, value)) = param.split_once('=') {
123 if name.trim() == "q" {
124 if let Ok(value) = f64::from_str(value.trim()) {
125 quality = (value * 1000.0) as u16;
126 }
127 }
128 }
129 }
130 Some((algorithm, quality))
131}
132
133pub(crate) fn find_matches(
136 requested: &str,
137 supported: &[CompressionAlgorithm],
138) -> Vec<CompressionAlgorithm> {
139 let mut requested = requested
140 .split(',')
141 .filter_map(parse_encoding)
142 .collect::<Vec<_>>();
143 requested.sort_by_key(|(_, quality)| -(*quality as i32));
144
145 let mut result = Vec::new();
146 for (algorithm, _) in requested {
147 if algorithm == "*" {
148 for algorithm in supported {
149 if !result.contains(algorithm) {
150 result.push(*algorithm);
151 }
152 }
153 break;
154 } else if let Some(algorithm) = CompressionAlgorithm::from_name(algorithm) {
155 if supported.contains(&algorithm) && !result.contains(&algorithm) {
156 result.push(algorithm);
157 }
158 }
159 }
160 result
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_find_matches() {
169 assert_eq!(
170 find_matches(
171 "",
172 &[CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
173 ),
174 Vec::new()
175 );
176
177 assert_eq!(
178 find_matches(
179 "identity",
180 &[CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
181 ),
182 Vec::new()
183 );
184
185 assert_eq!(
186 find_matches(
187 "*",
188 &[CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
189 ),
190 vec![CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
191 );
192
193 assert_eq!(
194 find_matches(
195 "br, *",
196 &[CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
197 ),
198 vec![CompressionAlgorithm::Brotli, CompressionAlgorithm::Gzip]
199 );
200
201 assert_eq!(
202 find_matches(
203 "br;q=0.9, *",
204 &[CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
205 ),
206 vec![CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
207 );
208
209 assert_eq!(
210 find_matches(
211 "deflate;q=0.7, gzip;q=0.9, zstd;q=0.8, br;q=1.0, compress;q=0.5",
212 &[
213 CompressionAlgorithm::Deflate,
214 CompressionAlgorithm::Gzip,
215 CompressionAlgorithm::Compress,
216 CompressionAlgorithm::Brotli,
217 CompressionAlgorithm::Zstandard,
218 ]
219 ),
220 vec![
221 CompressionAlgorithm::Brotli,
222 CompressionAlgorithm::Gzip,
223 CompressionAlgorithm::Zstandard,
224 CompressionAlgorithm::Deflate,
225 CompressionAlgorithm::Compress,
226 ]
227 );
228
229 assert_eq!(
230 find_matches(
231 "deflate;q=0.7, zstd;q=0.8, br;q=1.0",
232 &[
233 CompressionAlgorithm::Deflate,
234 CompressionAlgorithm::Gzip,
235 CompressionAlgorithm::Brotli,
236 CompressionAlgorithm::Zstandard,
237 ]
238 ),
239 vec![
240 CompressionAlgorithm::Brotli,
241 CompressionAlgorithm::Zstandard,
242 CompressionAlgorithm::Deflate,
243 ]
244 );
245 }
246}