use std::borrow::{Cow, Borrow};
use std::str::FromStr;
use std::fmt;
use std::hash::{Hash, Hasher};
use ext::IntoCollection;
use uncased::{uncased_eq, UncasedStr};
use parse::{Indexed, IndexedString, parse_media_type};
use smallvec::SmallVec;
#[derive(Debug, Clone)]
struct MediaParam {
key: IndexedString,
value: IndexedString,
}
#[derive(Debug, Clone)]
pub enum MediaParams {
Static(&'static [(IndexedString, IndexedString)]),
Dynamic(SmallVec<[(IndexedString, IndexedString); 2]>)
}
impl ::pear::parsers::Collection for MediaParams {
type Item = (IndexedString, IndexedString);
fn new() -> Self {
MediaParams::Dynamic(SmallVec::new())
}
fn add(&mut self, item: Self::Item) {
match *self {
MediaParams::Static(..) => panic!("can't add to static collection!"),
MediaParams::Dynamic(ref mut v) => v.push(item)
}
}
}
#[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: IndexedString,
#[doc(hidden)]
pub sub: IndexedString,
#[doc(hidden)]
pub params: MediaParams
}
macro_rules! media_str {
($string:expr) => (Indexed::Concrete(Cow::Borrowed($string)))
}
macro_rules! media_types {
($($name:ident ($check:ident): $str:expr, $t:expr,
$s:expr $(; $k:expr => $v:expr)*,)+) => {
$(
docify!([
Media Type for @{"**"}! @{$str}! @{"**"}!: @{"`"} @{$t}! @[/]! @{$s}!
$(; @{$k}! @[=]! @{$v}!)* @{"`"}!.
];
#[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))),*])
};
);
)+
pub fn is_known(&self) -> bool {
if let Source::Known(_) = self.source {
return true;
}
$(if self.$check() { return true })+
false
}
$(
docify!([
Returns @code{true} if the @[top-level] and sublevel types of
@code{self} are the same as those of @{"`MediaType::"}! $name
@{"`"}!.
];
#[inline(always)]
pub fn $check(&self) -> bool {
*self == MediaType::$name
}
);
)+
}}
macro_rules! from_extension {
($($ext:expr => $name:ident,)*) => (
docify!([
Returns the @[Media-Type] associated with the extension @code{ext}. Not
all extensions are recognized. If an extensions is not recognized,
@code{None} is returned. The currently recognized extensions are:
@nl
$(* @{$ext} - @{"`MediaType::"}! @[$name]! @{"`"} @nl)*
@nl
This list is likely to grow. Extensions are matched
@[case-insensitively.]
];
pub fn from_extension(ext: &str) -> Option<MediaType> {
match ext {
$(x if uncased_eq(x, $ext) => Some(MediaType::$name)),*,
_ => None
}
}
);)
}
macro_rules! parse_flexible {
($($short:expr => $name:ident,)*) => (
docify!([
Flexibly parses @code{name} into a @code{MediaType}. The parse is
@[_flexible_] because, in addition to stricly correct media types, it
recognizes the following shorthands:
@nl
$(* $short - @{"`MediaType::"}! @[$name]! @{"`"} @nl)*
@nl
];
pub fn parse_flexible(name: &str) -> Option<MediaType> {
match name {
$(x if uncased_eq(x, $short) => Some(MediaType::$name)),*,
_ => MediaType::from_str(name).ok(),
}
}
);)
}
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: Indexed::Concrete(top.into()),
sub: Indexed::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)| (
Indexed::Concrete(key.into()),
Indexed::Concrete(val.into())
));
MediaType {
source: Source::None,
top: Indexed::Concrete(top.into()),
sub: Indexed::Concrete(sub.into()),
params: MediaParams::Dynamic(params)
}
}
known_shorthands!(parse_flexible);
known_extensions!(from_extension);
#[inline]
pub fn top(&self) -> &UncasedStr {
self.top.from_source(self.source.as_str()).into()
}
#[inline]
pub fn sub(&self) -> &UncasedStr {
self.sub.from_source(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.from_source(source_str), val.from_source(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(())
}
}
}