use serde::Deserialize;
use std::fmt::Display;
use std::str::FromStr;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
pub enum CompressionAlgorithm {
#[serde(rename = "gz")]
Gzip,
#[serde(rename = "zz")]
Deflate,
#[serde(rename = "z")]
Compress,
#[serde(rename = "br")]
Brotli,
#[serde(rename = "zst")]
Zstandard,
}
impl CompressionAlgorithm {
pub fn ext(&self) -> &'static str {
match self {
Self::Gzip => "gz",
Self::Deflate => "zz",
Self::Compress => "z",
Self::Brotli => "br",
Self::Zstandard => "zst",
}
}
pub fn from_ext(ext: &str) -> Option<Self> {
match ext {
"gz" => Some(Self::Gzip),
"zz" => Some(Self::Deflate),
"z" => Some(Self::Compress),
"br" => Some(Self::Brotli),
"zst" => Some(Self::Zstandard),
_ => None,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Gzip => "gzip",
Self::Deflate => "deflate",
Self::Compress => "compress",
Self::Brotli => "br",
Self::Zstandard => "zstd",
}
}
pub fn from_name(name: &str) -> Option<Self> {
match name {
"gzip" => Some(Self::Gzip),
"deflate" => Some(Self::Deflate),
"compress" => Some(Self::Compress),
"br" => Some(Self::Brotli),
"zstd" => Some(Self::Zstandard),
_ => None,
}
}
}
impl FromStr for CompressionAlgorithm {
type Err = UnsupportedCompressionAlgorithm;
fn from_str(s: &str) -> Result<Self, Self::Err> {
CompressionAlgorithm::from_ext(s).ok_or(UnsupportedCompressionAlgorithm(s.to_owned()))
}
}
impl Display for CompressionAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "{}", self.name())
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct UnsupportedCompressionAlgorithm(String);
impl Display for UnsupportedCompressionAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "Unsupported compression algorithm: {}", self.0)
}
}
fn parse_encoding(encoding: &str) -> Option<(&str, u16)> {
let mut params = encoding.split(';');
let algorithm = params.next()?.trim();
let mut quality = 1000;
for param in params {
if let Some((name, value)) = param.split_once('=') {
if name.trim() == "q" {
if let Ok(value) = f64::from_str(value.trim()) {
quality = (value * 1000.0) as u16;
}
}
}
}
Some((algorithm, quality))
}
pub(crate) fn find_matches(
requested: &str,
supported: &[CompressionAlgorithm],
) -> Vec<CompressionAlgorithm> {
let mut requested = requested
.split(',')
.filter_map(parse_encoding)
.collect::<Vec<_>>();
requested.sort_by_key(|(_, quality)| -(*quality as i32));
let mut result = Vec::new();
for (algorithm, _) in requested {
if algorithm == "*" {
for algorithm in supported {
if !result.contains(algorithm) {
result.push(*algorithm);
}
}
break;
} else if let Some(algorithm) = CompressionAlgorithm::from_name(algorithm) {
if supported.contains(&algorithm) && !result.contains(&algorithm) {
result.push(algorithm);
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_matches() {
assert_eq!(
find_matches(
"",
&[CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
),
Vec::new()
);
assert_eq!(
find_matches(
"identity",
&[CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
),
Vec::new()
);
assert_eq!(
find_matches(
"*",
&[CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
),
vec![CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
);
assert_eq!(
find_matches(
"br, *",
&[CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
),
vec![CompressionAlgorithm::Brotli, CompressionAlgorithm::Gzip]
);
assert_eq!(
find_matches(
"br;q=0.9, *",
&[CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
),
vec![CompressionAlgorithm::Gzip, CompressionAlgorithm::Brotli]
);
assert_eq!(
find_matches(
"deflate;q=0.7, gzip;q=0.9, zstd;q=0.8, br;q=1.0, compress;q=0.5",
&[
CompressionAlgorithm::Deflate,
CompressionAlgorithm::Gzip,
CompressionAlgorithm::Compress,
CompressionAlgorithm::Brotli,
CompressionAlgorithm::Zstandard,
]
),
vec![
CompressionAlgorithm::Brotli,
CompressionAlgorithm::Gzip,
CompressionAlgorithm::Zstandard,
CompressionAlgorithm::Deflate,
CompressionAlgorithm::Compress,
]
);
assert_eq!(
find_matches(
"deflate;q=0.7, zstd;q=0.8, br;q=1.0",
&[
CompressionAlgorithm::Deflate,
CompressionAlgorithm::Gzip,
CompressionAlgorithm::Brotli,
CompressionAlgorithm::Zstandard,
]
),
vec![
CompressionAlgorithm::Brotli,
CompressionAlgorithm::Zstandard,
CompressionAlgorithm::Deflate,
]
);
}
}