Initial cleanup of 'http' docs. Add 'handler::Outcome' docs.

This commit also changes the signature of the 'ContentType'
'from_extension" method so that it returns an 'Option<ContentType>' as
opposed to 'ContentType'.

This commit also disallows negative quality values in 'Accept' media
types.
This commit is contained in:
Sergio Benitez 2017-06-19 17:20:10 -07:00
parent cdf9ff9bde
commit 6a7fde6d70
11 changed files with 144 additions and 93 deletions

View File

@ -197,10 +197,10 @@ impl RouteGenerateExt for RouteParams {
#[allow(non_snake_case)] #[allow(non_snake_case)]
let $ident: $ty = match let $ident: $ty = match
::rocket::request::FromRequest::from_request(__req) { ::rocket::request::FromRequest::from_request(__req) {
::rocket::outcome::Outcome::Success(v) => v, ::rocket::Outcome::Success(v) => v,
::rocket::outcome::Outcome::Forward(_) => ::rocket::Outcome::Forward(_) =>
return ::rocket::Outcome::forward(__data), return ::rocket::Outcome::Forward(__data),
::rocket::outcome::Outcome::Failure((code, _)) => { ::rocket::Outcome::Failure((code, _)) => {
return ::rocket::Outcome::Failure(code) return ::rocket::Outcome::Failure(code)
}, },
}; };

View File

@ -34,7 +34,7 @@ impl Context {
} }
let data_type = data_type_str.as_ref() let data_type = data_type_str.as_ref()
.map(|ext| ContentType::from_extension(ext)) .and_then(|ext| ContentType::from_extension(ext))
.unwrap_or(ContentType::HTML); .unwrap_or(ContentType::HTML);
templates.insert(name, TemplateInfo { templates.insert(name, TemplateInfo {

View File

@ -5,12 +5,7 @@ use super::data::BodyReader;
use http::hyper::net::NetworkStream; use http::hyper::net::NetworkStream;
use http::hyper::h1::HttpReader; use http::hyper::h1::HttpReader;
// It's very unfortunate that we have to wrap `BodyReader` in a `BufReader`
// since it already contains another `BufReader`. The issue is that Hyper's
// `HttpReader` doesn't implement `BufRead`. Unfortunately, this will likely
// stay "double buffered" until we switch HTTP libraries.
// |-- peek buf --| // |-- peek buf --|
// pub type InnerStream = Chain<Cursor<Vec<u8>>, BufReader<BodyReader>>;
pub type InnerStream = Chain<Cursor<Vec<u8>>, BodyReader>; pub type InnerStream = Chain<Cursor<Vec<u8>>, BodyReader>;
/// Raw data stream of a request body. /// Raw data stream of a request body.
@ -18,9 +13,11 @@ pub type InnerStream = Chain<Cursor<Vec<u8>>, BodyReader>;
/// This stream can only be obtained by calling /// This stream can only be obtained by calling
/// [Data::open](/rocket/data/struct.Data.html#method.open). The stream contains /// [Data::open](/rocket/data/struct.Data.html#method.open). The stream contains
/// all of the data in the body of the request. It exposes no methods directly. /// all of the data in the body of the request. It exposes no methods directly.
/// Instead, it must be used as an opaque `Read` or `BufRead` structure. /// Instead, it must be used as an opaque `Read` structure.
pub struct DataStream(pub(crate) InnerStream); pub struct DataStream(pub(crate) InnerStream);
// TODO: Have a `BufRead` impl for `DataStream`. At the moment, this isn't
// possible since Hyper's `HttpReader` doesn't implement `BufRead`.
impl Read for DataStream { impl Read for DataStream {
#[inline(always)] #[inline(always)]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
@ -29,18 +26,6 @@ impl Read for DataStream {
} }
} }
// impl BufRead for DataStream {
// #[inline(always)]
// fn fill_buf(&mut self) -> io::Result<&[u8]> {
// self.0.fill_buf()
// }
// #[inline(always)]
// fn consume(&mut self, amt: usize) {
// self.0.consume(amt)
// }
// }
pub fn kill_stream(stream: &mut BodyReader) { pub fn kill_stream(stream: &mut BodyReader) {
// Only do the expensive reading if we're not sure we're done. // Only do the expensive reading if we're not sure we're done.
use self::HttpReader::*; use self::HttpReader::*;

View File

@ -7,6 +7,9 @@
//! fairings to rewrite or record information about requests and responses, or //! fairings to rewrite or record information about requests and responses, or
//! to perform an action once a Rocket application has launched. //! to perform an action once a Rocket application has launched.
//! //!
//! To learn more about writing a fairing, see the [`Fairing` trait
//! documentation](/rocket/fairing/trait.Fairing.html).
//!
//! ## Attaching //! ## Attaching
//! //!
//! You must inform Rocket about fairings that you wish to be active by calling //! You must inform Rocket about fairings that you wish to be active by calling

View File

@ -11,6 +11,22 @@ use outcome;
pub type Outcome<'r> = outcome::Outcome<Response<'r>, Status, Data>; pub type Outcome<'r> = outcome::Outcome<Response<'r>, Status, Data>;
impl<'r> Outcome<'r> { impl<'r> Outcome<'r> {
/// Return the `Outcome` of response to `req` from `responder`.
///
/// If the responder responds with `Ok`, an outcome of `Success` is returns
/// with the response. If the outcomes reeturns `Err`, an outcome of
/// `Failure` is returned with the status code.
///
/// # Example
///
/// ```rust
/// use rocket::{Request, Data};
/// use rocket::handler::Outcome;
///
/// fn str_responder(req: &Request, _: Data) -> Outcome<'static> {
/// Outcome::from(req, "Hello, world!")
/// }
/// ```
#[inline] #[inline]
pub fn from<T: Responder<'r>>(req: &Request, responder: T) -> Outcome<'r> { pub fn from<T: Responder<'r>>(req: &Request, responder: T) -> Outcome<'r> {
match responder.respond_to(req) { match responder.respond_to(req) {
@ -19,11 +35,44 @@ impl<'r> Outcome<'r> {
} }
} }
/// Return an `Outcome` of `Failure` with the status code `code`. This is
/// equivalent to `Outcome::Failure(code)`.
///
/// This method exists to be used during manual routing where
/// `rocket::handler::Outcome` is imported instead of `rocket::Outcome`.
///
/// # Example
///
/// ```rust
/// use rocket::{Request, Data};
/// use rocket::handler::Outcome;
/// use rocket::http::Status;
///
/// fn bad_req_route(_: &Request, _: Data) -> Outcome<'static> {
/// Outcome::failure(Status::BadRequest)
/// }
/// ```
#[inline(always)] #[inline(always)]
pub fn failure(code: Status) -> Outcome<'static> { pub fn failure(code: Status) -> Outcome<'static> {
outcome::Outcome::Failure(code) outcome::Outcome::Failure(code)
} }
/// Return an `Outcome` of `Forward` with the data `data`. This is
/// equivalent to `Outcome::Forward(data)`.
///
/// This method exists to be used during manual routing where
/// `rocket::handler::Outcome` is imported instead of `rocket::Outcome`.
///
/// # Example
///
/// ```rust
/// use rocket::{Request, Data};
/// use rocket::handler::Outcome;
///
/// fn always_forward(_: &Request, data: Data) -> Outcome<'static> {
/// Outcome::forward(data)
/// }
/// ```
#[inline(always)] #[inline(always)]
pub fn forward(data: Data) -> Outcome<'static> { pub fn forward(data: Data) -> Outcome<'static> {
outcome::Outcome::Forward(data) outcome::Outcome::Forward(data)

View File

@ -26,11 +26,6 @@ impl WeightedMediaType {
pub fn media_type(&self) -> &MediaType { pub fn media_type(&self) -> &MediaType {
&self.0 &self.0
} }
#[inline(always)]
pub fn into_media_type(self) -> MediaType {
self.0
}
} }
impl From<MediaType> for WeightedMediaType { impl From<MediaType> for WeightedMediaType {

View File

@ -46,29 +46,13 @@ macro_rules! content_types {
($($name:ident ($check:ident): $str:expr, $t:expr, ($($name:ident ($check:ident): $str:expr, $t:expr,
$s:expr $(; $k:expr => $v:expr)*),+) => { $s:expr $(; $k:expr => $v:expr)*),+) => {
$( $(
#[doc="Media type for <b>"] #[doc=$str] #[doc="</b>: <i>"] #[doc="Content-Type for <b>"] #[doc=$str] #[doc="</b>: <i>"]
#[doc=$t] #[doc="/"] #[doc=$s] #[doc=$t] #[doc="/"] #[doc=$s]
$(#[doc="; "] #[doc=$k] #[doc=" = "] #[doc=$v])* $(#[doc="; "] #[doc=$k] #[doc=" = "] #[doc=$v])*
#[doc="</i>"] #[doc="</i>"]
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
pub const $name: ContentType = ContentType(MediaType::$name); pub const $name: ContentType = ContentType(MediaType::$name);
#[doc="Returns `true` if `self` is the media type for <b>"]
#[doc=$str]
#[doc="</b>, "]
/// without considering parameters.
#[inline(always)]
pub fn $check(&self) -> bool {
*self == ContentType::$name
}
)+ )+
/// Returns `true` if this `ContentType` is known to Rocket, that is,
/// there is an associated constant for `self`.
pub fn is_known(&self) -> bool {
$(if self.$check() { return true })+
false
}
}; };
} }
@ -95,11 +79,12 @@ impl ContentType {
ContentType(MediaType::new(top, sub)) ContentType(MediaType::new(top, sub))
} }
/// Returns the Content-Type associated with the extension `ext`. Not all /// Returns the Content-Type associated with the extension `ext` if the
/// extensions are recognized. If an extensions is not recognized, then this /// extension is recognized. Not all extensions are recognized. If an
/// method returns a ContentType of `Any`. The currently recognized /// extensions is not recognized, then this method returns `None`. The
/// extensions are: txt, html, htm, xml, js, css, json, png, gif, bmp, jpeg, /// currently recognized extensions are txt, html, htm, xml, csv, js, css,
/// jpg, and pdf. /// json, png, gif, bmp, jpeg, jpg, webp, svg, pdf, ttf, otf, woff, and
/// woff2.
/// ///
/// # Example /// # Example
/// ///
@ -109,7 +94,7 @@ impl ContentType {
/// use rocket::http::ContentType; /// use rocket::http::ContentType;
/// ///
/// let xml = ContentType::from_extension("xml"); /// let xml = ContentType::from_extension("xml");
/// assert!(xml.is_xml()); /// assert_eq!(xml, Some(ContentType::XML));
/// ``` /// ```
/// ///
/// An unrecognized content type: /// An unrecognized content type:
@ -118,12 +103,11 @@ impl ContentType {
/// use rocket::http::ContentType; /// use rocket::http::ContentType;
/// ///
/// let foo = ContentType::from_extension("foo"); /// let foo = ContentType::from_extension("foo");
/// assert!(foo.is_any()); /// assert!(foo.is_none());
/// ``` /// ```
pub fn from_extension(ext: &str) -> ContentType { #[inline]
MediaType::from_extension(ext) pub fn from_extension(ext: &str) -> Option<ContentType> {
.map(|mt| ContentType(mt)) MediaType::from_extension(ext).map(ContentType)
.unwrap_or(ContentType::Any)
} }
/// Creates a new `ContentType` with top-level type `top`, subtype `sub`, /// Creates a new `ContentType` with top-level type `top`, subtype `sub`,
@ -159,16 +143,21 @@ impl ContentType {
ContentType(MediaType::with_params(top, sub, ps)) ContentType(MediaType::with_params(top, sub, ps))
} }
/// Borrows the inner `MediaType` of `self`.
///
/// # Example
///
/// ```rust
/// use rocket::http::{ContentType, MediaType};
///
/// let http = ContentType::HTML;
/// let media_type = http.media_type();
/// ```
#[inline(always)] #[inline(always)]
pub fn media_type(&self) -> &MediaType { pub fn media_type(&self) -> &MediaType {
&self.0 &self.0
} }
#[inline(always)]
pub fn into_media_type(self) -> MediaType {
self.0
}
known_media_types!(content_types); known_media_types!(content_types);
} }

View File

@ -100,30 +100,29 @@ macro_rules! media_types {
macro_rules! from_extension { macro_rules! from_extension {
($($ext:expr => $name:ident),*) => ( ($($ext:expr => $name:ident),*) => (
/// Returns the Media-Type associated with the extension `ext`. Not all /// Returns the Media-Type associated with the extension `ext`. Not all
/// extensions are recognized. If an extensions is not recognized, then this /// extensions are recognized. If an extensions is not recognized,
/// method returns a ContentType of `Any`. The currently recognized /// `None` is returned. The currently recognized extensions are
/// extensions include
$(#[doc=$ext]#[doc=","])* $(#[doc=$ext]#[doc=","])*
/// and is likely to grow. /// and is likely to grow.
/// ///
/// # Example /// # Example
/// ///
/// A recognized content type: /// A recognized media type:
/// ///
/// ```rust /// ```rust
/// use rocket::http::ContentType; /// use rocket::http::MediaType;
/// ///
/// let xml = ContentType::from_extension("xml"); /// let xml = MediaType::from_extension("xml");
/// assert!(xml.is_xml()); /// assert_eq!(xml, Some(MediaType::XML));
/// ``` /// ```
/// ///
/// An unrecognized content type: /// An unrecognized media type:
/// ///
/// ```rust /// ```rust
/// use rocket::http::ContentType; /// use rocket::http::MediaType;
/// ///
/// let foo = ContentType::from_extension("foo"); /// let foo = MediaType::from_extension("foo");
/// assert!(foo.is_any()); /// assert!(foo.is_none());
/// ``` /// ```
pub fn from_extension(ext: &str) -> Option<MediaType> { pub fn from_extension(ext: &str) -> Option<MediaType> {
match ext { match ext {

View File

@ -9,6 +9,7 @@ fn q<'a>(_: &'a str, media_type: &MediaType) -> ParseResult<&'a str, Option<f32>
match media_type.params().next() { match media_type.params().next() {
Some(("q", value)) if value.len() <= 5 => match value.parse::<f32>().ok() { Some(("q", value)) if value.len() <= 5 => match value.parse::<f32>().ok() {
Some(q) if q > 1. => ParseError::custom("accept", "q value must be <= 1"), Some(q) if q > 1. => ParseError::custom("accept", "q value must be <= 1"),
Some(q) if q < 0. => ParseError::custom("accept", "q value must be > 0"),
Some(q) => ParseResult::Done(Some(q)), Some(q) => ParseResult::Done(Some(q)),
None => ParseError::custom("accept", "q value must be float") None => ParseError::custom("accept", "q value must be float")
}, },

View File

@ -14,8 +14,8 @@ use std::fmt;
/// A reference to an uncased (case-preserving) ASCII string. This is typically /// A reference to an uncased (case-preserving) ASCII string. This is typically
/// created from an `&str` as follows: /// created from an `&str` as follows:
/// ///
/// ```rust,ignore /// ```rust
/// use rocket::http::ascii::UncasedStr; /// use rocket::http::uncased::UncasedStr;
/// ///
/// let ascii_ref: &UncasedStr = "Hello, world!".into(); /// let ascii_ref: &UncasedStr = "Hello, world!".into();
/// ``` /// ```
@ -23,11 +23,34 @@ use std::fmt;
pub struct UncasedStr(str); pub struct UncasedStr(str);
impl UncasedStr { impl UncasedStr {
/// Returns a reference to an `UncasedStr` from an `&str`.
///
/// # Example
///
/// ```rust
/// use rocket::http::uncased::UncasedStr;
///
/// let uncased_str = UncasedStr::new("Hello!");
/// assert_eq!(uncased_str, "hello!");
/// assert_eq!(uncased_str, "Hello!");
/// assert_eq!(uncased_str, "HeLLo!");
/// ```
#[inline(always)] #[inline(always)]
pub fn new(string: &str) -> &UncasedStr { pub fn new(string: &str) -> &UncasedStr {
unsafe { &*(string as *const str as *const UncasedStr) } unsafe { &*(string as *const str as *const UncasedStr) }
} }
/// Returns `self` as an `&str`.
///
/// # Example
///
/// ```rust
/// use rocket::http::uncased::UncasedStr;
///
/// let uncased_str = UncasedStr::new("Hello!");
/// assert_eq!(uncased_str.as_str(), "Hello!");
/// assert_ne!(uncased_str.as_str(), "hELLo!");
/// ```
#[inline(always)] #[inline(always)]
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
&self.0 &self.0
@ -109,39 +132,46 @@ impl fmt::Display for UncasedStr {
} }
} }
/// An uncased (case-preserving) ASCII string. /// An uncased (case-preserving), owned _or_ borrowed ASCII string.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Uncased<'s> { pub struct Uncased<'s> {
#[doc(hidden)]
pub string: Cow<'s, str> pub string: Cow<'s, str>
} }
impl<'s> Uncased<'s> { impl<'s> Uncased<'s> {
/// Creates a new UncaseAscii string. /// Creates a new `Uncased` string from `string` without allocating.
/// ///
/// # Example /// # Example
/// ///
/// ```rust,ignore /// ```rust
/// use rocket::http::ascii::Uncased; /// use rocket::http::uncased::Uncased;
/// ///
/// let uncased_ascii = UncasedAScii::new("Content-Type"); /// let uncased = Uncased::new("Content-Type");
/// assert_eq!(uncased, "content-type");
/// assert_eq!(uncased, "CONTENT-Type");
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn new<S: Into<Cow<'s, str>>>(string: S) -> Uncased<'s> { pub fn new<S: Into<Cow<'s, str>>>(string: S) -> Uncased<'s> {
Uncased { string: string.into() } Uncased { string: string.into() }
} }
/// Converts `self` into an owned `String`, allocating if necessary, /// Converts `self` into an owned `String`, allocating if necessary.
///
/// # Example
///
/// ```rust
/// use rocket::http::uncased::Uncased;
///
/// let uncased = Uncased::new("Content-Type");
/// let string = uncased.into_string();
/// assert_eq!(string, "Content-Type".to_string());
/// ```
#[inline(always)] #[inline(always)]
pub fn into_string(self) -> String { pub fn into_string(self) -> String {
self.string.into_owned() self.string.into_owned()
} }
/// Borrows the inner string.
#[inline(always)]
pub fn as_str(&self) -> &str {
self.string.borrow()
}
/// Returns the inner `Cow`. /// Returns the inner `Cow`.
#[doc(hidden)] #[doc(hidden)]
#[inline(always)] #[inline(always)]
@ -155,14 +185,14 @@ impl<'a> Deref for Uncased<'a> {
#[inline(always)] #[inline(always)]
fn deref(&self) -> &UncasedStr { fn deref(&self) -> &UncasedStr {
self.as_str().into() UncasedStr::new(self.string.borrow())
} }
} }
impl<'a> AsRef<UncasedStr> for Uncased<'a>{ impl<'a> AsRef<UncasedStr> for Uncased<'a>{
#[inline(always)] #[inline(always)]
fn as_ref(&self) -> &UncasedStr { fn as_ref(&self) -> &UncasedStr {
self.as_str().into() UncasedStr::new(self.string.borrow())
} }
} }
@ -265,9 +295,10 @@ impl<'s> Hash for Uncased<'s> {
} }
} }
/// Returns true if `s1` and `s2` are equal without considering case. That is, /// Returns true if `s1` and `s2` are equal without considering case.
/// for ASCII strings, this function returns s1.to_lower() == s2.to_lower(), but ///
/// does it in a much faster way. /// That is, for ASCII strings, this function returns `s1.to_lower() ==
/// s2.to_lower()`, but does it in a much faster way.
#[inline(always)] #[inline(always)]
pub fn uncased_eq<S1: AsRef<str>, S2: AsRef<str>>(s1: S1, s2: S2) -> bool { pub fn uncased_eq<S1: AsRef<str>, S2: AsRef<str>>(s1: S1, s2: S2) -> bool {
let ascii_ref_1: &UncasedStr = s1.as_ref().into(); let ascii_ref_1: &UncasedStr = s1.as_ref().into();

View File

@ -85,8 +85,7 @@ impl Responder<'static> for NamedFile {
if let Some(ext) = self.path().extension() { if let Some(ext) = self.path().extension() {
// TODO: Use Cow for lowercase. // TODO: Use Cow for lowercase.
let ext_string = ext.to_string_lossy().to_lowercase(); let ext_string = ext.to_string_lossy().to_lowercase();
let content_type = ContentType::from_extension(&ext_string); if let Some(content_type) = ContentType::from_extension(&ext_string) {
if !content_type.is_any() {
response.set_header(content_type); response.set_header(content_type);
} }
} }