Fully document all public items in core.

This commit is contained in:
Sergio Benitez 2018-10-10 04:20:25 -07:00
parent f5219174d0
commit e34b2f3703
18 changed files with 472 additions and 173 deletions

View File

@ -2,18 +2,29 @@ use {Request, Data};
use handler::{Outcome, ErrorHandler}; use handler::{Outcome, ErrorHandler};
use http::{Method, MediaType}; use http::{Method, MediaType};
/// Type of a static handler, which users annotate with Rocket's attribute.
pub type StaticHandler = for<'r> fn(&'r Request, Data) -> Outcome<'r>; pub type StaticHandler = for<'r> fn(&'r Request, Data) -> Outcome<'r>;
/// Information generated by the `route` attribute during codegen.
pub struct StaticRouteInfo { pub struct StaticRouteInfo {
/// The route's name, i.e, the name of the function.
pub name: &'static str, pub name: &'static str,
/// The route's method.
pub method: Method, pub method: Method,
/// The route's path, without the base mount point.
pub path: &'static str, pub path: &'static str,
/// The route's format, if any.
pub format: Option<MediaType>, pub format: Option<MediaType>,
/// The route's handler, i.e, the annotated function.
pub handler: StaticHandler, pub handler: StaticHandler,
/// The route's rank, if any.
pub rank: Option<isize>, pub rank: Option<isize>,
} }
/// Information generated by the `catch` attribute during codegen.
pub struct StaticCatchInfo { pub struct StaticCatchInfo {
/// The catcher's status code.
pub code: u16, pub code: u16,
/// The catcher's handler, i.e, the annotated function.
pub handler: ErrorHandler, pub handler: ErrorHandler,
} }

View File

@ -15,7 +15,7 @@ pub struct ConfigBuilder {
/// The number of workers to run in parallel. /// The number of workers to run in parallel.
pub workers: u16, pub workers: u16,
/// Keep-alive timeout in seconds or None if disabled. /// Keep-alive timeout in seconds or None if disabled.
pub keep_alive: Option<u32>, pub keep_alive: u32,
/// How much information to log. /// How much information to log.
pub log_level: LoggingLevel, pub log_level: LoggingLevel,
/// The secret key. /// The secret key.
@ -64,7 +64,7 @@ impl ConfigBuilder {
address: config.address, address: config.address,
port: config.port, port: config.port,
workers: config.workers, workers: config.workers,
keep_alive: config.keep_alive, keep_alive: config.keep_alive.unwrap_or(0),
log_level: config.log_level, log_level: config.log_level,
secret_key: None, secret_key: None,
tls: None, tls: None,
@ -130,7 +130,7 @@ impl ConfigBuilder {
self self
} }
/// Sets the keep-alive timeout to `timeout` seconds. If `timeout` is `None`, /// Sets the keep-alive timeout to `timeout` seconds. If `timeout` is `0`,
/// keep-alive is disabled. /// keep-alive is disabled.
/// ///
/// # Example /// # Example
@ -145,14 +145,14 @@ impl ConfigBuilder {
/// assert_eq!(config.keep_alive, Some(10)); /// assert_eq!(config.keep_alive, Some(10));
/// ///
/// let config = Config::build(Environment::Staging) /// let config = Config::build(Environment::Staging)
/// .keep_alive(None) /// .keep_alive(0)
/// .unwrap(); /// .unwrap();
/// ///
/// assert_eq!(config.keep_alive, None); /// assert_eq!(config.keep_alive, None);
/// ``` /// ```
#[inline] #[inline]
pub fn keep_alive<T: Into<Option<u32>>>(mut self, timeout: T) -> Self { pub fn keep_alive(mut self, timeout: u32) -> Self {
self.keep_alive = timeout.into(); self.keep_alive = timeout;
self self
} }
@ -311,7 +311,7 @@ impl ConfigBuilder {
/// .address("127.0.0.1") /// .address("127.0.0.1")
/// .port(700) /// .port(700)
/// .workers(12) /// .workers(12)
/// .keep_alive(None) /// .keep_alive(0)
/// .finalize(); /// .finalize();
/// ///
/// assert!(config.is_ok()); /// assert!(config.is_ok());

View File

@ -305,7 +305,7 @@ impl Config {
/// * **address**: String /// * **address**: String
/// * **port**: Integer (16-bit unsigned) /// * **port**: Integer (16-bit unsigned)
/// * **workers**: Integer (16-bit unsigned) /// * **workers**: Integer (16-bit unsigned)
/// * **keep_alive**: Integer or Boolean (false) or String ('none') /// * **keep_alive**: Integer
/// * **log**: String /// * **log**: String
/// * **secret_key**: String (256-bit base64) /// * **secret_key**: String (256-bit base64)
/// * **tls**: Table (`certs` (path as String), `key` (path as String)) /// * **tls**: Table (`certs` (path as String), `key` (path as String))
@ -315,7 +315,7 @@ impl Config {
address => (str, set_address, id), address => (str, set_address, id),
port => (u16, set_port, ok), port => (u16, set_port, ok),
workers => (u16, set_workers, ok), workers => (u16, set_workers, ok),
keep_alive => (u32_option, set_keep_alive, ok), keep_alive => (u32, set_keep_alive, ok),
log => (log_level, set_log_level, ok), log => (log_level, set_log_level, ok),
secret_key => (str, set_secret_key, id), secret_key => (str, set_secret_key, id),
tls => (tls_config, set_raw_tls, id), tls => (tls_config, set_raw_tls, id),
@ -422,7 +422,7 @@ impl Config {
self.workers = workers; self.workers = workers;
} }
/// Sets the keep-alive timeout to `timeout` seconds. If `timeout` is `None`, /// Sets the keep-alive timeout to `timeout` seconds. If `timeout` is `0`,
/// keep-alive is disabled. /// keep-alive is disabled.
/// ///
/// # Example /// # Example
@ -438,13 +438,17 @@ impl Config {
/// config.set_keep_alive(10); /// config.set_keep_alive(10);
/// ///
/// // Disable keep-alive. /// // Disable keep-alive.
/// config.set_keep_alive(None); /// config.set_keep_alive(0);
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline] #[inline]
pub fn set_keep_alive<T: Into<Option<u32>>>(&mut self, timeout: T) { pub fn set_keep_alive(&mut self, timeout: u32) {
self.keep_alive = timeout.into(); if timeout == 0 {
self.keep_alive = None;
} else {
self.keep_alive = Some(timeout);
}
} }
/// Sets the `secret_key` in `self` to `key` which must be a 256-bit base64 /// Sets the `secret_key` in `self` to `key` which must be a 256-bit base64

View File

@ -3,7 +3,6 @@ use std::fmt;
#[cfg(feature = "tls")] use http::tls::{Certificate, PrivateKey}; #[cfg(feature = "tls")] use http::tls::{Certificate, PrivateKey};
use config::{Result, Config, Value, ConfigError, LoggingLevel}; use config::{Result, Config, Value, ConfigError, LoggingLevel};
use http::uncased::uncased_eq;
use http::Key; use http::Key;
#[derive(Clone)] #[derive(Clone)]
@ -215,6 +214,13 @@ pub fn u16(conf: &Config, name: &str, value: &Value) -> Result<u16> {
} }
} }
pub fn u32(conf: &Config, name: &str, value: &Value) -> Result<u32> {
match value.as_integer() {
Some(x) if x >= 0 && x <= (u32::max_value() as i64) => Ok(x as u32),
_ => Err(conf.bad_type(name, value.type_str(), "a 32-bit unsigned integer"))
}
}
pub fn log_level(conf: &Config, pub fn log_level(conf: &Config,
name: &str, name: &str,
value: &Value value: &Value
@ -260,21 +266,3 @@ pub fn limits(conf: &Config, name: &str, value: &Value) -> Result<Limits> {
Ok(limits) Ok(limits)
} }
pub fn u32_option(conf: &Config, name: &str, value: &Value) -> Result<Option<u32>> {
let expect = "a 32-bit unsigned integer or 'none' or 'false'";
let err = Err(conf.bad_type(name, value.type_str(), expect));
match value.as_integer() {
Some(x) if x >= 0 && x <= (u32::max_value() as i64) => Ok(Some(x as u32)),
Some(_) => err,
None => match value.as_str() {
Some(v) if uncased_eq(v, "none") => Ok(None),
Some(_) => err,
_ => match value.as_bool() {
Some(false) => Ok(None),
_ => err
}
}
}
}

View File

@ -31,37 +31,25 @@
//! not used by Rocket itself but can be used by external libraries. The //! not used by Rocket itself but can be used by external libraries. The
//! standard configuration parameters are: //! standard configuration parameters are:
//! //!
//! * **address**: _string_ an IP address or host the application will //! | name | type | description | examples |
//! listen on //! |------------|----------------|-------------------------------------------------------------|----------------------------|
//! * examples: `"localhost"`, `"0.0.0.0"`, `"1.2.3.4"` //! | address | string | ip address or host to listen on | `"localhost"`, `"1.2.3.4"` |
//! * **port**: _integer_ a port number to listen on //! | port | integer | port number to listen on | `8000`, `80` |
//! * examples: `"8000"`, `"80"`, `"4242"` //! | keep_alive | integer | keep-alive timeout in seconds | `0` (disable), `10` |
//! * **workers**: _integer_ the number of concurrent workers to use //! | workers | integer | number of concurrent thread workers | `36`, `512` |
//! * examples: `12`, `1`, `4` //! | log | string | max log level: `"off"`, `"normal"`, `"debug"`, `"critical"` | `"off"`, `"normal"` |
//! * **keep_alive**: _integer, 'false', or 'none'_ timeout, in seconds, for //! | secret_key | 256-bit base64 | secret key for private cookies | `"8Xui8SI..."` (44 chars) |
//! HTTP keep-alive. disabled on 'false' or 'none' //! | tls | table | tls config table with two keys (`certs`, `key`) | _see below_ |
//! * examples: `5`, `60`, `false`, `"none"` //! | tls.certs | string | path to certificate chain in PEM format | `"private/cert.pem"` |
//! * **log**: _string_ how much information to log; one of `"off"`, //! | tls.key | string | path to private key for `tls.certs` in PEM format | `"private/key.pem"` |
//! `"normal"`, `"debug"`, or `"critical"` //! | limits | table | map from data type (string) to data limit (integer: bytes) | `{ forms = 65536 }` |
//! * **secret_key**: _string_ a 256-bit base64 encoded string (44
//! characters) to use as the secret key
//! * example: `"8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="`
//! * **tls**: _table_ a table with two keys:
//! 1. `certs`: _string_ a path to a certificate chain in PEM format
//! 2. `key`: _string_ a path to a private key file in PEM format for the
//! certificate in `certs`
//!
//! * example: `{ certs = "/path/to/certs.pem", key = "/path/to/key.pem" }`
//! * **limits**: _table_ a table where each key (_string_) corresponds to a
//! data type and the value (`u64`) corresponds to the maximum size in bytes
//! Rocket should accept for that type.
//! * example: `{ forms = 65536 }` (maximum form size to 64KiB)
//! //!
//! ### Rocket.toml //! ### Rocket.toml
//! //!
//! The `Rocket.toml` file is used to specify the configuration parameters for //! `Rocket.toml` is a Rocket application's configuration file. It can
//! each environment. The file is optional. If it is not present, the default //! optionally be used to specify the configuration parameters for each
//! configuration parameters are used. //! environment. If it is not present, the default configuration parameters or
//! environment supplied parameters are used.
//! //!
//! The file must be a series of TOML tables, at most one for each environment, //! The file must be a series of TOML tables, at most one for each environment,
//! and an optional "global" table, where each table contains key-value pairs //! and an optional "global" table, where each table contains key-value pairs
@ -603,7 +591,7 @@ mod test {
port = 7810 port = 7810
workers = 21 workers = 21
log = "critical" log = "critical"
keep_alive = false keep_alive = 0
secret_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=" secret_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="
template_dir = "mine" template_dir = "mine"
json = true json = true
@ -615,7 +603,7 @@ mod test {
.port(7810) .port(7810)
.workers(21) .workers(21)
.log_level(LoggingLevel::Critical) .log_level(LoggingLevel::Critical)
.keep_alive(None) .keep_alive(0)
.secret_key("8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=") .secret_key("8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=")
.extra("template_dir", "mine") .extra("template_dir", "mine")
.extra("json", true) .extra("json", true)
@ -919,23 +907,9 @@ mod test {
check_config!(RocketConfig::parse(r#" check_config!(RocketConfig::parse(r#"
[stage] [stage]
keep_alive = false keep_alive = 0
"#.to_string(), TEST_CONFIG_FILENAME), { "#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(None) default_config(Staging).keep_alive(0)
});
check_config!(RocketConfig::parse(r#"
[stage]
keep_alive = "none"
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(None)
});
check_config!(RocketConfig::parse(r#"
[stage]
keep_alive = "None"
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(None)
}); });
} }

View File

@ -18,10 +18,15 @@ use router::Route;
/// other kinds of launch errors. /// other kinds of launch errors.
#[derive(Debug)] #[derive(Debug)]
pub enum LaunchErrorKind { pub enum LaunchErrorKind {
/// Binding to the provided address/port failed.
Bind(hyper::Error), Bind(hyper::Error),
/// An I/O error occurred during launch.
Io(io::Error), Io(io::Error),
/// Route collisions were detected.
Collision(Vec<(Route, Route)>), Collision(Vec<(Route, Route)>),
/// A launch fairing reported an error.
FailedFairings(Vec<&'static str>), FailedFairings(Vec<&'static str>),
/// An otherwise uncategorized error occurred during launch.
Unknown(Box<::std::error::Error + Send + Sync>) Unknown(Box<::std::error::Error + Send + Sync>)
} }
@ -214,3 +219,46 @@ impl Drop for LaunchError {
} }
} }
} }
use http::uri;
use http::ext::IntoOwned;
use http::route::{Error as SegmentError};
/// Error returned by [`set_uri()`](::Route::set_uri()) on invalid URIs.
#[derive(Debug)]
pub enum RouteUriError {
/// The base (mount point) or route path contains invalid segments.
Segment,
/// The route URI is not a valid URI.
Uri(uri::Error<'static>),
/// The base (mount point) contains dynamic segments.
DynamicBase,
}
impl<'a> From<(&'a str, SegmentError<'a>)> for RouteUriError {
fn from(_: (&'a str, SegmentError<'a>)) -> Self {
RouteUriError::Segment
}
}
impl<'a> From<uri::Error<'a>> for RouteUriError {
fn from(error: uri::Error<'a>) -> Self {
RouteUriError::Uri(error.into_owned())
}
}
impl fmt::Display for RouteUriError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
RouteUriError::Segment => {
write!(f, "The URI contains malformed dynamic route path segments.")
}
RouteUriError::DynamicBase => {
write!(f, "The mount point contains dynamic parameters.")
}
RouteUriError::Uri(error) => {
write!(f, "Malformed URI: {}", error)
}
}
}
}

