use axum::http::header::ACCEPT; use axum::http::HeaderMap; use mime::{Mime, TEXT_HTML}; use std::cmp::Ordering; pub fn accepts_html(headers: &HeaderMap) -> bool { headers.get(ACCEPT).map_or(false, |val| { let s = val.to_str().unwrap(); parse_accept(s).accepts(&TEXT_HTML) }) } pub fn best_match<'a>(headers: &HeaderMap, options: &'a [&'a Mime]) -> Option<&'a Mime> { headers .get(ACCEPT) .map(|val| { let accept = parse_accept(val.to_str().unwrap()); for &opt in options.iter() { if accept.accepts(opt) { return Some(opt); } } None }) .flatten() } fn parse_accept(s: &str) -> Accept { let mut entries = s .split(",") .flat_map(|s| s.trim().parse::().ok()) .collect::>(); entries.sort_by(|a, b| { if b.is_wildcard() || b.is_subtype_wildcard() { Ordering::Less } else { b.quality() .unwrap_or(1f32) .partial_cmp(&a.quality().unwrap_or(1f32)) .unwrap() } }); Accept { entries } } #[derive(Debug, PartialEq)] struct Accept { entries: Vec, } impl Accept { fn accepts(&self, mime: &Mime) -> bool { self.entries.iter().any(|e| { if e == mime { true } else if e.type_() == mime.type_() && e.is_subtype_wildcard() { true } else { false } }) } } trait MimeExt { fn quality(&self) -> Option; fn is_wildcard(&self) -> bool; fn is_subtype_wildcard(&self) -> bool; } impl MimeExt for Mime { fn quality(&self) -> Option { self.params() .find(|p| p.0 == "q") .map(|(_, val)| val.as_str().parse().unwrap()) } fn is_wildcard(&self) -> bool { *self == mime::STAR_STAR } fn is_subtype_wildcard(&self) -> bool { self.subtype() == "*" } } #[cfg(test)] mod tests { use axum::http::HeaderMap; use super::best_match; #[test] fn test_sort_accept() { let accept = super::parse_accept("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"); assert_eq!( accept.entries, vec![ "text/html", "text/x-c", "text/x-dvi; q=0.8", "text/plain; q=0.5" ] ); } #[test] fn test_accept() { let accept = super::parse_accept("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"); assert!(accept.accepts(&"text/html".parse().unwrap())); assert!(!accept.accepts(&"application/json".parse().unwrap())); } #[test] fn test_best_match() { let mut headers = HeaderMap::new(); headers.insert("Accept", "text/plain; q=0.5, text/html".parse().unwrap()); assert_eq!( best_match(&headers, &[&mime::TEXT_HTML, &mime::APPLICATION_JAVASCRIPT]), Some(&mime::TEXT_HTML) ); } }