mirror of https://github.com/rwf2/Rocket.git
Rename 'WeightedMediaType' to 'QMediaType'. More docs.
This commit nears completion of the 'http' module docs.
This commit is contained in:
parent
3c4cb27d55
commit
0376fb5fe5
|
@ -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);
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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)| {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 <script> or <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 {
|
||||
|
|
|
@ -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
|
||||
///
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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._
|
||||
///
|
||||
|
|
|
@ -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._
|
||||
///
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue