use http::request::Parts;
use crate::extractors::FromRequestParts;
#[derive(Debug, Clone)]
pub struct Accept {
media_types: Vec<MediaType>,
}
#[derive(Debug, Clone)]
struct MediaType {
essence: String,
quality: f32,
}
impl Accept {
pub fn prefers(&self, media_type: &str) -> bool {
self
.media_types
.first()
.is_some_and(|mt| mt.essence == media_type || mt.essence == "*/*")
}
pub fn accepts(&self, media_type: &str) -> bool {
self.media_types.iter().any(|mt| {
mt.essence == media_type
|| mt.essence == "*/*"
|| (mt.essence.ends_with("/*")
&& media_type.starts_with(mt.essence.trim_end_matches("/*")))
})
}
pub fn preferred(&self) -> Option<&str> {
self.media_types.first().map(|mt| mt.essence.as_str())
}
pub fn types(&self) -> Vec<&str> {
self.media_types.iter().map(|mt| mt.essence.as_str()).collect()
}
}
fn parse_accept(header: &str) -> Vec<MediaType> {
let mut types: Vec<MediaType> = header
.split(',')
.filter_map(|part| {
let part = part.trim();
if part.is_empty() {
return None;
}
let mut quality = 1.0f32;
let mut essence = part;
if let Some(idx) = part.find(";q=") {
essence = part[..idx].trim();
if let Ok(q) = part[idx + 3..].trim().parse::<f32>() {
quality = q;
}
} else if let Some(idx) = part.find(';') {
essence = part[..idx].trim();
}
Some(MediaType {
essence: essence.to_string(),
quality,
})
})
.collect();
types.sort_by(|a, b| b.quality.partial_cmp(&a.quality).unwrap_or(std::cmp::Ordering::Equal));
types
}
impl<'a> FromRequestParts<'a> for Accept {
type Error = std::convert::Infallible;
fn from_request_parts(
parts: &'a mut Parts,
) -> impl core::future::Future<Output = core::result::Result<Self, Self::Error>> + Send + 'a {
let accept_header = parts
.headers
.get(http::header::ACCEPT)
.and_then(|v| v.to_str().ok())
.unwrap_or("*/*");
let media_types = parse_accept(accept_header);
futures_util::future::ready(Ok(Accept { media_types }))
}
}