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 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>;
/// Information generated by the `route` attribute during codegen.
pub struct StaticRouteInfo {
/// The route's name, i.e, the name of the function.
pub name: &'static str,
/// The route's method.
pub method: Method,
/// The route's path, without the base mount point.
pub path: &'static str,
/// The route's format, if any.
pub format: Option<MediaType>,
/// The route's handler, i.e, the annotated function.
pub handler: StaticHandler,
/// The route's rank, if any.
pub rank: Option<isize>,
}
/// Information generated by the `catch` attribute during codegen.
pub struct StaticCatchInfo {
/// The catcher's status code.
pub code: u16,
/// The catcher's handler, i.e, the annotated function.
pub handler: ErrorHandler,
}

View File

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

View File

@ -305,7 +305,7 @@ impl Config {
/// * **address**: String
/// * **port**: Integer (16-bit unsigned)
/// * **workers**: Integer (16-bit unsigned)
/// * **keep_alive**: Integer or Boolean (false) or String ('none')
/// * **keep_alive**: Integer
/// * **log**: String
/// * **secret_key**: String (256-bit base64)
/// * **tls**: Table (`certs` (path as String), `key` (path as String))
@ -315,7 +315,7 @@ impl Config {
address => (str, set_address, id),
port => (u16, set_port, 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),
secret_key => (str, set_secret_key, id),
tls => (tls_config, set_raw_tls, id),
@ -422,7 +422,7 @@ impl Config {
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.
///
/// # Example
@ -438,13 +438,17 @@ impl Config {
/// config.set_keep_alive(10);
///
/// // Disable keep-alive.
/// config.set_keep_alive(None);
/// config.set_keep_alive(0);
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn set_keep_alive<T: Into<Option<u32>>>(&mut self, timeout: T) {
self.keep_alive = timeout.into();
pub fn set_keep_alive(&mut self, timeout: u32) {
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

View File

@ -3,7 +3,6 @@ use std::fmt;
#[cfg(feature = "tls")] use http::tls::{Certificate, PrivateKey};
use config::{Result, Config, Value, ConfigError, LoggingLevel};
use http::uncased::uncased_eq;
use http::Key;
#[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,
name: &str,
value: &Value
@ -260,21 +266,3 @@ pub fn limits(conf: &Config, name: &str, value: &Value) -> Result<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
//! standard configuration parameters are:
//!
//! * **address**: _string_ an IP address or host the application will
//! listen on
//! * examples: `"localhost"`, `"0.0.0.0"`, `"1.2.3.4"`
//! * **port**: _integer_ a port number to listen on
//! * examples: `"8000"`, `"80"`, `"4242"`
//! * **workers**: _integer_ the number of concurrent workers to use
//! * examples: `12`, `1`, `4`
//! * **keep_alive**: _integer, 'false', or 'none'_ timeout, in seconds, for
//! HTTP keep-alive. disabled on 'false' or 'none'
//! * examples: `5`, `60`, `false`, `"none"`
//! * **log**: _string_ how much information to log; one of `"off"`,
//! `"normal"`, `"debug"`, or `"critical"`
//! * **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)
//! | name | type | description | examples |
//! |------------|----------------|-------------------------------------------------------------|----------------------------|
//! | address | string | ip address or host to listen on | `"localhost"`, `"1.2.3.4"` |
//! | port | integer | port number to listen on | `8000`, `80` |
//! | keep_alive | integer | keep-alive timeout in seconds | `0` (disable), `10` |
//! | workers | integer | number of concurrent thread workers | `36`, `512` |
//! | log | string | max log level: `"off"`, `"normal"`, `"debug"`, `"critical"` | `"off"`, `"normal"` |
//! | secret_key | 256-bit base64 | secret key for private cookies | `"8Xui8SI..."` (44 chars) |
//! | tls | table | tls config table with two keys (`certs`, `key`) | _see below_ |
//! | tls.certs | string | path to certificate chain in PEM format | `"private/cert.pem"` |
//! | tls.key | string | path to private key for `tls.certs` in PEM format | `"private/key.pem"` |
//! | limits | table | map from data type (string) to data limit (integer: bytes) | `{ forms = 65536 }` |
//!
//! ### Rocket.toml
//!
//! The `Rocket.toml` file is used to specify the configuration parameters for
//! each environment. The file is optional. If it is not present, the default
//! configuration parameters are used.
//! `Rocket.toml` is a Rocket application's configuration file. It can
//! optionally be used to specify the configuration parameters for each
//! 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,
//! and an optional "global" table, where each table contains key-value pairs
@ -603,7 +591,7 @@ mod test {
port = 7810
workers = 21
log = "critical"
keep_alive = false
keep_alive = 0
secret_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="
template_dir = "mine"
json = true
@ -615,7 +603,7 @@ mod test {
.port(7810)
.workers(21)
.log_level(LoggingLevel::Critical)
.keep_alive(None)
.keep_alive(0)
.secret_key("8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=")
.extra("template_dir", "mine")
.extra("json", true)
@ -919,23 +907,9 @@ mod test {
check_config!(RocketConfig::parse(r#"
[stage]
keep_alive = false
keep_alive = 0
"#.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)
});
check_config!(RocketConfig::parse(r#"
[stage]
keep_alive = "None"
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(None)
default_config(Staging).keep_alive(0)
});
}

View File

@ -18,10 +18,15 @@ use router::Route;
/// other kinds of launch errors.
#[derive(Debug)]
pub enum LaunchErrorKind {
/// Binding to the provided address/port failed.
Bind(hyper::Error),
/// An I/O error occurred during launch.
Io(io::Error),
/// Route collisions were detected.
Collision(Vec<(Route, Route)>),
/// A launch fairing reported an error.
FailedFairings(Vec<&'static str>),
/// An otherwise uncategorized error occurred during launch.
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
/// `Handler` automatically implement `Cloneable`.
pub trait Cloneable {
/// Clones `self`.
fn clone_handler(&self) -> Box<Handler>;
}

View File

@ -26,9 +26,49 @@ pub enum FormParseError<'f> {
/// [`Form`](::request::Form) and [`LenientForm`](::request::LenientForm).
#[derive(Debug)]
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),
/// The form string (in `.0`) is malformed and was unable to be parsed as
/// HTTP `application/x-www-form-urlencoded` data.
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)
}
/// 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>>;

View File

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

View File

@ -104,10 +104,12 @@ use http::RawStr;
/// ```
#[derive(Debug)]
pub enum FormItems<'f> {
#[doc(hidden)]
Raw {
string: &'f RawStr,
next_index: usize
},
#[doc(hidden)]
Cooked {
items: &'f [FormItem<'f>],
next_index: usize
@ -117,7 +119,7 @@ pub enum FormItems<'f> {
/// A form items returned by the [`FormItems`] iterator.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
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,
/// 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> {
/// 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)]
pub fn key_value(&self) -> (&'f RawStr, &'f RawStr) {
(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)]
pub fn key_value_decoded(&self) -> (String, String) {
(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)]
pub fn explode(&self) -> (&'f RawStr, &'f RawStr, &'f RawStr) {
(self.raw, self.key, self.value)

View File

@ -3,14 +3,23 @@ use std::str::FromStr;
use http::RawStr;
/// Trait to create instance of some type from a form value; expected from field
/// types in structs deriving [`FromForm`](::request::FromForm).
/// Trait to parse a typed value from a form value.
///
/// 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`
/// implementation of each field's type to validate the form input. To
/// illustrate, consider the following structure:
///
/// ```rust,ignore
/// ```rust
/// # #[macro_use] extern crate rocket;
/// #[derive(FromForm)]
/// struct Person {
/// name: String,
@ -23,22 +32,37 @@ use http::RawStr;
/// for the `age` field. The `Person` structure can only be created from a form
/// if both calls return successfully.
///
/// ## Catching Validation Errors
/// # Dynamic Query Parameters
///
/// Sometimes you want to be informed of validation errors. When this is
/// desired, types of `Option<T>` or `Result<T, T::Error>` can be used. These
/// types implement `FromFormValue` themselves. Their implementations always
/// 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
/// value from the failed call.
/// Types of dynamic query parameters are required to implement this trait. The
/// `FromFormValue` implementation is used to parse and validate each parameter
/// according to its target type:
///
/// ```rust
/// # #![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
/// form corresponding to the `Person` structure above, we could use the
/// following structure:
/// form corresponding to the `Person` structure in the first example, we could
/// use the following structure:
///
/// ```rust
/// # use rocket::http::RawStr;
/// # #[allow(dead_code)]
/// struct Person<'r> {
/// name: String,
/// age: Result<u16, &'r RawStr>
@ -124,7 +148,9 @@ use http::RawStr;
///
/// 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)]
/// struct Person {
/// name: String,

View File

@ -32,7 +32,6 @@ use http::uri::FromUriParam;
///
/// ```rust
/// # #![feature(proc_macro_hygiene, decl_macro)]
/// # #![allow(deprecated, unused_attributes)]
/// # #[macro_use] extern crate rocket;
/// 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
/// 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>")]
/// 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
/// example above, for instance, the order will be `a` followed by `b` followed
/// by `c`. Failure is short-circuiting; if one guard fails, the remaining are
/// not attempted.
/// example above, the order is `a` followed by `b` followed by `c`. Failure is
/// short-circuiting; if one guard fails, the remaining are not attempted.
///
/// # Outcomes
///

View File

@ -13,8 +13,9 @@ mod tests;
pub use self::request::Request;
pub use self::from_request::{FromRequest, Outcome};
pub use self::param::{FromParam, FromSegments};
pub use self::form::{FromForm, FromFormValue};
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::query::{Query, FromQuery};

View File

@ -190,11 +190,11 @@ use http::{RawStr, uri::{Segments, SegmentError}};
/// # fn main() { }
/// ```
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;
/// Parses an instance of `Self` from a dynamic path parameter string or
/// returns an `Error` if one cannot be parsed.
/// Parses and validates an instance of `Self` from a path parameter string
/// or returns an `Error` if parsing or validation fails.
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};
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 IntoIter = Cloned<Iter<'q, FormItem<'q>>>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.0.iter().cloned()
fn next(&mut self) -> Option<Self::Item> {
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 {
/// The associated error to be returned if parsing/validation fails.
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> {

View File

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

View File

@ -6,9 +6,10 @@ use yansi::Color::*;
use codegen::StaticRouteInfo;
use handler::Handler;
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::uri::{self, Origin};
use http::uri::Origin;
/// A route: a method, its handler, path, rank, and format/media type.
#[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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", Green.paint(&self.method), Blue.paint(&self.uri))?;