View File

@ -151,6 +151,7 @@ pub trait Handler: Cloneable + Send + Sync + 'static {
/// any type. Instead, implement `Clone`. All types that implement `Clone` and /// any type. Instead, implement `Clone`. All types that implement `Clone` and
/// `Handler` automatically implement `Cloneable`. /// `Handler` automatically implement `Cloneable`.
pub trait Cloneable { pub trait Cloneable {
/// Clones `self`.
fn clone_handler(&self) -> Box<Handler>; fn clone_handler(&self) -> Box<Handler>;
} }

View File

@ -26,9 +26,49 @@ pub enum FormParseError<'f> {
/// [`Form`](::request::Form) and [`LenientForm`](::request::LenientForm). /// [`Form`](::request::Form) and [`LenientForm`](::request::LenientForm).
#[derive(Debug)] #[derive(Debug)]
pub enum FormDataError<'f, E> { pub enum FormDataError<'f, E> {
/// An I/O error occurred while reading reading the data stream. This can
/// also mean that the form contained invalid UTF-8.
Io(io::Error), Io(io::Error),
/// The form string (in `.0`) is malformed and was unable to be parsed as
/// HTTP `application/x-www-form-urlencoded` data.
Malformed(&'f str), Malformed(&'f str),
/// The form string (in `.1`) failed to parse as the intended structure. The
/// error type in `.0` contains further details.
Parse(E, &'f str) Parse(E, &'f str)
} }
/// Alias to the type of form errors returned by the [`FromData`]
/// implementations of [`Form<T>`] where the [`FromForm`] implementation for `T`
/// was derived.
///
/// This alias is particularly useful when "catching" form errors in routes.
///
/// [`FromData`]: ::data::FromData
/// [`Form<T>`]: ::request::Form
/// [`FromForm`]: ::request::FromForm
///
/// # Example
///
/// ```rust
/// # #![feature(proc_macro_hygiene, decl_macro)]
/// # #[macro_use] extern crate rocket;
/// use rocket::request::{Form, FormError, FormDataError};
///
/// #[derive(FromForm)]
/// struct Input {
/// value: String,
/// }
///
/// #[post("/", data = "<sink>")]
/// fn submit(sink: Result<Form<Input>, FormError>) -> String {
/// match sink {
/// Ok(form) => form.into_inner().value,
/// Err(FormDataError::Io(_)) => "I/O error".into(),
/// Err(FormDataError::Malformed(f)) | Err(FormDataError::Parse(_, f)) => {
/// format!("invalid form input: {}", f)
/// }
/// }
/// }
/// # fn main() {}
/// ```
pub type FormError<'f> = FormDataError<'f, FormParseError<'f>>; pub type FormError<'f> = FormDataError<'f, FormParseError<'f>>;

View File

@ -26,30 +26,18 @@ use http::{Status, uri::FromUriParam};
/// for more information on deriving or implementing the trait. /// for more information on deriving or implementing the trait.
/// ///
/// Because `Form` implements `FromData`, it can be used directly as a target of /// Because `Form` implements `FromData`, it can be used directly as a target of
/// the `data = "<param>"` route parameter. For instance, if some structure of /// the `data = "<param>"` route parameter as long as its generic type
/// type `T` implements the `FromForm` trait, an incoming form can be /// implements the `FromForm` trait:
/// automatically parsed into the `T` structure with the following route and
/// handler:
///
/// ```rust,ignore
/// #[post("/form_submit", data = "<param>")]
/// fn submit(form: Form<T>) ... { ... }
/// ```
///
/// A type of `Form<T>` automatically dereferences into an `&T`, though you can
/// also tranform a `Form<T>` into a `T` by calling
/// [`into_inner()`](Form::into_inner()). Thanks automatic dereferencing, you
/// can access fields of `T` transparently through a `Form<T>`:
/// ///
/// ```rust /// ```rust
/// # #![feature(proc_macro_hygiene, decl_macro)] /// # #![feature(proc_macro_hygiene, decl_macro)]
/// # #![allow(deprecated, unused_attributes)]
/// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket;
/// use rocket::request::Form; /// use rocket::request::Form;
/// use rocket::http::RawStr; /// use rocket::http::RawStr;
/// ///
/// #[derive(FromForm)] /// #[derive(FromForm)]
/// struct UserInput<'f> { /// struct UserInput<'f> {
/// // The raw, undecoded value. You _probably_ want `String` instead.
/// value: &'f RawStr /// value: &'f RawStr
/// } /// }
/// ///
@ -60,10 +48,17 @@ use http::{Status, uri::FromUriParam};
/// # fn main() { } /// # fn main() { }
/// ``` /// ```
/// ///
/// A type of `Form<T>` automatically dereferences into an `&T`, though you can
/// also transform a `Form<T>` into a `T` by calling
/// [`into_inner()`](Form::into_inner()). Thanks to automatic dereferencing, you
/// can access fields of `T` transparently through a `Form<T>`, as seen above
/// with `user_input.value`.
///
/// For posterity, the owned analog of the `UserInput` type above is: /// For posterity, the owned analog of the `UserInput` type above is:
/// ///
/// ```rust /// ```rust
/// struct OwnedUserInput { /// struct OwnedUserInput {
/// // The decoded value. You _probably_ want this.
/// value: String /// value: String
/// } /// }
/// ``` /// ```
@ -86,22 +81,21 @@ use http::{Status, uri::FromUriParam};
/// # fn main() { } /// # fn main() { }
/// ``` /// ```
/// ///
/// Note that no lifetime annotations are required: Rust is able to infer the /// Note that no lifetime annotations are required in either case.
/// lifetime as `` `static``. Because the lifetime is `` `static``, the
/// `into_inner` method can be used to directly retrieve the parsed value.
/// ///
/// ## Performance and Correctness Considerations /// ## `&RawStr` vs. `String`
/// ///
/// Whether you should use a `&RawStr` or `String` in your `FromForm` type /// Whether you should use a `&RawStr` or `String` in your `FromForm` type
/// depends on your use case. The primary question to answer is: _Can the input /// depends on your use case. The primary question to answer is: _Can the input
/// contain characters that must be URL encoded?_ Note that this includes /// contain characters that must be URL encoded?_ Note that this includes common
/// common characters such as spaces. If so, then you must use `String`, whose /// characters such as spaces. If so, then you must use `String`, whose
/// [`FromFormValue`](::request::FromFormValue) implementation automatically URL /// [`FromFormValue`](::request::FromFormValue) implementation automatically URL
/// decodes strings. Because the `&RawStr` references will refer directly to the /// decodes the value. Because the `&RawStr` references will refer directly to
/// underlying form data, they will be raw and URL encoded. /// the underlying form data, they will be raw and URL encoded.
/// ///
/// If your string values will not contain URL encoded characters, using /// If it is known that string values will not contain URL encoded characters,
/// `&RawStr` will result in fewer allocation and is thus preferred. /// or you wish to handle decoding and validation yourself, using `&RawStr` will
/// result in fewer allocation and is thus preferred.
/// ///
/// ## Incoming Data Limits /// ## Incoming Data Limits
/// ///

