diff --git a/core/lib/src/codegen.rs b/core/lib/src/codegen.rs index cf84330a..a8d98f85 100644 --- a/core/lib/src/codegen.rs +++ b/core/lib/src/codegen.rs @@ -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, + /// The route's handler, i.e, the annotated function. pub handler: StaticHandler, + /// The route's rank, if any. pub rank: Option, } +/// 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, } diff --git a/core/lib/src/config/builder.rs b/core/lib/src/config/builder.rs index d9acc4d9..b07c4808 100644 --- a/core/lib/src/config/builder.rs +++ b/core/lib/src/config/builder.rs @@ -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, + 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>>(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()); diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs index fbcbf051..ecd11fea 100644 --- a/core/lib/src/config/config.rs +++ b/core/lib/src/config/config.rs @@ -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>>(&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 diff --git a/core/lib/src/config/custom_values.rs b/core/lib/src/config/custom_values.rs index 067228a0..cb9c7702 100644 --- a/core/lib/src/config/custom_values.rs +++ b/core/lib/src/config/custom_values.rs @@ -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 { } } +pub fn u32(conf: &Config, name: &str, value: &Value) -> Result { + 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 { Ok(limits) } - -pub fn u32_option(conf: &Config, name: &str, value: &Value) -> Result> { - 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 - } - } - } -} diff --git a/core/lib/src/config/mod.rs b/core/lib/src/config/mod.rs index 4532949a..9d1547fe 100644 --- a/core/lib/src/config/mod.rs +++ b/core/lib/src/config/mod.rs @@ -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) }); } diff --git a/core/lib/src/error.rs b/core/lib/src/error.rs index 5cc9781f..cea9c967 100644 --- a/core/lib/src/error.rs +++ b/core/lib/src/error.rs @@ -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> 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) + } + } + } +} diff --git a/core/lib/src/handler.rs b/core/lib/src/handler.rs index 2dae01c4..33b93004 100644 --- a/core/lib/src/handler.rs +++ b/core/lib/src/handler.rs @@ -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; } diff --git a/core/lib/src/request/form/error.rs b/core/lib/src/request/form/error.rs index 845f9b67..b8f0bf18 100644 --- a/core/lib/src/request/form/error.rs +++ b/core/lib/src/request/form/error.rs @@ -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`] where the [`FromForm`] implementation for `T` +/// was derived. +/// +/// This alias is particularly useful when "catching" form errors in routes. +/// +/// [`FromData`]: ::data::FromData +/// [`Form`]: ::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 = "")] +/// fn submit(sink: Result, 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>>; diff --git a/core/lib/src/request/form/form.rs b/core/lib/src/request/form/form.rs index 611a119e..d78d0feb 100644 --- a/core/lib/src/request/form/form.rs +++ b/core/lib/src/request/form/form.rs @@ -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 = ""` 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 = "")] -/// fn submit(form: Form) ... { ... } -/// ``` -/// -/// A type of `Form` automatically dereferences into an `&T`, though you can -/// also tranform a `Form` into a `T` by calling -/// [`into_inner()`](Form::into_inner()). Thanks automatic dereferencing, you -/// can access fields of `T` transparently through a `Form`: +/// the `data = ""` 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` automatically dereferences into an `&T`, though you can +/// also transform a `Form` into a `T` by calling +/// [`into_inner()`](Form::into_inner()). Thanks to automatic dereferencing, you +/// can access fields of `T` transparently through a `Form`, 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 /// diff --git a/core/lib/src/request/form/form_items.rs b/core/lib/src/request/form/form_items.rs index 21e19f61..46f9dd31 100644 --- a/core/lib/src/request/form/form_items.rs +++ b/core/lib/src/request/form/form_items.rs @@ -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) diff --git a/core/lib/src/request/form/from_form_value.rs b/core/lib/src/request/form/from_form_value.rs index 6c0b42f2..e8af153b 100644 --- a/core/lib/src/request/form/from_form_value.rs +++ b/core/lib/src/request/form/from_form_value.rs @@ -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 (`?`) 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` or `Result` 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?&")] +/// 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` +/// and `Result` 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 @@ -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, diff --git a/core/lib/src/request/form/lenient.rs b/core/lib/src/request/form/lenient.rs index 740746d5..bac3b05b 100644 --- a/core/lib/src/request/form/lenient.rs +++ b/core/lib/src/request/form/lenient.rs @@ -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; /// diff --git a/core/lib/src/request/from_request.rs b/core/lib/src/request/from_request.rs index 2bf1e8f3..548ac74f 100644 --- a/core/lib/src/request/from_request.rs +++ b/core/lib/src/request/from_request.rs @@ -54,15 +54,19 @@ impl IntoOutcome for Result { /// 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("/")] -/// 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 /// diff --git a/core/lib/src/request/mod.rs b/core/lib/src/request/mod.rs index 98211ec2..09be821a 100644 --- a/core/lib/src/request/mod.rs +++ b/core/lib/src/request/mod.rs @@ -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}; diff --git a/core/lib/src/request/param.rs b/core/lib/src/request/param.rs index 07453939..394e3c7f 100644 --- a/core/lib/src/request/param.rs +++ b/core/lib/src/request/param.rs @@ -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; } diff --git a/core/lib/src/request/query.rs b/core/lib/src/request/query.rs index a29de7cd..3ca0a076 100644 --- a/core/lib/src/request/query.rs +++ b/core/lib/src/request/query.rs @@ -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, `` 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; +/// # +/// #[get("/user?")] +/// 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; +/// # +/// # 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>>; #[inline(always)] - fn into_iter(self) -> Self::IntoIter { - self.0.iter().cloned() + fn next(&mut self) -> Option { + 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 `` 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?&")] +/// fn item(id: usize, user: Form) { /* ... */ } +/// # fn main() { } +/// ``` +/// +/// The `FromQuery` implementation of `Form` will be passed in a [`Query`] +/// that iterates over all of the query items that don't have the key `id` +/// (because of the `` 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` or `LenientForm`, 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 { +/// 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; + /// 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; } impl<'q, T: FromForm<'q>> FromQuery<'q> for Form { diff --git a/core/lib/src/router/collider.rs b/core/lib/src/router/collider.rs index 7fd4dccf..340e0fd6 100644 --- a/core/lib/src/router/collider.rs +++ b/core/lib/src/router/collider.rs @@ -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) diff --git a/core/lib/src/router/route.rs b/core/lib/src/router/route.rs index 5a8f7773..c8e5ba44 100644 --- a/core/lib/src/router/route.rs +++ b/core/lib/src/router/route.rs @@ -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> 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))?;