Rename 'WeightedMediaType' to 'QMediaType'. More docs.

This commit nears completion of the 'http' module docs.
This commit is contained in:
Sergio Benitez 2017-06-22 04:29:59 -07:00
parent 3c4cb27d55
commit 0376fb5fe5
12 changed files with 589 additions and 76 deletions

View File

@ -52,14 +52,30 @@ impl<T> IntoCollection<T> for Vec<T> {
}
}
impl<'a, T: Clone> IntoCollection<T> for &'a [T] {
#[inline(always)]
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A> {
self.iter().cloned().collect()
}
macro_rules! impl_for_slice {
($($size:tt)*) => (
impl<'a, T: Clone> IntoCollection<T> for &'a [T $($size)*] {
#[inline(always)]
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A> {
self.iter().cloned().collect()
}
#[inline]
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, mut f: F) -> SmallVec<A> {
self.iter().cloned().map(|item| f(item)).collect()
}
#[inline]
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, mut f: F) -> SmallVec<A> {
self.iter().cloned().map(|item| f(item)).collect()
}
}
)
}
impl_for_slice!();
impl_for_slice!(; 1);
impl_for_slice!(; 2);
impl_for_slice!(; 3);
impl_for_slice!(; 4);
impl_for_slice!(; 5);
impl_for_slice!(; 6);
impl_for_slice!(; 7);
impl_for_slice!(; 8);
impl_for_slice!(; 9);
impl_for_slice!(; 10);

View File

