rocket_http 0.4.2

Types, traits, and parsers for HTTP requests, responses, and headers.
Documentation
use std::borrow::{Borrow, Cow};
use std::ops::Deref;
use std::str::FromStr;
use std::fmt;

use header::Header;
use media_type::{MediaType, Source};
use ext::IntoCollection;
use hyper::mime::Mime;

/// Representation of HTTP Content-Types.
///
/// # Usage
///
/// `ContentType`s should rarely be created directly. Instead, an associated
/// constant should be used; one is declared for most commonly used content
/// types.
///
/// ## Example
///
/// A Content-Type of `text/html; charset=utf-8` can be instantiated via the
/// `HTML` constant:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentType;
///
/// # #[allow(unused_variables)]
/// let html = ContentType::HTML;
/// ```
///
/// # Header
///
/// `ContentType` implements `Into<Header>`. As such, it can be used in any
/// context where an `Into<Header>` is expected:
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::ContentType;
/// use rocket::response::Response;
///
/// # #[allow(unused_variables)]
/// let response = Response::build().header(ContentType::HTML).finalize();
/// ```
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct ContentType(pub MediaType);

macro_rules! content_types {
    ($($name:ident ($check:ident): $str:expr, $t:expr,
        $s:expr $(; $k:expr => $v:expr)*,)+) => {
    $(
        docify!([
            Content Type for @{"**"}! @{$str}! @{"**"}!: @{"`"} @{$t}! @[/]! @{$s}!
            $(; @{$k}! @[=]! @{$v}!)* @{"`"}!.
        ];
            #[allow(non_upper_case_globals)]
            pub const $name: ContentType = ContentType(MediaType::$name);
        );
    )+
}}

macro_rules! from_extension {
    ($($ext:expr => $name:ident,)*) => (
    docify!([
        Returns the @[Content-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} - @{"`ContentType::"}! @[$name]! @{"`"} @nl)*
        @nl

        This list is likely to grow. Extensions are matched
        @[case-insensitively.]
    ];
        /// # Example
        ///
        /// Recognized content types:
        ///
        /// ```rust
        /// # extern crate rocket;
        /// use rocket::http::ContentType;
        ///
        /// let xml = ContentType::from_extension("xml");
        /// assert_eq!(xml, Some(ContentType::XML));
        ///
        /// let xml = ContentType::from_extension("XML");
        /// assert_eq!(xml, Some(ContentType::XML));
        /// ```
        ///
        /// An unrecognized content type:
        ///
        /// ```rust
        /// # extern crate rocket;
        /// use rocket::http::ContentType;
        ///
        /// let foo = ContentType::from_extension("foo");
        /// assert!(foo.is_none());
        /// ```
        #[inline]
        pub fn from_extension(ext: &str) -> Option<ContentType> {
            MediaType::from_extension(ext).map(ContentType)
        }
    );)
}

macro_rules! parse_flexible {
    ($($short:expr => $name:ident,)*) => (
    docify!([
        Flexibly parses @code{name} into a @code{ContentType}. The parse is
        @[_flexible_] because, in addition to stricly correct content types, it
        recognizes the following shorthands:

        @nl
        $(* $short - @{"`ContentType::"}! @[$name]! @{"`"} @nl)*
        @nl
    ];
        /// For regular parsing, use the
        /// [`ContentType::from_str()`](#impl-FromStr) method.
        ///
        /// # Example
        ///
        /// Using a shorthand:
        ///
        /// ```rust
        /// # extern crate rocket;
        /// use rocket::http::ContentType;
        ///
        /// let html = ContentType::parse_flexible("html");
        /// assert_eq!(html, Some(ContentType::HTML));
        ///
        /// let json = ContentType::parse_flexible("json");
        /// assert_eq!(json, Some(ContentType::JSON));
        /// ```
        ///
        /// Using the full content-type:
        ///
        /// ```rust
        /// # extern crate rocket;
        /// use rocket::http::ContentType;
        ///
        /// let html = ContentType::parse_flexible("text/html; charset=utf-8");
        /// assert_eq!(html, Some(ContentType::HTML));
        ///
        /// let json = ContentType::parse_flexible("application/json");
        /// assert_eq!(json, Some(ContentType::JSON));
        ///
        /// let custom = ContentType::parse_flexible("application/x+custom");
        /// assert_eq!(custom, Some(ContentType::new("application", "x+custom")));
        /// ```
        ///
        /// An unrecognized content-type:
        ///
        /// ```rust
        /// # extern crate rocket;
        /// use rocket::http::ContentType;
        ///
        /// let foo = ContentType::parse_flexible("foo");
        /// assert_eq!(foo, None);
        ///
        /// let bar = ContentType::parse_flexible("foo/bar/baz");
        /// assert_eq!(bar, None);
        /// ```
        #[inline]
        pub fn parse_flexible(name: &str) -> Option<ContentType> {
            MediaType::parse_flexible(name).map(ContentType)
        }
    );)
}

