mirror of https://github.com/rwf2/Rocket.git
Fully document all public items in core.
This commit is contained in:
parent
f5219174d0
commit
e34b2f3703
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>>;
|
||||
|
|
|
@ -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
|
||||
///
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
///
|
||||
|
|
|
@ -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
|
||||
///
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<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<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<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<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> {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))?;
|
||||
|
|
Loading…
Reference in New Issue