#![forbid(unsafe_code, future_incompatible)]
#![forbid(rust_2018_idioms, rust_2018_compatibility)]
#![deny(missing_debug_implementations, bad_style)]
#![deny(missing_docs)]
#![cfg_attr(test, deny(warnings))]
mod error;
pub use crate::error::{Error, Result};
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
use itertools::Itertools;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum Encoding {
Gzip,
Deflate,
Brotli,
Zstd,
Identity,
}
impl Encoding {
fn parse(s: &str) -> Result<Option<Encoding>> {
match s {
"gzip" => Ok(Some(Encoding::Gzip)),
"deflate" => Ok(Some(Encoding::Deflate)),
"br" => Ok(Some(Encoding::Brotli)),
"zstd" => Ok(Some(Encoding::Zstd)),
"identity" => Ok(Some(Encoding::Identity)),
"*" => Ok(None),
_ => Err(Error::UnknownEncoding),
}
}
pub fn to_header_value(self) -> HeaderValue {
match self {
Encoding::Gzip => HeaderValue::from_str("gzip").unwrap(),
Encoding::Deflate => HeaderValue::from_str("deflate").unwrap(),
Encoding::Brotli => HeaderValue::from_str("br").unwrap(),
Encoding::Zstd => HeaderValue::from_str("zstd").unwrap(),
Encoding::Identity => HeaderValue::from_str("identity").unwrap(),
}
}
}
pub fn parse(headers: &HeaderMap) -> Result<Option<Encoding>> {
preferred(encodings_iter(headers))
}
pub fn preferred(
encodings: impl Iterator<Item = Result<(Option<Encoding>, f32)>>,
) -> Result<Option<Encoding>> {
let mut preferred_encoding = None;
let mut max_qval = 0.0;
for r in encodings {
let (encoding, qval) = r?;
if (qval - 1.0f32).abs() < 0.01 {
return Ok(encoding);
} else if qval > max_qval {
preferred_encoding = encoding;
max_qval = qval;
}
}
Ok(preferred_encoding)
}
pub fn encodings(headers: &HeaderMap) -> Result<Vec<(Option<Encoding>, f32)>> {
encodings_iter(headers).collect()
}
pub fn encodings_iter(
headers: &HeaderMap,
) -> impl Iterator<Item = Result<(Option<Encoding>, f32)>> + '_ {
headers
.get_all(ACCEPT_ENCODING)
.iter()
.map(|hval| hval.to_str().map_err(|_| Error::InvalidEncoding))
.map_ok(|s| s.split(',').map(str::trim))
.flatten_ok()
.filter_map_ok(|v| {
let (e, q) = match v.split_once(";q=") {
Some((e, q)) => (e, q),
None => return Some(Ok((Encoding::parse(v).ok()?, 1.0f32))),
};
let encoding = Encoding::parse(e).ok()?; let qval = match q.parse() {
Ok(f) if f > 1.0 => return Some(Err(Error::InvalidEncoding)), Ok(f) => f,
Err(_) => return Some(Err(Error::InvalidEncoding)),
};
Some(Ok((encoding, qval)))
})
.map(|r| r?) }