use std::borrow::{Cow, Borrow};
use std::str::FromStr;
use std::fmt;
use std::hash::{Hash, Hasher};
use ext::IntoCollection;
use http::uncased::{uncased_eq, UncasedStr};
use http::parse::{IndexedStr, parse_media_type};
use smallvec::SmallVec;
#[derive(Debug, Clone)]
struct MediaParam {
key: IndexedStr,
value: IndexedStr,
}
#[derive(Debug, Clone)]
pub enum MediaParams {
Static(&'static [(IndexedStr, IndexedStr)]),
Dynamic(SmallVec<[(IndexedStr, IndexedStr); 2]>)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Source {
Known(&'static str),
Custom(Cow<'static, str>),
None
}
impl Source {
#[inline]
fn as_str(&self) -> Option<&str> {
match *self {
Source::Known(s) => Some(s),
Source::Custom(ref s) => Some(s.borrow()),
Source::None => None
}
}
}
#[derive(Debug, Clone)]
pub struct MediaType {
#[doc(hidden)]
pub source: Source,
#[doc(hidden)]
pub top: IndexedStr,
#[doc(hidden)]
pub sub: IndexedStr,
#[doc(hidden)]
pub params: MediaParams
}
macro_rules! media_str {
($string:expr) => (IndexedStr::Concrete(Cow::Borrowed($string)))
}
macro_rules! media_types {
($($name:ident ($check:ident): $str:expr, $t:expr,
$s:expr $(; $k:expr => $v:expr)*),+) => {
$(
#[doc="Media type for <b>"] #[doc=$str] #[doc="</b>: <i>"]
#[doc=$t] #[doc="/"] #[doc=$s]
$(#[doc="; "] #[doc=$k] #[doc=" = "] #[doc=$v])*
#[doc="</i>"]
#[allow(non_upper_case_globals)]
pub const $name: MediaType = MediaType {
source: Source::Known(concat!($t, "/", $s, $("; ", $k, "=", $v),*)),
top: media_str!($t),
sub: media_str!($s),
params: MediaParams::Static(&[$((media_str!($k), media_str!($v))),*])
};
#[doc="Returns `true` if `self` is the media type for <b>"]
#[doc=$str]
#[doc="</b>, "]
#[inline(always)]
pub fn $check(&self) -> bool {
*self == MediaType::$name
}
)+
pub fn is_known(&self) -> bool {
$(if self.$check() { return true })+
false
}
};
}
macro_rules! from_extension {
($($ext:expr => $name:ident),*) => (
$(#[doc=$ext]#[doc=","])*
pub fn from_extension(ext: &str) -> Option<MediaType> {
match ext {
$(x if uncased_eq(x, $ext) => Some(MediaType::$name)),*,
_ => None
}
}
)
}
impl MediaType {
#[inline]
pub fn new<T, S>(top: T, sub: S) -> MediaType
where T: Into<Cow<'static, str>>, S: Into<Cow<'static, str>>
{
MediaType {
source: Source::None,
top: IndexedStr::Concrete(top.into()),
sub: IndexedStr::Concrete(sub.into()),
params: MediaParams::Static(&[]),
}
}
#[inline]
pub fn with_params<T, S, K, V, P>(top: T, sub: S, ps: P) -> MediaType
where T: Into<Cow<'static, str>>, S: Into<Cow<'static, str>>,
K: Into<Cow<'static, str>>, V: Into<Cow<'static, str>>,
P: IntoCollection<(K, V)>
{
let params = ps.mapped(|(key, val)| (
IndexedStr::Concrete(key.into()),
IndexedStr::Concrete(val.into())
));
MediaType {
source: Source::None,
top: IndexedStr::Concrete(top.into()),
sub: IndexedStr::Concrete(sub.into()),
params: MediaParams::Dynamic(params)
}
}
known_extensions!(from_extension);
#[inline]
pub fn top(&self) -> &UncasedStr {
self.top.to_str(self.source.as_str()).into()
}
#[inline]
pub fn sub(&self) -> &UncasedStr {
self.sub.to_str(self.source.as_str()).into()
}
#[inline]
pub fn specificity(&self) -> u8 {
(self.top() != "*") as u8 + (self.sub() != "*") as u8
}
pub fn exact_eq(&self, other: &MediaType) -> bool {
self == other && {
let (mut a_params, mut b_params) = (self.params(), other.params());
loop {
match (a_params.next(), b_params.next()) {
(Some(a), Some(b)) if a != b => return false,
(Some(_), Some(_)) => continue,
(Some(_), None) => return false,
(None, Some(_)) => return false,
(None, None) => return true
}
}
}
}
#[inline]
pub fn params<'a>(&'a self) -> impl Iterator<Item=(&'a str, &'a str)> + 'a {
let param_slice = match self.params {
MediaParams::Static(slice) => slice,
MediaParams::Dynamic(ref vec) => &vec[..],
};
param_slice.iter()
.map(move |&(ref key, ref val)| {
let source_str = self.source.as_str();
(key.to_str(source_str), val.to_str(source_str))
})
}
known_media_types!(media_types);
}
impl FromStr for MediaType {
type Err = String;
#[inline]
fn from_str(raw: &str) -> Result<MediaType, String> {
parse_media_type(raw).map_err(|e| e.to_string())
}
}
impl PartialEq for MediaType {
#[inline(always)]
fn eq(&self, other: &MediaType) -> bool {
self.top() == other.top() && self.sub() == other.sub()
}
}
impl Hash for MediaType {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.top().hash(state);
self.sub().hash(state);
for (key, val) in self.params() {
key.hash(state);
val.hash(state);
}
}
}
impl fmt::Display for MediaType {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Source::Known(src) = self.source {
src.fmt(f)
} else {
write!(f, "{}/{}", self.top(), self.sub())?;
for (key, val) in self.params() {
write!(f, "; {}={}", key, val)?;
}
Ok(())
}
}
}