use std::borrow::{Cow, Borrow};
use std::str::FromStr;
use std::fmt;
use std::hash::{Hash, Hasher};
use either::Either;
use crate::ext::IntoCollection;
use crate::uncased::UncasedStr;
use crate::parse::{Indexed, IndexedStr, parse_media_type};
use smallvec::SmallVec;
#[derive(Debug, Clone)]
pub struct MediaType {
pub(crate) source: Source,
pub(crate) top: IndexedStr<'static>,
pub(crate) sub: IndexedStr<'static>,
pub(crate) params: MediaParams
}
#[derive(Debug, Clone)]
pub(crate) enum MediaParams {
Static(&'static [(&'static str, &'static str)]),
Dynamic(SmallVec<[(IndexedStr<'static>, IndexedStr<'static>); 2]>)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Source {
Known(&'static str),
Custom(Cow<'static, str>),
None
}
impl From<Cow<'static, str>> for Source {
fn from(custom: Cow<'static, str>) -> Source {
Source::Custom(custom)
}
}
macro_rules! media_types {
($($name:ident ($check:ident): $str:expr, $t:expr,
$s:expr $(; $k:expr => $v:expr)*,)+) => {
$(
/// Media Type for
#[doc = concat!("**", $str, "**: ")]
#[doc = concat!("`", $t, "/", $s, $("; ", $k, "=", $v,)* "`")]
#[allow(non_upper_case_globals)]
pub const $name: MediaType = MediaType::new_known(
concat!($t, "/", $s, $("; ", $k, "=", $v),*),
$t, $s, &[$(($k, $v)),*]
);
)+
pub fn is_known(&self) -> bool {
if let Source::Known(_) = self.source {
return true;
}
$(if self.$check() { return true })+
false
}
$(
#[doc = concat!("`MediaType::", stringify!($name), "`, ")]
#[doc = concat!("`", $t, "/", $s, "`.")]
#[inline(always)]
pub fn $check(&self) -> bool {
*self == MediaType::$name
}
)+
}}
macro_rules! from_extension {
($($ext:expr => $name:ident,)*) => (
$(
#[doc = concat!("* ", $ext, " - [`MediaType::", stringify!($name), "`]")]
)*
pub fn from_extension(ext: &str) -> Option<MediaType> {
match ext {
$(x if uncased::eq(x, $ext) => Some(MediaType::$name)),*,
_ => None
}
}
)
}
macro_rules! extension {
($($ext:expr => $name:ident,)*) => (
#[inline]
pub fn extension(&self) -> Option<&UncasedStr> {
$(if self == &MediaType::$name { return Some($ext.into()) })*
None
}
)
}
macro_rules! parse_flexible {
($($short:expr => $name:ident,)*) => (
$(
#[doc = concat!("* ", $short, " - [`MediaType::", stringify!($name), "`]")]
)*
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(&[]),
}
}
pub fn with_params<K, V, P>(mut self, ps: P) -> MediaType
where K: Into<Cow<'static, str>>,
V: Into<Cow<'static, str>>,
P: IntoCollection<(K, V)>
{
use Indexed::Concrete;
let params = ps.mapped(|(k, v)| (Concrete(k.into()), Concrete(v.into())));
self.params = MediaParams::Dynamic(params);
self
}
#[inline]
pub const fn const_new(
top: &'static str,
sub: &'static str,
params: &'static [(&'static str, &'static str)]
) -> MediaType {
MediaType {
source: Source::None,
top: Indexed::Concrete(Cow::Borrowed(top)),
sub: Indexed::Concrete(Cow::Borrowed(sub)),
params: MediaParams::Static(params),
}
}
#[inline]
pub(crate) const fn new_known(
source: &'static str,
top: &'static str,
sub: &'static str,
params: &'static [(&'static str, &'static str)]
) -> MediaType {
MediaType {
source: Source::Known(source),
top: Indexed::Concrete(Cow::Borrowed(top)),
sub: Indexed::Concrete(Cow::Borrowed(sub)),
params: MediaParams::Static(params),
}
}
pub(crate) fn known_source(&self) -> Option<&'static str> {
match self.source {
Source::Known(string) => Some(string),
Source::Custom(Cow::Borrowed(string)) => Some(string),
_ => None
}
}
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 && self.params().eq(other.params())
}
#[inline]
pub fn params(&self) -> impl Iterator<Item=(&'_ UncasedStr, &'_ str)> + '_ {
let raw = match self.params {
MediaParams::Static(slice) => Either::Left(slice.iter().cloned()),
MediaParams::Dynamic(ref vec) => {
Either::Right(vec.iter().map(move |&(ref key, ref val)| {
let source_str = self.source.as_str();
(key.from_source(source_str), val.from_source(source_str))
}))
}
};
raw.map(|(k, v)| (k.into(), v))
}
#[inline]
pub fn param<'a>(&'a self, name: &str) -> Option<&'a str> {
self.params()
.filter(|(k, _)| *k == name)
.map(|(_, v)| v)
.next()
}
known_extensions!(extension);
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 Eq for MediaType { }
impl Hash for MediaType {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.top().hash(state);
self.sub().hash(state);
}
}
impl fmt::Display for MediaType {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(src) = self.known_source() {
src.fmt(f)
} else {
write!(f, "{}/{}", self.top(), self.sub())?;
for (key, val) in self.params() {
write!(f, "; {}={}", key, val)?;
}
Ok(())
}
}
}
impl Default for MediaParams {
fn default() -> Self {
MediaParams::Dynamic(SmallVec::new())
}
}
impl Extend<(IndexedStr<'static>, IndexedStr<'static>)> for MediaParams {
fn extend<T>(&mut self, iter: T)
where T: IntoIterator<Item = (IndexedStr<'static>, IndexedStr<'static>)>
{
match self {
MediaParams::Static(..) => panic!("can't add to static collection!"),
MediaParams::Dynamic(ref mut v) => v.extend(iter)
}
}
}
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
}
}
}