View File

@ -104,10 +104,12 @@ use http::RawStr;
/// ``` /// ```
#[derive(Debug)] #[derive(Debug)]
pub enum FormItems<'f> { pub enum FormItems<'f> {
#[doc(hidden)]
Raw { Raw {
string: &'f RawStr, string: &'f RawStr,
next_index: usize next_index: usize
}, },
#[doc(hidden)]
Cooked { Cooked {
items: &'f [FormItem<'f>], items: &'f [FormItem<'f>],
next_index: usize next_index: usize
@ -117,7 +119,7 @@ pub enum FormItems<'f> {
/// A form items returned by the [`FormItems`] iterator. /// A form items returned by the [`FormItems`] iterator.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct FormItem<'f> { pub struct FormItem<'f> {
/// The full, nonempty string for the item, not including delimiters. /// The full, nonempty string for the item, not including `&` delimiters.
pub raw: &'f RawStr, pub raw: &'f RawStr,
/// The key for the item, which may be empty if `value` is nonempty. /// The key for the item, which may be empty if `value` is nonempty.
/// ///
@ -134,16 +136,75 @@ pub struct FormItem<'f> {
} }
impl<'f> FormItem<'f> { impl<'f> FormItem<'f> {
/// Extracts the raw `key` and `value` as a tuple.
///
/// This is equivalent to `(item.key, item.value)`.
///
/// # Example
///
/// ```rust
/// use rocket::request::FormItem;
///
/// let item = FormItem {
/// raw: "hello=%2C+world%21".into(),
/// key: "hello".into(),
/// value: "%2C+world%21".into(),
/// };
///
/// let (key, value) = item.key_value();
/// assert_eq!(key, "hello");
/// assert_eq!(value, "%2C+world%21");
/// ```
#[inline(always)] #[inline(always)]
pub fn key_value(&self) -> (&'f RawStr, &'f RawStr) { pub fn key_value(&self) -> (&'f RawStr, &'f RawStr) {
(self.key, self.value) (self.key, self.value)
} }
/// Extracts and lossy URL decodes the `key` and `value` as a tuple.
///
/// This is equivalent to `(item.key.url_decode_lossy(),
/// item.value.url_decode_lossy)`.
///
/// # Example
///
/// ```rust
/// use rocket::request::FormItem;
///
/// let item = FormItem {
/// raw: "hello=%2C+world%21".into(),
/// key: "hello".into(),
/// value: "%2C+world%21".into(),
/// };
///
/// let (key, value) = item.key_value_decoded();
/// assert_eq!(key, "hello");
/// assert_eq!(value, ", world!");
/// ```
#[inline(always)] #[inline(always)]
pub fn key_value_decoded(&self) -> (String, String) { pub fn key_value_decoded(&self) -> (String, String) {
(self.key.url_decode_lossy(), self.value.url_decode_lossy()) (self.key.url_decode_lossy(), self.value.url_decode_lossy())
} }
/// Extracts `raw` and the raw `key` and `value` as a triple.
///
/// This is equivalent to `(item.raw, item.key, item.value)`.
///
/// # Example
///
/// ```rust
/// use rocket::request::FormItem;
///
/// let item = FormItem {
/// raw: "hello=%2C+world%21".into(),
/// key: "hello".into(),
/// value: "%2C+world%21".into(),
/// };
///
/// let (raw, key, value) = item.explode();
/// assert_eq!(raw, "hello=%2C+world%21");
/// assert_eq!(key, "hello");
/// assert_eq!(value, "%2C+world%21");
/// ```
#[inline(always)] #[inline(always)]
pub fn explode(&self) -> (&'f RawStr, &'f RawStr, &'f RawStr) { pub fn explode(&self) -> (&'f RawStr, &'f RawStr, &'f RawStr) {
(self.raw, self.key, self.value) (self.raw, self.key, self.value)

View File

@ -3,14 +3,23 @@ use std::str::FromStr;
use http::RawStr; use http::RawStr;
/// Trait to create instance of some type from a form value; expected from field /// Trait to parse a typed value from a form value.
/// types in structs deriving [`FromForm`](::request::FromForm). ///
/// This trait is used by Rocket's code generation in two places:
///
/// 1. Fields in structs deriving [`FromForm`](::request::FromForm) are
/// required to implement this trait.
/// 2. Types of dynamic query parameters (`?<param>`) are required to
/// implement this trait.
///
/// # `FromForm` Fields
/// ///
/// When deriving the `FromForm` trait, Rocket uses the `FromFormValue` /// When deriving the `FromForm` trait, Rocket uses the `FromFormValue`
/// implementation of each field's type to validate the form input. To /// implementation of each field's type to validate the form input. To
/// illustrate, consider the following structure: /// illustrate, consider the following structure:
/// ///
/// ```rust,ignore /// ```rust
/// # #[macro_use] extern crate rocket;
/// #[derive(FromForm)] /// #[derive(FromForm)]
/// struct Person { /// struct Person {
/// name: String, /// name: String,
@ -23,22 +32,37 @@ use http::RawStr;
/// for the `age` field. The `Person` structure can only be created from a form /// for the `age` field. The `Person` structure can only be created from a form
/// if both calls return successfully. /// if both calls return successfully.
/// ///
/// ## Catching Validation Errors /// # Dynamic Query Parameters
/// ///
/// Sometimes you want to be informed of validation errors. When this is /// Types of dynamic query parameters are required to implement this trait. The
/// desired, types of `Option<T>` or `Result<T, T::Error>` can be used. These /// `FromFormValue` implementation is used to parse and validate each parameter
/// types implement `FromFormValue` themselves. Their implementations always /// according to its target type:
/// return successfully, so their validation never fails. They can be used to ///
/// determine if the `from_form_value` call failed and to retrieve the error /// ```rust
/// value from the failed call. /// # #![feature(proc_macro_hygiene, decl_macro)]
/// # #[macro_use] extern crate rocket;
/// # type Size = String;
/// #[get("/item?<id>&<size>")]
/// fn item(id: usize, size: Size) { /* ... */ }
/// # fn main() { }
/// ```
///
/// To generate values for `id` and `size`, Rocket calls
/// `usize::from_form_value()` and `Size::from_form_value()`, respectively.
///
/// # Validation Errors
///
/// It is sometimes desired to prevent a validation error from forwarding a
/// request to another route. The `FromFormValue` implementation for `Option<T>`
/// and `Result<T, T::Error>` make this possible. Their implementations always
/// return successfully, effectively "catching" the error.
/// ///
/// For instance, if we wanted to know if a user entered an invalid `age` in the /// For instance, if we wanted to know if a user entered an invalid `age` in the
/// form corresponding to the `Person` structure above, we could use the /// form corresponding to the `Person` structure in the first example, we could
/// following structure: /// use the following structure:
/// ///
/// ```rust /// ```rust
/// # use rocket::http::RawStr; /// # use rocket::http::RawStr;
/// # #[allow(dead_code)]
/// struct Person<'r> { /// struct Person<'r> {
/// name: String, /// name: String,
/// age: Result<u16, &'r RawStr> /// age: Result<u16, &'r RawStr>
@ -124,7 +148,9 @@ use http::RawStr;
/// ///
/// The type can then be used in a `FromForm` struct as follows: /// The type can then be used in a `FromForm` struct as follows:
/// ///
/// ```rust,ignore /// ```rust
/// # #[macro_use] extern crate rocket;
/// # type AdultAge = usize;
/// #[derive(FromForm)] /// #[derive(FromForm)]
/// struct Person { /// struct Person {
/// name: String, /// name: String,

View File

@ -32,7 +32,6 @@ use http::uri::FromUriParam;
/// ///
/// ```rust /// ```rust
/// # #![feature(proc_macro_hygiene, decl_macro)] /// # #![feature(proc_macro_hygiene, decl_macro)]
/// # #![allow(deprecated, unused_attributes)]
/// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket;
/// use rocket::request::LenientForm; /// use rocket::request::LenientForm;
/// ///

View File

@ -54,15 +54,19 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
/// in the route attribute. This is why, for instance, `param` is not a request /// in the route attribute. This is why, for instance, `param` is not a request
/// guard. /// guard.
/// ///
/// ```rust,ignore /// ```rust
/// # #![feature(proc_macro_hygiene, decl_macro)]
/// # #[macro_use] extern crate rocket;
/// # use rocket::http::Method;
/// # type A = Method; type B = Method; type C = Method; type T = ();
/// #[get("/<param>")] /// #[get("/<param>")]
/// fn index(param: isize, a: A, b: B, c: C) -> ... { ... } /// fn index(param: isize, a: A, b: B, c: C) -> T { /* ... */ }
/// # fn main() {}
/// ``` /// ```
/// ///
/// Request guards always fire in left-to-right declaration order. In the /// Request guards always fire in left-to-right declaration order. In the
/// example above, for instance, the order will be `a` followed by `b` followed /// example above, the order is `a` followed by `b` followed by `c`. Failure is
/// by `c`. Failure is short-circuiting; if one guard fails, the remaining are /// short-circuiting; if one guard fails, the remaining are not attempted.
/// not attempted.
/// ///
/// # Outcomes /// # Outcomes
/// ///

View File

@ -13,8 +13,9 @@ mod tests;
pub use self::request::Request; pub use self::request::Request;
pub use self::from_request::{FromRequest, Outcome}; pub use self::from_request::{FromRequest, Outcome};
pub use self::param::{FromParam, FromSegments}; pub use self::param::{FromParam, FromSegments};
pub use self::form::{FromForm, FromFormValue};
pub use self::form::{Form, LenientForm, FormItems, FormItem}; pub use self::form::{Form, LenientForm, FormItems, FormItem};
pub use self::form::{FromForm, FormError, FromFormValue, FormParseError, FormDataError}; pub use self::form::{FormError, FormParseError, FormDataError};
pub use self::state::State; pub use self::state::State;
pub use self::query::{Query, FromQuery}; pub use self::query::{Query, FromQuery};

View File

@ -190,11 +190,11 @@ use http::{RawStr, uri::{Segments, SegmentError}};
/// # fn main() { } /// # fn main() { }
/// ``` /// ```
pub trait FromParam<'a>: Sized { pub trait FromParam<'a>: Sized {
/// The associated error to be returned when parsing fails. /// The associated error to be returned if parsing/validation fails.
type Error: Debug; type Error: Debug;
/// Parses an instance of `Self` from a dynamic path parameter string or /// Parses and validates an instance of `Self` from a path parameter string
/// returns an `Error` if one cannot be parsed. /// or returns an `Error` if parsing or validation fails.
fn from_param(param: &'a RawStr) -> Result<Self, Self::Error>; fn from_param(param: &'a RawStr) -> Result<Self, Self::Error>;
} }

View File

@ -1,23 +1,203 @@
use std::{slice::Iter, iter::Cloned};
use request::{FormItems, FormItem, Form, LenientForm, FromForm}; use request::{FormItems, FormItem, Form, LenientForm, FromForm};
pub struct Query<'q>(pub &'q [FormItem<'q>]); /// Iterator over form items in a query string.
///
/// The `Query` type exists to separate, at the type level, _form_ form items
/// ([`FormItems`]) from _query_ form items (`Query`). A value of type `Query`
/// is passed in to implementations of the [`FromQuery`] trait by Rocket's code
/// generation for every trailing query parameter, `<params..>` below:
///
/// ```rust
/// # #![feature(proc_macro_hygiene, decl_macro)]
/// # #[macro_use] extern crate rocket;
/// #
/// # use rocket::request::Form;
/// # #[derive(FromForm)] struct Q { foo: usize }
/// # type T = Form<Q>;
/// #
/// #[get("/user?<params..>")]
/// fn user(params: T) { /* ... */ }
/// # fn main() { }
/// ```
///
/// # Usage
///
/// A value of type `Query` can only be used as an iterator over values of type
/// [`FormItem`]. As such, its usage is equivalent to that of [`FormItems`], and
/// we refer you to its documentation for further details.
///
/// ## Example
///
/// ```rust
/// use rocket::request::Query;
///
/// # use rocket::request::FromQuery;
/// #
/// # struct MyType;
/// # type Result = ::std::result::Result<MyType, ()>;
/// #
/// # impl<'q> FromQuery<'q> for MyType {
/// # type Error = ();
/// #
/// fn from_query(query: Query) -> Result {
/// for item in query {
/// println!("query key/value: ({}, {})", item.key, item.value);
/// }
///
/// // ...
/// # Ok(MyType)
/// }
/// # }
/// ```
#[derive(Debug, Clone)]
pub struct Query<'q>(#[doc(hidden)] pub &'q [FormItem<'q>]);
impl<'q> IntoIterator for Query<'q> { impl<'q> Iterator for Query<'q> {
type Item = FormItem<'q>; type Item = FormItem<'q>;
type IntoIter = Cloned<Iter<'q, FormItem<'q>>>;
#[inline(always)] #[inline(always)]
fn into_iter(self) -> Self::IntoIter { fn next(&mut self) -> Option<Self::Item> {
self.0.iter().cloned() if self.0.is_empty() {
return None;
}
let next = self.0[0];
self.0 = &self.0[1..];
Some(next)
} }
} }
/// Trait implemented by query guards to derive a value from a query string.
///
/// # Query Guards
///
/// A query guard operates on multiple items of a request's query string. It
/// validates and optionally converts a query string into another value.
/// Validation and parsing/conversion is implemented through `FromQuery`. In
/// other words, every type that implements `FromQuery` is a query guard.
///
/// Query guards are used as the target of trailing query parameters, which
/// syntactically take the form `<param..>` after a `?` in a route's path. For
/// example, the parameter `user` is a trailing query parameter in the following
/// route:
///
/// ```rust
/// # #![feature(proc_macro_hygiene, decl_macro)]
/// # #[macro_use] extern crate rocket;
/// use rocket::request::Form;
///
/// #[derive(FromForm)]
/// struct User {
/// name: String,
/// account: usize,
/// }
///
/// #[get("/item?<id>&<user..>")]
/// fn item(id: usize, user: Form<User>) { /* ... */ }
/// # fn main() { }
/// ```
///
/// The `FromQuery` implementation of `Form<User>` will be passed in a [`Query`]
/// that iterates over all of the query items that don't have the key `id`
/// (because of the `<id>` dynamic query parameter). For posterity, note that
/// the `value` of an `id=value` item in a query string will be parsed as a
/// `usize` and passed in to `item` as `id`.
///
/// # Forwarding
///
/// If the conversion fails, signaled by returning an `Err` from a `FromQuery`
/// implementation, the incoming request will be forwarded to the next matching
/// route, if any. For instance, in the `item` route above, if a query string is
/// missing either a `name` or `account` key/value pair, or there is a query
/// item with a key that is not `id`, `name`, or `account`, the request will be
/// forwarded. Note that this strictness is imposed by the [`Form`] type. As an
/// example, using the [`LenientForm`] type instead would allow extra form items
/// to be ignored without forwarding. Alternatively, _not_ having a trailing
/// parameter at all would result in the same.
///
/// # Provided Implementations
///
/// Rocket implements `FromQuery` for several standard types. Their behavior is
/// documented here.
///
/// * **Form&lt;T>** _where_ **T: FromForm**
///
/// Parses the query as a strict form, where each key is mapped to a field
/// in `T`. See [`Form`] for more information.
///
/// * **LenientForm&lt;T>** _where_ **T: FromForm**
///
/// Parses the query as a lenient form, where each key is mapped to a field
/// in `T`. See [`LenientForm`] for more information.
///
/// * **Option&lt;T>** _where_ **T: FromQuery**
///
/// _This implementation always returns successfully._
///
/// The query is parsed by `T`'s `FromQuery` implementation. If the parse
/// succeeds, a `Some(parsed_value)` is returned. Otherwise, a `None` is
/// returned.
///
/// * **Result&lt;T, T::Error>** _where_ **T: FromQuery**
///
/// _This implementation always returns successfully._
///
/// The path segment is parsed by `T`'s `FromQuery` mplementation. The
/// returned `Result` value is returned.
///
/// # Example
///
/// Explicitly implementing `FromQuery` should be rare. For most use-cases, a
/// query guard of `Form<T>` or `LenientForm<T>`, coupled with deriving
/// `FromForm` (as in the previous example) will suffice. For special cases
/// however, an implementation of `FromQuery` may be warranted.
///
/// Consider a contrived scheme where we expect to recieve one query key, `key`,
/// three times and wish to take the middle value. For instance, consider the
/// query:
///
/// ```text
/// key=first_value&key=second_value&key=third_value
/// ```
///
/// We wish to extract `second_value` from this query into a `Contrived` struct.
/// Because `Form` and `LenientForm` will take the _last_ value (`third_value`
/// here) and don't check that there are exactly three keys named `key`, we
/// cannot make use of them and must implement `FromQuery` manually. Such an
/// implementation might look like:
///
/// ```rust
/// use rocket::http::RawStr;
/// use rocket::request::{Query, FromQuery};
///
/// /// Our custom query guard.
/// struct Contrived<'q>(&'q RawStr);
///
/// impl<'q> FromQuery<'q> for Contrived<'q> {
/// /// The number of `key`s we actually saw.
/// type Error = usize;
///
/// fn from_query(query: Query<'q>) -> Result<Self, Self::Error> {
/// let mut key_items = query.filter(|i| i.key == "key");
///
/// // This is cloning an iterator, which is cheap.
/// let count = key_items.clone().count();
/// if count != 3 {
/// return Err(count);
/// }
///
/// // The `ok_or` gets us a `Result`. We will never see `Err(0)`.
/// key_items.map(|i| Contrived(i.value)).nth(1).ok_or(0)
/// }
/// }
/// ```
pub trait FromQuery<'q>: Sized { pub trait FromQuery<'q>: Sized {
/// The associated error to be returned if parsing/validation fails.
type Error; type Error;
fn from_query(q: Query<'q>) -> Result<Self, Self::Error>; /// Parses and validates an instance of `Self` from a query or returns an
/// `Error` if parsing or validation fails.
fn from_query(query: Query<'q>) -> Result<Self, Self::Error>;
} }
impl<'q, T: FromForm<'q>> FromQuery<'q> for Form<T> { impl<'q, T: FromForm<'q>> FromQuery<'q> for Form<T> {

View File

@ -17,6 +17,7 @@ impl Route {
/// ///
/// Because query parsing is lenient, and dynamic query parameters can be /// Because query parsing is lenient, and dynamic query parameters can be
/// missing, queries do not impact whether two routes collide. /// missing, queries do not impact whether two routes collide.
#[doc(hidden)]
pub fn collides_with(&self, other: &Route) -> bool { pub fn collides_with(&self, other: &Route) -> bool {
self.method == other.method self.method == other.method
&& self.rank == other.rank && self.rank == other.rank
@ -36,6 +37,7 @@ impl Route {
/// * All static components in the route's query string are also in the /// * All static components in the route's query string are also in the
/// request query string, though in any position. /// request query string, though in any position.
/// - If no query in route, requests with/without queries match. /// - If no query in route, requests with/without queries match.
#[doc(hidden)]
pub fn matches(&self, req: &Request) -> bool { pub fn matches(&self, req: &Request) -> bool {
self.method == req.method() self.method == req.method()
&& paths_match(self, req) && paths_match(self, req)

View File

@ -6,9 +6,10 @@ use yansi::Color::*;
use codegen::StaticRouteInfo; use codegen::StaticRouteInfo;
use handler::Handler; use handler::Handler;
use http::{Method, MediaType}; use http::{Method, MediaType};
use http::route::{RouteSegment, Kind, Error as SegmentError}; use http::route::{RouteSegment, Kind};
use error::RouteUriError;
use http::ext::IntoOwned; use http::ext::IntoOwned;
use http::uri::{self, Origin}; use http::uri::Origin;
/// A route: a method, its handler, path, rank, and format/media type. /// A route: a method, its handler, path, rank, and format/media type.
#[derive(Clone)] #[derive(Clone)]
@ -279,41 +280,6 @@ impl Route {
} }
} }
#[derive(Debug)]
pub enum RouteUriError {
Segment,
Uri(uri::Error<'static>),
DynamicBase,
}
impl<'a> From<(&'a str, SegmentError<'a>)> for RouteUriError {
fn from(_: (&'a str, SegmentError<'a>)) -> Self {
RouteUriError::Segment
}
}
impl<'a> From<uri::Error<'a>> for RouteUriError {
fn from(error: uri::Error<'a>) -> Self {
RouteUriError::Uri(error.into_owned())
}
}
impl fmt::Display for RouteUriError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
RouteUriError::Segment => {
write!(f, "The URI contains malformed dynamic route path segments.")
}
RouteUriError::DynamicBase => {
write!(f, "The mount point contains dynamic parameters.")
}
RouteUriError::Uri(error) => {
write!(f, "Malformed URI: {}", error)
}
}
}
}
impl fmt::Display for Route { impl fmt::Display for Route {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.uri))?; write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.uri))?;