impl ContentType {
    /// Creates a new `ContentType` with top-level type `top` and subtype `sub`.
    /// This should _only_ be used to construct uncommon or custom content
    /// types. Use an associated constant for everything else.
    ///
    /// # Example
    ///
    /// Create a custom `application/x-person` content type:
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::ContentType;
    ///
    /// let custom = ContentType::new("application", "x-person");
    /// assert_eq!(custom.top(), "application");
    /// assert_eq!(custom.sub(), "x-person");
    /// ```
    #[inline(always)]
    pub fn new<T, S>(top: T, sub: S) -> ContentType
        where T: Into<Cow<'static, str>>, S: Into<Cow<'static, str>>
    {
        ContentType(MediaType::new(top, sub))
    }

    known_shorthands!(parse_flexible);

    known_extensions!(from_extension);

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

    /// Borrows the inner `MediaType` of `self`.
    ///
    /// # Example
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::{ContentType, MediaType};
    ///
    /// let http = ContentType::HTML;
    /// let media_type = http.media_type();
    /// ```
    #[inline(always)]
    pub fn media_type(&self) -> &MediaType {
        &self.0
    }

    known_media_types!(content_types);
}

impl Default for ContentType {
    /// Returns a ContentType of `Any`, or `*/*`.
    #[inline(always)]
    fn default() -> ContentType {
        ContentType::Any
    }
}

impl Deref for ContentType {
    type Target = MediaType;

    #[inline(always)]
    fn deref(&self) -> &MediaType {
        &self.0
    }
}

#[doc(hidden)]
impl<T: Borrow<Mime>> From<T> for ContentType {
    #[inline(always)]
    default fn from(mime: T) -> ContentType {
        let mime: Mime = mime.borrow().clone();
        ContentType::from(mime)
    }
}

#[doc(hidden)]
impl From<Mime> for ContentType {
    #[inline]
    fn from(mime: Mime) -> ContentType {
        // soooo inefficient.
        let params = mime.2.into_iter()
            .map(|(attr, value)| (attr.to_string(), value.to_string()))
            .collect::<Vec<_>>();

        ContentType::with_params(mime.0.to_string(), mime.1.to_string(), params)
    }
}

impl FromStr for ContentType {
    type Err = String;

    /// Parses a `ContentType` from a given Content-Type header value.
    ///
    /// # Examples
    ///
    /// Parsing an `application/json`:
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use std::str::FromStr;
    /// use rocket::http::ContentType;
    ///
    /// let json = ContentType::from_str("application/json").unwrap();
    /// assert!(json.is_known());
    /// assert_eq!(json, ContentType::JSON);
    /// ```
    ///
    /// Parsing a content type extension:
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use std::str::FromStr;
    /// use rocket::http::ContentType;
    ///
    /// let custom = ContentType::from_str("application/x-custom").unwrap();
    /// assert!(!custom.is_known());
    /// assert_eq!(custom.top(), "application");
    /// assert_eq!(custom.sub(), "x-custom");
    /// ```
    ///
    /// Parsing an invalid Content-Type value:
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use std::str::FromStr;
    /// use rocket::http::ContentType;
    ///
    /// let custom = ContentType::from_str("application//x-custom");
    /// assert!(custom.is_err());
    /// ```
    #[inline(always)]
    fn from_str(raw: &str) -> Result<ContentType, String> {
        MediaType::from_str(raw).map(ContentType)
    }
}

impl fmt::Display for ContentType {
    /// Formats the ContentType as an HTTP Content-Type value.
    ///
    /// # Example
    ///
    /// ```rust
    /// # extern crate rocket;
    /// use rocket::http::ContentType;
    ///
    /// let ct = format!("{}", ContentType::JSON);
    /// assert_eq!(ct, "application/json");
    /// ```
    #[inline(always)]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Creates a new `Header` with name `Content-Type` and the value set to the
/// HTTP rendering of this Content-Type.
impl Into<Header<'static>> for ContentType {
    #[inline(always)]
    fn into(self) -> Header<'static> {
        // FIXME: For known media types, don't do `to_string`. Store the whole
        // string as a `source` and have a way to know that the source is
        // everything. That removes the allocation here. Then, in
        // `MediaType::fmt`, write the source string out directly as well.
        //
        // We could also use an `enum` for MediaType. But that kinda sucks. But
        // maybe it's what we want.
        if let Source::Known(src) = self.0.source {
            Header::new("Content-Type", src)
        } else {
            Header::new("Content-Type", self.to_string())
        }
    }
}