use std::ops::Deref;
use std::str::FromStr;
use std::fmt;
use smallvec::SmallVec;
use crate::{Header, MediaType};
use crate::ext::IntoCollection;
use crate::parse::parse_accept;
#[derive(Debug, Clone)]
pub struct Accept(pub(crate) SmallVec<[QMediaType; 1]>);
#[derive(Debug, Clone, PartialEq)]
pub struct QMediaType(pub MediaType, pub Option<f32>);
macro_rules! accept_constructor {
($($name:ident ($check:ident): $str:expr, $t:expr,
$s:expr $(; $k:expr => $v:expr)*,)+) => {
$(
#[doc="An `Accept` header with the single media type for"]
#[doc=concat!("**", $str, "**: ", "_", $t, "/", $s, "_")]
#[allow(non_upper_case_globals)]
pub const $name: Accept = Accept(
SmallVec::from_const([QMediaType(MediaType::$name, None)])
);
)+
};
}
impl Accept {
#[inline(always)]
pub fn new<T: IntoCollection<QMediaType>>(items: T) -> Accept {
Accept(items.into_collection())
}
pub fn preferred(&self) -> &QMediaType {
static ANY: QMediaType = QMediaType(MediaType::Any, None);
let mut all = self.iter();
let mut preferred = all.next().unwrap_or(&ANY);
for media_type in all {
if media_type.weight().is_none() && preferred.weight().is_some() {
preferred = media_type;
} else if media_type.weight_or(0.0) > preferred.weight_or(1.0) {
preferred = media_type;
} else if media_type.specificity() > preferred.specificity() {
preferred = media_type;
} else if media_type == preferred {
if media_type.params().count() > preferred.params().count() {
preferred = media_type;
}
}
}
preferred
}
#[inline(always)]
pub fn first(&self) -> Option<&QMediaType> {
self.iter().next()
}
#[inline(always)]
pub fn iter(&self) -> impl Iterator<Item=&'_ QMediaType> + '_ {
self.0.iter()
}
#[inline(always)]
pub fn media_types(&self) -> impl Iterator<Item=&'_ MediaType> + '_ {
self.iter().map(|weighted_mt| weighted_mt.media_type())
}
known_media_types!(accept_constructor);
}
impl<T: IntoCollection<MediaType>> From<T> for Accept {
#[inline(always)]
fn from(items: T) -> Accept {
Accept(items.mapped(|item| item.into()))
}
}
impl PartialEq for Accept {
fn eq(&self, other: &Accept) -> bool {
self.iter().eq(other.iter())
}
}
impl fmt::Display for Accept {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, media_type) in self.iter().enumerate() {
if i >= 1 {
write!(f, ", {}", media_type.0)?;
} else {
write!(f, "{}", media_type.0)?;
}
}
Ok(())
}
}
impl FromStr for Accept {
type Err = String;
#[inline]
fn from_str(raw: &str) -> Result<Accept, String> {
parse_accept(raw).map_err(|e| e.to_string())
}
}
impl From<Accept> for Header<'static> {
#[inline(always)]
fn from(val: Accept) -> Self {
Header::new("Accept", val.to_string())
}
}
impl QMediaType {
#[inline(always)]
pub fn weight(&self) -> Option<f32> {
self.1
}
#[inline(always)]
pub fn weight_or(&self, default: f32) -> f32 {
self.1.unwrap_or(default)
}
#[inline(always)]
pub fn media_type(&self) -> &MediaType {
&self.0
}
}
impl From<MediaType> for QMediaType {
#[inline(always)]
fn from(media_type: MediaType) -> QMediaType {
QMediaType(media_type, None)
}
}
impl Deref for QMediaType {
type Target = MediaType;
#[inline(always)]
fn deref(&self) -> &MediaType {
&self.0
}
}
#[cfg(test)]
mod test {
use crate::{Accept, MediaType};
#[track_caller]
fn assert_preference(string: &str, expect: &str) {
let accept: Accept = string.parse().expect("accept string parse");
let expected: MediaType = expect.parse().expect("media type parse");
let preferred = accept.preferred();
let actual = preferred.media_type();
if *actual != expected {
panic!("mismatch for {}: expected {}, got {}", string, expected, actual)
}
}
#[test]
fn test_preferred() {
assert_preference("text/*", "text/*");
assert_preference("text/*, text/html", "text/html");
assert_preference("text/*; q=0.1, text/html", "text/html");
assert_preference("text/*; q=1, text/html", "text/html");
assert_preference("text/html, text/*", "text/html");
assert_preference("text/*, text/html", "text/html");
assert_preference("text/html, text/*; q=1", "text/html");
assert_preference("text/html; q=1, text/html", "text/html");
assert_preference("text/html, text/*; q=0.1", "text/html");
assert_preference("text/html, application/json", "text/html");
assert_preference("text/html, application/json; q=1", "text/html");
assert_preference("application/json; q=1, text/html", "text/html");
assert_preference("text/*, application/json", "application/json");
assert_preference("*/*, text/*", "text/*");
assert_preference("*/*, text/*, text/plain", "text/plain");
assert_preference("a/b; q=0.1, a/b; q=0.2", "a/b; q=0.2");
assert_preference("a/b; q=0.1, b/c; q=0.2", "b/c; q=0.2");
assert_preference("a/b; q=0.5, b/c; q=0.2", "a/b; q=0.5");
assert_preference("a/b; q=0.5, b/c; q=0.2, c/d", "c/d");
assert_preference("a/b; q=0.5; v=1, a/b", "a/b");
assert_preference("a/b; v=1, a/b; v=1; c=2", "a/b; v=1; c=2");
assert_preference("a/b; v=1; c=2, a/b; v=1", "a/b; v=1; c=2");
assert_preference("a/b; q=0.5; v=1, a/b; q=0.5; v=1; c=2", "a/b; q=0.5; v=1; c=2");
assert_preference("a/b; q=0.6; v=1, a/b; q=0.5; v=1; c=2", "a/b; q=0.6; v=1");
}
}