rocket_http 0.4.2

Types, traits, and parsers for HTTP requests, responses, and headers.
Documentation
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,
}

// FIXME: `Static` is needed for `const` items. Need `const SmallVec::new`.
#[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
        }
    }
}

/// An HTTP media type.
///
/// # Usage
///
/// A `MediaType` should rarely be used directly. Instead, one is typically used
/// indirectly via types like [`Accept`] and [`ContentType`], which internally
/// contain `MediaType`s. Nonetheless, a `MediaType` can be created via the
/// [`MediaType::new()`], [`MediaType::with_params()`], and
/// [`MediaType::from_extension`()] methods. The preferred method, however, is
/// to create a `MediaType` via an associated constant.
///
/// ## Example
///
/// A media type of `application/json` can be instantiated via the
/// [`MediaType::JSON`] constant:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::MediaType;
///
/// let json = MediaType::JSON;
/// assert_eq!(json.top(), "application");
/// assert_eq!(json.sub(), "json");
///
/// let json = MediaType::new("application", "json");
/// assert_eq!(MediaType::JSON, json);
/// ```
///
/// # Comparison and Hashing
///
/// The `PartialEq` and `Hash` implementations for `MediaType` _do not_ take
/// into account parameters. This means that a media type of `text/html` is
/// equal to a media type of `text/html; charset=utf-8`, for instance. This is
/// typically the comparison that is desired.
///
/// If an exact comparison is desired that takes into account parameters, the
/// [`exact_eq()`](MediaType::exact_eq()) method can be used.
#[derive(Debug, Clone)]
pub struct MediaType {
    /// Storage for the entire media type string.
    #[doc(hidden)]
    pub source: Source,
    /// The top-level type.
    #[doc(hidden)]
    pub top: IndexedString,
    /// The subtype.
    #[doc(hidden)]
    pub sub: IndexedString,
    /// The parameters, if any.
    #[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))),*])
            };
        );
    )+

    /// Returns `true` if this MediaType is known to Rocket. In other words,
    /// returns `true` if there is an associated constant for `self`.
    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.]
    ];
        /// # Example
        ///
        /// Recognized media types:
        ///
        /// ```rust
        /// # extern crate rocket;
        /// use rocket::http::MediaType;
        ///
        /// let xml = MediaType::from_extension("xml");
        /// assert_eq!(xml, Some(MediaType::XML));
        ///
        /// let xml = MediaType::from_extension("XML");
        /// assert_eq!(xml, Some(MediaType::XML));
        /// ```
        ///
        /// An unrecognized media type:
        ///
        /// ```rust
        /// # extern crate rocket;
        /// use rocket::http::MediaType;
        ///
        /// let foo = MediaType::from_extension("foo");
        /// assert!(foo.is_none());
        /// ```
        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
    ];
        /// For regular parsing, use the
        /// [`MediaType::from_str()`](#impl-FromStr) method.
        ///
        /// # Example
        ///
        /// Using a shorthand:
        ///
        /// ```rust
        /// # extern crate rocket;
        /// use rocket::http::MediaType;
        ///
        /// let html = MediaType::parse_flexible("html");
        /// assert_eq!(html, Some(MediaType::HTML));
        ///
        /// let json = MediaType::parse_flexible("json");
        /// assert_eq!(json, Some(MediaType::JSON));
        /// ```
        ///
        /// Using the full media type:
        ///
        /// ```rust
        /// # extern crate rocket;
        /// use rocket::http::MediaType;
        ///
        /// let html = MediaType::parse_flexible("text/html; charset=utf-8");
        /// assert_eq!(html, Some(MediaType::HTML));
        ///
        /// let json = MediaType::parse_flexible("application/json");
        /// assert_eq!(json, Some(MediaType::JSON));
        ///
        /// let custom = MediaType::parse_flexible("application/x+custom");
        /// assert_eq!(custom, Some(MediaType::new("application", "x+custom")));
        /// ```
        ///
        /// An unrecognized media type:
        ///
        /// ```rust
        /// # extern crate rocket;
        /// use rocket::http::MediaType;
        ///
        /// let foo = MediaType::parse_flexible("foo");
        /// assert_eq!(foo, None);
        ///
        /// let bar = MediaType::parse_flexible("foo/bar/baz");
        /// assert_eq!(bar, None);
        /// ```
        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 {
    /// Creates a new `MediaType` with top-level type `top` and subtype `sub`.
    /// This should _only_ be used to construct uncommon or custom media types.
    /// Use an associated constant for everything else.
    ///
    /// # Example
    ///
    /// Create a custom `application/x-person` media type:
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::MediaType;
    ///
    /// let custom = MediaType::new("application", "x-person");
    /// assert_eq!(custom.top(), "application");
    /// assert_eq!(custom.sub(), "x-person");
    /// ```
    #[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(&[]),
        }
    }

    /// Creates a new `MediaType` with top-level type `top`, subtype `sub`, and
    /// parameters `ps`. This should _only_ be used to construct uncommon or
    /// custom media types. Use an associated constant for everything else.
    ///
    /// # Example
    ///
    /// Create a custom `application/x-id; id=1` media type:
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::MediaType;
    ///
    /// let id = MediaType::with_params("application", "x-id", ("id", "1"));
    /// assert_eq!(id.to_string(), "application/x-id; id=1".to_string());
    /// ```
    ///
    /// Create a custom `text/person; name=bob; weight=175` media type:
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::MediaType;
    ///
    /// let params = vec![("name", "bob"), ("ref", "2382")];
    /// let mt = MediaType::with_params("text", "person", params);
    /// assert_eq!(mt.to_string(), "text/person; name=bob; ref=2382".to_string());
    /// ```
    #[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);

    /// Returns the top-level type for this media type. The return type,
    /// `UncasedStr`, has caseless equality comparison and hashing.
    ///
    /// # Example
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::MediaType;
    ///
    /// let plain = MediaType::Plain;
    /// assert_eq!(plain.top(), "text");
    /// assert_eq!(plain.top(), "TEXT");
    /// assert_eq!(plain.top(), "Text");
    /// ```
    #[inline]
    pub fn top(&self) -> &UncasedStr {
        self.top.from_source(self.source.as_str()).into()
    }

    /// Returns the subtype for this media type. The return type,
    /// `UncasedStr`, has caseless equality comparison and hashing.
    ///
    /// # Example
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::MediaType;
    ///
    /// let plain = MediaType::Plain;
    /// assert_eq!(plain.sub(), "plain");
    /// assert_eq!(plain.sub(), "PlaIN");
    /// assert_eq!(plain.sub(), "pLaIn");
    /// ```
    #[inline]
    pub fn sub(&self) -> &UncasedStr {
        self.sub.from_source(self.source.as_str()).into()
    }

    /// Returns a `u8` representing how specific the top-level type and subtype
    /// of this media type are.
    ///
    /// The return value is either `0`, `1`, or `2`, where `2` is the most
    /// specific. A `0` is returned when both the top and sublevel types are
    /// `*`. A `1` is returned when only one of the top or sublevel types is
    /// `*`, and a `2` is returned when neither the top or sublevel types are
    /// `*`.
    ///
    /// # Example
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::MediaType;
    ///
    /// let mt = MediaType::Plain;
    /// assert_eq!(mt.specificity(), 2);
    ///
    /// let mt = MediaType::new("text", "*");
    /// assert_eq!(mt.specificity(), 1);
    ///
    /// let mt = MediaType::Any;
    /// assert_eq!(mt.specificity(), 0);
    /// ```
    #[inline]
    pub fn specificity(&self) -> u8 {
        (self.top() != "*") as u8 + (self.sub() != "*") as u8
    }

    /// Compares `self` with `other` and returns `true` if `self` and `other`
    /// are exactly equal to each other, including with respect to their
    /// parameters.
    ///
    /// This is different from the `PartialEq` implementation in that it
    /// considers parameters. If `PartialEq` returns false, this function is
    /// guaranteed to return false. Similarly, if this function returns `true`,
    /// `PartialEq` is guaranteed to return true. However, if `PartialEq`
    /// returns `true`, this function may or may not return `true`.
    ///
    /// # Example
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::MediaType;
    ///
    /// let plain = MediaType::Plain;
    /// let plain2 = MediaType::with_params("text", "plain", ("charset", "utf-8"));
    /// let just_plain = MediaType::new("text", "plain");
    ///
    /// // The `PartialEq` implementation doesn't consider parameters.
    /// assert!(plain == just_plain);
    /// assert!(just_plain == plain2);
    /// assert!(plain == plain2);
    ///
    /// // While `exact_eq` does.
    /// assert!(!plain.exact_eq(&just_plain));
    /// assert!(!plain2.exact_eq(&just_plain));
    /// assert!(plain.exact_eq(&plain2));
    /// ```
    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
                }
            }
        }
    }

    /// Returns an iterator over the (key, value) pairs of the media type's
    /// parameter list. The iterator will be empty if the media type has no
    /// parameters.
    ///
    /// # Example
    ///
    /// The `MediaType::Plain` type has one parameter: `charset=utf-8`:
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::MediaType;
    ///
    /// let plain = MediaType::Plain;
    /// let plain_params: Vec<_> = plain.params().collect();
    /// assert_eq!(plain_params, vec![("charset", "utf-8")]);
    /// ```
    ///
    /// The `MediaType::PNG` type has no parameters:
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::MediaType;
    ///
    /// let png = MediaType::PNG;
    /// assert_eq!(png.params().count(), 0);
    /// ```
    #[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 {
    // Ideally we'd return a `ParseError`, but that requires a lifetime.
    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(())
        }
    }
}