@ -8,34 +8,68 @@ use ext::IntoCollection;
use http::{Header, MediaType};
use http::parse::parse_accept;
/// A `MediaType` with an associated quality value.
#[derive(Debug, Clone, PartialEq)]
pub struct WeightedMediaType(pub MediaType, pub Option<f32>);
pub struct QMediaType(pub MediaType, pub Option<f32>);
impl WeightedMediaType {
impl QMediaType {
/// Retrieve the weight of the media type, if there is any.
///
/// # Example
///
/// ```rust
/// use rocket::http::{MediaType, QMediaType};
///
/// let q_type = QMediaType(MediaType::HTML, Some(0.3));
/// assert_eq!(q_type.weight(), Some(0.3));
/// ```
#[inline(always)]
pub fn weight(&self) -> Option<f32> {
self.1
}
/// Retrieve the weight of the media type or a given default value.
///
/// # Example
///
/// ```rust
/// use rocket::http::{MediaType, QMediaType};
///
/// let q_type = QMediaType(MediaType::HTML, Some(0.3));
/// assert_eq!(q_type.weight_or(0.9), 0.3);
///
/// let q_type = QMediaType(MediaType::HTML, None);
/// assert_eq!(q_type.weight_or(0.9), 0.9);
/// ```
#[inline(always)]
pub fn weight_or(&self, default: f32) -> f32 {
self.1.unwrap_or(default)
}
/// Borrow the internal `MediaType`.
///
/// # Example
///
/// ```rust
/// use rocket::http::{MediaType, QMediaType};
///
/// let q_type = QMediaType(MediaType::HTML, Some(0.3));
/// assert_eq!(q_type.media_type(), &MediaType::HTML);
/// ```
#[inline(always)]
pub fn media_type(&self) -> &MediaType {
&self.0
}
}
impl From<MediaType> for WeightedMediaType {
impl From<MediaType> for QMediaType {
#[inline(always)]
fn from(media_type: MediaType) -> WeightedMediaType {
WeightedMediaType(media_type, None)
fn from(media_type: MediaType) -> QMediaType {
QMediaType(media_type, None)
}
}
impl Deref for WeightedMediaType {
impl Deref for QMediaType {
type Target = MediaType;
#[inline(always)]
@ -47,11 +81,53 @@ impl Deref for WeightedMediaType {
// FIXME: `Static` is needed for `const` items. Need `const SmallVec::new`.
#[derive(Debug, PartialEq, Clone)]
pub enum AcceptParams {
Static(&'static [WeightedMediaType]),
Dynamic(SmallVec<[WeightedMediaType; 1]>)
Static(&'static [QMediaType]),
Dynamic(SmallVec<[QMediaType; 1]>)
}
/// The HTTP Accept header.
///
/// An `Accept` header is composed of zero or more media types, each of which
/// may have an optional quality value (a [`QMediaType`]). The header is sent by an HTTP client to
/// describe the formats it accepts as well as the order in which it prefers
/// different formats.
///
/// # Usage
///
/// The Accept header of an incoming request can be retrieved via the
/// [`Request::accept`] method. The [`preferred`] method can be used to retrieve
/// the client's preferred media type.
///
/// [`Request::accept`]: /rocket/struct.Request.html#method.accept
/// [`preferred`]: /rocket/http/struct.Accept.html#method.preferred
/// [`QMediaType`]: /rocket/http/struct.QMediaType.html
///
/// An `Accept` type with a single, common media type can be easily constructed
/// via provided associated constants.
///
/// ## Example
///
/// Construct an `Accept` header with a single `application/json` media type:
///
/// ```rust
/// use rocket::http::Accept;
///
/// # #[allow(unused_variables)]
/// let accept_json = Accept::JSON;
/// ```
///
/// # Header
///
/// `Accept` implements `Into<Header>`. As such, it can be used in any context
/// where an `Into<Header>` is expected:
///
/// ```rust
/// use rocket::http::Accept;
/// use rocket::response::Response;
///
/// # #[allow(unused_variables)]
/// let response = Response::build().header(Accept::JSON).finalize();
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct Accept(AcceptParams);
@ -65,7 +141,7 @@ macro_rules! accept_constructor {
#[doc="</i>"]
#[allow(non_upper_case_globals)]
pub const $name: Accept = Accept(
AcceptParams::Static(&[WeightedMediaType(MediaType::$name, None)])
AcceptParams::Static(&[QMediaType(MediaType::$name, None)])
);
)+
};
@ -79,20 +155,62 @@ impl<T: IntoCollection<MediaType>> From<T> for Accept {
}
impl Accept {
/// Constructs a new `Accept` header from one or more media types.
///
/// The `items` parameter may be of type `QMediaType`, `&[QMediaType]`, or
/// `Vec<QMediaType>`. To prevent additional allocations, prefer to provide
/// inputs of type `QMediaType` and `Vec<QMediaType>`.
///
/// # Example
///
/// ```rust
/// use rocket::http::{QMediaType, MediaType, Accept};
///
/// // Construct an `Accept` via a `Vec<QMediaType>`.
/// let json_then_html = vec![MediaType::JSON.into(), MediaType::HTML.into()];
/// let accept = Accept::new(json_then_html);
/// assert_eq!(accept.preferred().media_type(), &MediaType::JSON);
///
/// // Construct an `Accept` via an `&[QMediaType]`.
/// let accept = Accept::new(&[MediaType::JSON.into(), MediaType::HTML.into()]);
/// assert_eq!(accept.preferred().media_type(), &MediaType::JSON);
///
/// // Construct an `Accept` via a `QMediaType`.
/// let accept = Accept::new(QMediaType(MediaType::JSON, None));
/// assert_eq!(accept.preferred().media_type(), &MediaType::JSON);
/// ```
#[inline(always)]
pub fn new<T: IntoCollection<WeightedMediaType>>(items: T) -> Accept {
pub fn new<T: IntoCollection<QMediaType>>(items: T) -> Accept {
Accept(AcceptParams::Dynamic(items.into_collection()))
}
// FIXME: IMPLEMENT THIS.
// TODO: Implement this.
// #[inline(always)]
// pub fn add<M: Into<WeightedMediaType>>(&mut self, media_type: M) {
// pub fn add<M: Into<QMediaType>>(&mut self, media_type: M) {
// self.0.push(media_type.into());
// }
/// TODO: Cache this?
pub fn preferred(&self) -> &WeightedMediaType {
static ANY: WeightedMediaType = WeightedMediaType(MediaType::Any, None);
/// Retrieve the client's preferred media type. This method follows [RFC
/// 7231 5.3.2]. If the list of media types is empty, this method returns a
/// media type of any with no quality value: (`*/*`).
///
/// [RFC 7231 5.3.2]: https://tools.ietf.org/html/rfc7231#section-5.3.2
///
/// # Example
///
/// ```rust
/// use rocket::http::{QMediaType, MediaType, Accept};
///
/// let media_types = vec![
/// QMediaType(MediaType::JSON, Some(0.3)),
/// QMediaType(MediaType::HTML, Some(0.9))
/// ];
///
/// let accept = Accept::new(media_types);
/// assert_eq!(accept.preferred().media_type(), &MediaType::HTML);
/// ```
pub fn preferred(&self) -> &QMediaType {
static ANY: QMediaType = QMediaType(MediaType::Any, None);
// See https://tools.ietf.org/html/rfc7231#section-5.3.2.
let mut all = self.iter();
@ -122,13 +240,44 @@ impl Accept {
preferred
}
/// Retrieve the first media type in `self`, if any.
///
/// # Example
///
/// ```rust
/// use rocket::http::{QMediaType, MediaType, Accept};
///
/// let accept = Accept::new(QMediaType(MediaType::XML, None));
/// assert_eq!(accept.first(), Some(&MediaType::XML.into()));
/// ```
#[inline(always)]
pub fn first(&self) -> Option<&WeightedMediaType> {
pub fn first(&self) -> Option<&QMediaType> {
self.iter().next()
}
/// Returns an iterator over all of the (quality) media types in `self`.
/// Media types are returned in the order in which they appear in the
/// header.
///
/// # Example
///
/// ```rust
/// use rocket::http::{QMediaType, MediaType, Accept};
///
/// let qmedia_types = vec![
/// QMediaType(MediaType::JSON, Some(0.3)),
/// QMediaType(MediaType::HTML, Some(0.9))
/// ];
///
/// let accept = Accept::new(qmedia_types.clone());
///
/// let mut iter = accept.iter();
/// assert_eq!(iter.next(), Some(&qmedia_types[0]));
/// assert_eq!(iter.next(), Some(&qmedia_types[1]));
/// assert_eq!(iter.next(), None);
/// ```
#[inline(always)]
pub fn iter<'a>(&'a self) -> impl Iterator<Item=&'a WeightedMediaType> + 'a {
pub fn iter<'a>(&'a self) -> impl Iterator<Item=&'a QMediaType> + 'a {
let slice = match self.0 {
AcceptParams::Static(slice) => slice,
AcceptParams::Dynamic(ref vec) => &vec[..],
@ -137,6 +286,26 @@ impl Accept {
slice.iter()
}
/// Returns an iterator over all of the (bare) media types in `self`. Media
/// types are returned in the order in which they appear in the header.
///
/// # Example
///
/// ```rust
/// use rocket::http::{QMediaType, MediaType, Accept};
///
/// let qmedia_types = vec![
/// QMediaType(MediaType::JSON, Some(0.3)),
/// QMediaType(MediaType::HTML, Some(0.9))
/// ];
///
/// let accept = Accept::new(qmedia_types.clone());
///
/// let mut iter = accept.media_types();
/// assert_eq!(iter.next(), Some(qmedia_types[0].media_type()));
/// assert_eq!(iter.next(), Some(qmedia_types[1].media_type()));
/// assert_eq!(iter.next(), None);
/// ```
#[inline(always)]
pub fn media_types<'a>(&'a self) -> impl Iterator<Item=&'a MediaType> + 'a {
self.iter().map(|weighted_mt| weighted_mt.media_type())
@ -148,8 +317,11 @@ impl Accept {
impl fmt::Display for Accept {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (i, media_type) in self.iter().enumerate() {
if i >= 1 { write!(f, ", ")?; }
write!(f, "{}", media_type.0)?;
if i >= 1 {
write!(f, ", {}", media_type.0)?;
} else {
write!(f, "{}", media_type.0)?;
}
}
Ok(())

View File

@ -108,6 +108,13 @@ impl<'h> fmt::Display for Header<'h> {
}
/// A collection of headers, mapping a header name to its many ordered values.
///
/// # Case-Insensitivity
///
/// All header names, including those passed in to `HeaderMap` methods and those
/// stored in an existing `HeaderMap`, are treated case-insensitively. This
/// means that, for instance, a look for a header by the name of "aBC" will
/// returns values for headers of names "AbC", "ABC", "abc", and so on.
#[derive(Clone, Debug, PartialEq, Default)]
pub struct HeaderMap<'h> {
headers: OrderMap<Uncased<'h>, Vec<Cow<'h, str>>>
@ -115,6 +122,14 @@ pub struct HeaderMap<'h> {
impl<'h> HeaderMap<'h> {
/// Returns an empty collection.
///
/// # Example
///
/// ```rust
/// use rocket::http::HeaderMap;
///
/// let map = HeaderMap::new();
/// ```
#[inline(always)]
pub fn new() -> HeaderMap<'h> {
HeaderMap { headers: OrderMap::new() }
@ -277,6 +292,21 @@ impl<'h> HeaderMap<'h> {
/// assert_eq!(map.get_one("Content-Type"), Some("image/gif"));
/// assert_eq!(map.len(), 1);
/// ```
///
/// An example of case-insensitivity.
///
/// ```rust
/// use rocket::http::{HeaderMap, Header, ContentType};
///
/// let mut map = HeaderMap::new();
///
/// map.replace(ContentType::JSON);
/// assert_eq!(map.get_one("Content-Type"), Some("application/json"));
///
/// map.replace(Header::new("CONTENT-type", "image/gif"));
/// assert_eq!(map.get_one("Content-Type"), Some("image/gif"));
/// assert_eq!(map.len(), 1);
/// ```
#[inline(always)]
pub fn replace<'p: 'h, H: Into<Header<'p>>>(&mut self, header: H) -> bool {
let header = header.into();
@ -310,6 +340,7 @@ impl<'h> HeaderMap<'h> {
/// Replaces all of the values for a header with name `name` with `values`.
/// This a low-level method and should rarely be used.
///
///
/// # Example
///
/// ```rust
@ -475,6 +506,38 @@ impl<'h> HeaderMap<'h> {
/// Returns an iterator over all of the `Header`s stored in the map. Header
/// names are returned in no specific order, but all values for a given
/// header name are grouped together, and values are in FIFO order.
///
/// # Example
///
/// ```rust
/// use rocket::http::{HeaderMap, Header};
///
/// // The headers we'll be storing.
/// let all_headers = vec![
/// Header::new("X-Custom", "value_1"),
/// Header::new("X-Other", "other"),
/// Header::new("X-Third", "third"),
/// ];
///
/// // Create a map, store all of the headers.
/// let mut map = HeaderMap::new();
/// for header in all_headers {
/// map.add(header)
/// }
///
/// // Ensure there are three headers via the iterator.
/// assert_eq!(map.iter().count(), 3);
///
/// // Actually iterate through them.
/// for header in map.iter() {
/// match header.name() {
/// "X-Custom" => assert_eq!(header.value(), "value_1"),
/// "X-Other" => assert_eq!(header.value(), "other"),
/// "X-Third" => assert_eq!(header.value(), "third"),
/// _ => unreachable!("there are only three headers")
/// }
/// }
/// ```
pub fn iter<'s>(&'s self) -> impl Iterator<Item=Header<'s>> {
self.headers.iter().flat_map(|(key, values)| {
values.iter().map(move |val| {
@ -487,7 +550,39 @@ impl<'h> HeaderMap<'h> {
/// in the map. Header names are returned in no specific order, but all
/// values for a given header name are grouped together, and values are in
/// FIFO order.
// TODO: Figure out what the return type is to implement IntoIterator.
///
/// # Example
///
/// ```rust
/// use rocket::http::{HeaderMap, Header};
///
/// // The headers we'll be storing.
/// let all_headers = vec![
/// Header::new("X-Custom", "value_1"),
/// Header::new("X-Other", "other"),
/// Header::new("X-Third", "third"),
/// ];
///
/// // Create a map, store all of the headers.
/// let mut map = HeaderMap::new();
/// for header in all_headers {
/// map.add(header)
/// }
///
/// // Ensure there are three headers via the iterator.
/// assert_eq!(map.iter().count(), 3);
///
/// // Actually iterate through them.
/// for header in map.into_iter() {
/// match header.name() {
/// "X-Custom" => assert_eq!(header.value(), "value_1"),
/// "X-Other" => assert_eq!(header.value(), "other"),
/// "X-Third" => assert_eq!(header.value(), "third"),
/// _ => unreachable!("there are only three headers")
/// }
/// }
/// ```
// TODO: Implement IntoIterator.
#[inline(always)]
pub fn into_iter(self) -> impl Iterator<Item=Header<'h>> {
self.headers.into_iter().flat_map(|(name, value)| {

View File

@ -40,8 +40,49 @@ impl Source {
}
}
// Describe a media type. In particular, describe its comparison and hashing
// semantics.
/// 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
/// [`new`], [`with_params`], and [`from_extension`] methods. The preferred
/// method, however, is to create a `MediaType` via an associated constant.
///
/// [`Accept`]: /rocket/http/struct.Accept.html
/// [`ContentType`]: /rocket/http/struct.ContentType.html
/// [`new`]: /rocket/http/struct.MediaType.html#method.new
/// [`with_params`]: /rocket/http/struct.MediaType.html#method.with_params
/// [`from_extension`]: /rocket/http/struct.MediaType.html#method.from_extension
///
/// ## Example
///
/// A media type of `application/json` can be insantiated via the `JSON`
/// constant:
///
/// ```rust
/// 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`] method can be used.
///
/// [`exact_eq`]: /rocket/http/struct.MediaType.html#method.exact_eq
#[derive(Debug, Clone)]
pub struct MediaType {
/// Storage for the entire media type string.

View File

@ -29,7 +29,7 @@ pub mod uncased;
pub use self::method::Method;
pub use self::content_type::ContentType;
pub use self::accept::{Accept, WeightedMediaType};
pub use self::accept::{Accept, QMediaType};
pub use self::status::{Status, StatusClass};
pub use self::header::{Header, HeaderMap};
pub use self::raw_str::RawStr;

View File

@ -3,7 +3,7 @@ use pear::parsers::*;
use http::parse::checkers::is_whitespace;
use http::parse::media_type::media_type;
use http::{MediaType, Accept, WeightedMediaType};
use http::{MediaType, Accept, QMediaType};
fn q<'a>(_: &'a str, media_type: &MediaType) -> ParseResult<&'a str, Option<f32>> {
match media_type.params().next() {
@ -24,7 +24,7 @@ fn accept<'a>(input: &mut &'a str) -> ParseResult<&'a str, Accept> {
skip_while(is_whitespace);
let media_type = media_type();
let weight = q(&media_type);
media_types.push(WeightedMediaType(media_type, weight));
media_types.push(QMediaType(media_type, weight));
});
Accept::new(media_types)

View File

@ -10,31 +10,139 @@ use url;
use http::uncased::UncasedStr;
/// A reference to a raw HTTP string.
/// A reference to a string inside of a raw HTTP message.
///
/// A `RawStr` is an unsanitzed, unvalidated, and undecoded raw string from an
/// HTTP message. It exists to separate validated string inputs, represented by
/// the `String`, `&str`, and `Cow<str>` types, from unvalidated inputs,
/// represented by `&RawStr`.
///
/// # Validation
///
/// An `&RawStr` should be converted into one of the validated string input
/// types through methods on `RawStr`. These methods are summarized below:
///
/// * **[`url_decode`]** - used to decode a raw string in a form value context
/// * **[`percent_decode`], [`percent_decode_lossy`]** - used to
/// percent-decode a raw string, typically in a URL context
/// * **[`html_escape`]** - used to decode a string for use in HTML templates
/// * **[`as_str`]** - used when the `RawStr` is known to be safe in the
/// context of its intended use. Use sparingly and with care!
/// * **[`as_uncased_str`]** - used when the `RawStr` is known to be safe in
/// the context of its intended, uncased use
///
/// [`as_str`]: /rocket/http/struct.RawStr.html#method.as_str
/// [`as_uncased_str`]: /rocket/http/struct.RawStr.html#method.as_uncased_str
/// [`url_decode`]: /rocket/http/struct.RawStr.html#method.url_decode
/// [`html_escape`]: /rocket/http/struct.RawStr.html#method.html_escape
/// [`percent_decode`]: /rocket/http/struct.RawStr.html#method.percent_decode
/// [`percent_decode_lossy`]: /rocket/http/struct.RawStr.html#method.percent_decode_lossy
///
/// # Usage
///
/// A `RawStr` is a dynamically sized type (just like `str`). It is always used
/// through a reference an as `&RawStr` (just like &str). You'll likely
/// encounted an `&RawStr` as a parameter via [`FromParam`] or as a form value
/// via [`FromFormValue`].
///
/// [`FromParam`]: /rocket/request/trait.FromParam.html
/// [`FromFormValue`]: /rocket/request/trait.FromFormValue.html
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RawStr(str);
impl RawStr {
/// Constructs an `&RawStr` from an `&str` at no cost.
///
/// # Example
///
/// ```rust
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::from_str("Hello, world!");
///
/// // `into` can also be used; note that the type must be specified
/// let raw_str: &RawStr = "Hello, world!".into();
/// ```
#[inline(always)]
pub fn from_str<'a>(string: &'a str) -> &'a RawStr {
string.into()
}
/// Returns a percent-decoded version of the string.
///
/// # Errors
///
/// Returns an `Err` if the percent encoded values are not valid UTF-8.
///
/// # Example
///
/// With a valid string:
///
/// ```rust
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::from_str("Hello%21");
/// let decoded = raw_str.percent_decode();
/// assert_eq!(decoded, Ok("Hello!".into()));
/// ```
///
/// With an invalid string:
///
/// ```rust
/// use rocket::http::RawStr;
///
/// // Note: Rocket should never hand you a bad `&RawStr`.
/// let bad_str = unsafe { ::std::str::from_utf8_unchecked(b"a=\xff") };
/// let bad_raw_str = RawStr::from_str(bad_str);
/// assert!(bad_raw_str.percent_decode().is_err());
/// ```
#[inline(always)]
pub fn as_str(&self) -> &str {
self
pub fn percent_decode(&self) -> Result<Cow<str>, Utf8Error> {
url::percent_encoding::percent_decode(self.as_bytes()).decode_utf8()
}
/// Returns a percent-decoded version of the string. Any invalid UTF-8
/// percent-encoded byte sequences will be replaced <20> U+FFFD, the
/// replacement character.
///
/// # Errors
///
/// Returns an `Err` if the percent encoded values are not valid UTF-8.
///
/// # Example
///
/// With a valid string:
///
/// ```rust
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::from_str("Hello%21");
/// let decoded = raw_str.percent_decode_lossy();
/// assert_eq!(decoded, "Hello!");
/// ```
///
/// With an invalid string:
///
/// ```rust
/// use rocket::http::RawStr;
///
/// // Note: Rocket should never hand you a bad `&RawStr`.
/// let bad_str = unsafe { ::std::str::from_utf8_unchecked(b"a=\xff") };
/// let bad_raw_str = RawStr::from_str(bad_str);
/// assert_eq!(bad_raw_str.percent_decode_lossy(), "a=<3D>");
/// ```
#[inline(always)]
pub fn as_uncased_str(&self) -> &UncasedStr {
self.as_str().into()
pub fn percent_decode_lossy(&self) -> Cow<str> {
url::percent_encoding::percent_decode(self.as_bytes()).decode_utf8_lossy()
}
/// Returns a URL-decoded version of the string. This is identical to
/// percent decoding except that '+' characters are converted into spaces.
/// percent decoding except that `+` characters are converted into spaces.
/// This is the encoding used by form values.
///
/// If the percent encoded values are not valid UTF-8, an `Err` is returned.
/// # Errors
///
/// Returns an `Err` if the percent encoded values are not valid UTF-8.
///
/// # Example
///
@ -45,7 +153,6 @@ impl RawStr {
/// let decoded = raw_str.url_decode();
/// assert_eq!(decoded, Ok("Hello, world!".to_string()));
/// ```
#[inline]
pub fn url_decode(&self) -> Result<String, Utf8Error> {
let replaced = self.replace("+", " ");
RawStr::from_str(replaced.as_str())
@ -53,22 +160,15 @@ impl RawStr {
.map(|cow| cow.into_owned())
}
/// Returns a percent-decoded version of the string. If the percent encoded
/// values are not valid UTF-8, an `Err` is returned.
#[inline(always)]
pub fn percent_decode(&self) -> Result<Cow<str>, Utf8Error> {
url::percent_encoding::percent_decode(self.as_bytes()).decode_utf8()
}
/// Returns a percent-decoded version of the string. Any invalid UTF-8
/// percent-encoded byte sequences will be replaced <20> U+FFFD, the
/// replacement character.
#[inline(always)]
pub fn percent_decode_lossy(&self) -> Cow<str> {
url::percent_encoding::percent_decode(self.as_bytes()).decode_utf8_lossy()
}
/// Do some HTML escaping.
/// Returns an HTML escaped version of `self`. Allocates only when
/// characters need to be escaped.
///
/// The following characters are escaped: `&`, `<`, `>`, `"`, `'`, `/`,
/// <code>`</code>. **This suffices as long as the escaped string is not
/// used in an execution context such as inside of &lt;script> or &lt;style>
/// tags!** See the [OWASP XSS Prevention Rules] for more information.
///
/// [OWASP XSS Prevention Rules]: https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet#XSS_Prevention_Rules
///
/// # Example
///
@ -136,6 +236,42 @@ impl RawStr {
Cow::Borrowed(self.as_str())
}
}
/// Converts `self` into an `&str`.
///
/// This method should be used sparingly. **Only use this method when you
/// are absolutely certain that doing so is safe.**
///
/// # Example
///
/// ```rust
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::from_str("Hello, world!");
/// assert_eq!(raw_str.as_str(), "Hello, world!");
/// ```
#[inline(always)]
pub fn as_str(&self) -> &str {
self
}
/// Converts `self` into an `&UncasedStr`.
///
/// This method should be used sparingly. **Only use this method when you
/// are absolutely certain that doing so is safe.**
///
/// # Example
///
/// ```rust
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::from_str("Content-Type");
/// assert!(raw_str.as_uncased_str() == "content-TYPE");
/// ```
#[inline(always)]
pub fn as_uncased_str(&self) -> &UncasedStr {
self.as_str().into()
}
}
impl<'a> From<&'a str> for &'a RawStr {

View File

@ -19,7 +19,7 @@ pub enum StatusClass {
Unknown
}
/// Structure representing HTTP statuses: an integer code and a reason phrase.
/// Structure representing an HTTP status: an integer code and a reason phrase.
///
/// # Usage
///

View File

@ -3,8 +3,15 @@ use request::FormItems;
/// Trait to create an instance of some type from an HTTP form.
/// [Form](struct.Form.html) requires its generic type to implement this trait.
///
/// # Deriving
///
/// This trait can be automatically derived via the
/// [rocket_codegen](/rocket_codegen) plugin:
/// [rocket_codegen](/rocket_codegen) plugin. When deriving `FromForm`, every
/// field in the structure must implement
/// [FromFormValue](trait.FromFormValue.html). Rocket validates each field in
/// the structure by calling its `FromFormValue` implemention. You may wish to
/// implement `FromFormValue` for your own types for custom, automatic
/// validation.
///
/// ```rust
/// #![feature(plugin, custom_derive)]
@ -21,8 +28,10 @@ use request::FormItems;
/// # fn main() { }
/// ```
///
/// The type can then be parsed from incoming form data via the `data`
/// parameter and `Form` type.
/// # Data Guard
///
/// Types that implement `FromForm` can be parsed directly from incoming form
/// data via the `data` parameter and `Form` type.
///
/// ```rust
/// # #![feature(plugin, custom_derive)]
@ -39,21 +48,70 @@ use request::FormItems;
/// # fn main() { }
/// ```
///
/// When deriving `FromForm`, every field in the structure must implement
/// [FromFormValue](trait.FromFormValue.html).
///
/// # Implementing
///
/// An implementation of `FromForm` uses the [FormItems](struct.FormItems.html)
/// Implementing `FromForm` should be a rare occurence. Prefer instead to use
/// Rocket's built-in derivation.
///
/// When implementing `FromForm`, use use the [FormItems](struct.FormItems.html)
/// iterator to iterate through the raw form key/value pairs. Be aware that form
/// fields that are typically hidden from your application, such as `_method`,
/// will be present while iterating.
/// will be present while iterating. Ensure that you adhere to the properties of
/// the `strict` parameter, as detailed in the documentation below.
///
/// ## Example
///
/// Consider the following scenario: we have a struct `Item` with field name
/// `field`. We'd like to parse any form that has a field named either `balloon`
/// _or_ `space`, and we'd like that field's value to be the value for our
/// structure's `field`. The following snippet shows how this would be
/// implemented:
///
/// ```rust
/// use rocket::request::{FromForm, FormItems};
///
/// struct Item {
/// field: String
/// }
///
/// impl<'f> FromForm<'f> for Item {
/// // In practice, we'd use a more descriptive error type.
/// type Error = ();
///
/// fn from_form(items: &mut FormItems<'f>, strict: bool) -> Result<Item, ()> {
/// let mut field = None;
///
/// for (key, value) in items {
/// match key.as_str() {
/// "balloon" | "space" if field.is_none() => {
/// let decoded = value.url_decode().map_err(|_| ())?;
/// field = Some(decoded);
/// }
/// _ if strict => return Err(()),
/// _ => { /* allow extra value when not strict */ }
/// }
/// }
///
/// field.map(|field| Item { field }).ok_or(())
/// }
/// }
/// ```
pub trait FromForm<'f>: Sized {
/// The associated error to be returned when parsing fails.
type Error;
/// Parses an instance of `Self` from the iterator of form items `it` or
/// returns an instance of `Self::Error` if one cannot be parsed.
/// Parses an instance of `Self` from the iterator of form items `it`.
///
/// Extra form field are allowed when `strict` is `false` and disallowed
/// when `strict` is `true`.
///
/// # Errors
///
/// If `Self` cannot be parsed from the given form items, an instance of
/// `Self::Error` will be returned.
///
/// When `strict` is `true` and unexpected, extra fields are present in
/// `it`, an instance of `Self::Error` will be returned.
fn from_form(it: &mut FormItems<'f>, strict: bool) -> Result<Self, Self::Error>;
}

View File

@ -67,7 +67,7 @@ use http::RawStr;
/// `"false"`, `"off"`, or not present. In any other case, the raw form
/// value is returned in the `Err` value.
///
/// * **&RawStr**
/// * **&[RawStr](/rocket/http/struct.RawStr.html)**
///
/// _This implementation always returns successfully._
///

View File

@ -82,7 +82,7 @@ use http::RawStr;
/// type returns successfully. Otherwise, the raw path segment is returned
/// in the `Err` value.
///
/// * **&RawStr**
/// * **&[`RawStr`](/rocket/http/struct.RawStr.html)**
///
/// _This implementation always returns successfully._
///

View File

@ -327,11 +327,6 @@ impl<'r> Request<'r> {
}).as_ref()
}
#[inline(always)]
pub fn accept_first(&self) -> Option<&MediaType> {
self.accept().and_then(|accept| accept.first()).map(|wmt| wmt.media_type())
}
pub fn format(&self) -> Option<&MediaType> {
static ANY: MediaType = MediaType::Any;
if self.method.supports_payload() {