From 2da43e24f741d26ede81a89570bda1d50e4aac24 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Fri, 21 Oct 2016 02:56:57 -0700 Subject: [PATCH] Document most of the request module. --- lib/src/lib.rs | 2 +- lib/src/request/data/data.rs | 50 ++++++++++++ lib/src/request/data/from_data.rs | 40 +++++++++ lib/src/request/form/from_form.rs | 28 +++++-- lib/src/request/form/mod.rs | 131 +++++++++++++++++++++++++++++- lib/src/request/from_request.rs | 1 + lib/src/request/mod.rs | 16 +++- lib/src/request/param.rs | 59 ++++++++++++++ lib/src/request/request.rs | 27 +++--- lib/src/rocket.rs | 2 + 10 files changed, 334 insertions(+), 22 deletions(-) diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 088739fa..fedc1bbc 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -52,7 +52,7 @@ //! extern crate rocket; //! ``` //! -//! See the [guide](https://guide.rocket.rs) for more information on how to +//! See the [guide](https://rocket.rs/guide) for more information on how to //! write Rocket applications. Here's a simple example to get you started: //! //! ```rust diff --git a/lib/src/request/data/data.rs b/lib/src/request/data/data.rs index 896392db..a9c83b53 100644 --- a/lib/src/request/data/data.rs +++ b/lib/src/request/data/data.rs @@ -9,6 +9,34 @@ use http::hyper::{HyperBodyReader, HyperHttpStream}; use http::hyper::HyperNetworkStream; use http::hyper::HyperHttpReader::*; +/// Type representing the data in the body of an incoming request. +/// +/// This type is the only means by which the body of a request can be retrieved. +/// This type is not usually used directly. Instead, types that implement +/// [FromData](trait.FromData.html) are used via code generation by specifying +/// the `data = ""` route parameter as follows: +/// +/// ```rust,ignore +/// #[post("/submit", data = "")] +/// fn submit(var: T) -> ... { ... } +/// ``` +/// +/// Above, `T` can be any type that implements `FromData`. Note that `Data` +/// itself implements `FromData`. +/// +/// # Reading Data +/// +/// Data may be read from a `Data` object by calling either the +/// [open](#method.open) or [peek](#method.peek) methods. +/// +/// The `open` method consumes the `Data` object and returns the raw data +/// stream. The `Data` object is consumed for safety reasons: consuming the +/// object ensures that holding a `Data` object means that all of the data is +/// available for reading. +/// +/// The `peek` method returns a slice containing at most 4096 bytes of buffered +/// body data. This enables partially or fully reading from a `Data` object +/// without consuming the `Data` object. pub struct Data { buffer: Vec, is_done: bool, @@ -20,6 +48,12 @@ pub struct Data { } impl Data { + /// Returns the raw data stream. + /// + /// The stream contains all of the data in the body of the request, + /// including that in the `peek` buffer. The method consumes the `Data` + /// instance. This ensures that a `Data` type _always_ represents _all_ of + /// the data in a request. pub fn open(mut self) -> impl BufRead { // Swap out the buffer and stream for empty ones so we can move. let mut buffer = vec![]; @@ -68,21 +102,37 @@ impl Data { Ok(Data::new(vec, pos, cap, stream)) } + /// Retrieve the `peek` buffer. + /// + /// The peek buffer contains at most 4096 bytes of the body of the request. + /// The actual size of the returned buffer varies by web request. The + /// [peek_complete](#method.peek_complete) can be used to determine if this + /// buffer contains _all_ of the data in the body of the request. #[inline(always)] pub fn peek(&self) -> &[u8] { &self.buffer[self.position..self.capacity] } + /// Returns true if the `peek` buffer contains all of the data in the body + /// of the request. #[inline(always)] pub fn peek_complete(&self) -> bool { self.is_done } + /// A helper method to write the body of the request to any `Write` type. + /// + /// This method is identical to `io::copy(&mut data.open(), writer)`. #[inline(always)] pub fn stream_to(self, writer: &mut W) -> io::Result { io::copy(&mut self.open(), writer) } + /// A helper method to write the body of the request to a file at the path + /// determined by `path`. + /// + /// This method is identical to + /// `io::copy(&mut self.open(), &mut File::create(path)?)`. #[inline(always)] pub fn stream_to_file>(self, path: P) -> io::Result { io::copy(&mut self.open(), &mut File::create(path)?) diff --git a/lib/src/request/data/from_data.rs b/lib/src/request/data/from_data.rs index c1552e33..a7d2eb01 100644 --- a/lib/src/request/data/from_data.rs +++ b/lib/src/request/data/from_data.rs @@ -2,6 +2,7 @@ use outcome::Outcome; use http::StatusCode; use request::{Request, Data}; +/// Type alias for the `Outcome` of a `FromData` conversion. pub type DataOutcome = Outcome; impl DataOutcome { @@ -30,12 +31,51 @@ impl DataOutcome { } /// Trait used to derive an object from incoming request data. +/// +/// Type that implement this trait can be used as target for the `data = +/// ""` route parmater, as illustrated below: +/// +/// ```rust,ignore +/// #[post("/submit", data = "")] +/// fn submit(var: T) -> ... { ... } +/// ``` +/// +/// In this example, `T` can be any type that implements `FromData.` +/// +/// # Outcomes +/// +/// The returned [Outcome](/rocket/outcome/index.html) of a `from_data` call +/// determines what will happen with the incoming request. +/// +/// * **Success** +/// +/// If the `Outcome` is `Success`, then the `Success` value will be used as +/// the value for the data parameter. +/// +/// * **Failure** +/// +/// If the `Outcome` is `Failure`, the request will fail with the given status +/// code. Note that users can request types of `Result` and `Option` +/// to catch `Failure`s. +/// +/// * **Failure** +/// +/// If the `Outcome` is `Forward`, the request will be forwarded to the next +/// matching request. This requires that no data has been read from the `Data` +/// parameter. Note that users can request an `Option` to catch `Forward`s. pub trait FromData: Sized { + /// The associated error to be returned when parsing fails. type Error; + /// Parses an instance of `Self` from the incoming request body data. + /// + /// If the parse is successful, an outcome of `Success` is returned. If the + /// data does not correspond to the type of `Self`, `Forward` is returned. + /// If parsing fails, `Failure` is returned. fn from_data(request: &Request, data: Data) -> DataOutcome; } +/// The identity implementation of `FromData`. Always returns `Success`. impl FromData for Data { type Error = (); fn from_data(_: &Request, data: Data) -> DataOutcome { diff --git a/lib/src/request/form/from_form.rs b/lib/src/request/form/from_form.rs index 849c2eb7..08848aa5 100644 --- a/lib/src/request/form/from_form.rs +++ b/lib/src/request/form/from_form.rs @@ -1,12 +1,13 @@ use error::Error; -/// Trait to create instance of some type from an HTTP form; used by code -/// generation for `form` route parameters. +/// Trait to create an instance of some type from an HTTP form. The +/// [Form](struct.Form.html) type requires that its generic parameter implements +/// this trait. /// /// This trait can be automatically derived via the /// [rocket_codegen](/rocket_codegen) plugin: /// -/// ```rust,ignore +/// ```rust /// #![feature(plugin, custom_derive)] /// #![plugin(rocket_codegen)] /// @@ -19,15 +20,32 @@ use error::Error; /// } /// ``` /// +/// The type can then be parsed from incoming form data via the `data` +/// parameter and `Form` type. +/// +/// ```rust +/// # #![feature(plugin, custom_derive)] +/// # #![plugin(rocket_codegen)] +/// # extern crate rocket; +/// # use rocket::request::Form; +/// # #[derive(FromForm)] +/// # struct TodoTask { description: String, completed: bool } +/// #[post("/submit", data = "")] +/// fn submit_task(task: Form) -> String { +/// format!("New task: {}", task.get().description) +/// } +/// # fn main() { } +/// ``` +/// /// When deriving `FromForm`, every field in the structure must implement /// [FromFormValue](trait.FromFormValue.html). If you implement `FormForm` /// yourself, use the [FormItems](struct.FormItems.html) iterator to iterate /// through the form key/value pairs. pub trait FromForm<'f>: Sized { - /// The associated error which can be returned from parsing. + /// The associated error to be returned when parsing fails. type Error; - /// Parses an instance of `Self` from a raw HTTP form + /// Parses an instance of `Self` from a raw HTTP form string /// (`application/x-www-form-urlencoded data`) or returns an `Error` if one /// cannot be parsed. fn from_form_string(form_string: &'f str) -> Result; diff --git a/lib/src/request/form/mod.rs b/lib/src/request/form/mod.rs index 9cacd6fc..ee906a9a 100644 --- a/lib/src/request/form/mod.rs +++ b/lib/src/request/form/mod.rs @@ -30,7 +30,122 @@ use std::io::Read; use http::StatusCode; use request::{Request, FromData, Data, DataOutcome}; -// This works, and it's safe, but it sucks to have the lifetime appear twice. +// TODO: This works and is safe, but the lifetime appears twice. +/// A `FromData` type for parsing `FromForm` types. +/// +/// This type implements the `FromData` trait. It provides a generic means to +/// parse arbitrary structure from incoming form data. +/// +/// # Usage +/// +/// This type can be used with any type that implements the `FromForm` trait. +/// The trait can be automatically derived; see the +/// [FromForm](trait.FromForm.html) documentation for more information about +/// implementing the trait. +/// +/// Because `Form` implement `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) ... { ... } +/// ``` +/// +/// To preserve memory safety, if the underlying structure type contains +/// references into form data, the type can only be borrowed via the +/// [get](#method.get) or [get_mut](#method.get_mut) methods. Otherwise, the +/// parsed structure can be retrieved with the [into_inner](#method.into_inner) +/// method. +/// +/// ## With References +/// +/// The simplest data structure with a reference into form data looks like this: +/// +/// ```rust +/// # #![feature(plugin, custom_derive)] +/// # #![plugin(rocket_codegen)] +/// # extern crate rocket; +/// #[derive(FromForm)] +/// struct UserInput<'f> { +/// value: &'f str +/// } +/// ``` +/// +/// This corresponds to a form with a single field named `value` that should be +/// a string. A handler for this type can be written as: +/// +/// ```rust +/// # #![feature(plugin, custom_derive)] +/// # #![plugin(rocket_codegen)] +/// # extern crate rocket; +/// # use rocket::request::Form; +/// # #[derive(FromForm)] +/// # struct UserInput<'f> { +/// # value: &'f str +/// # } +/// #[post("/submit", data = "")] +/// fn submit_task<'r>(user_input: Form<'r, UserInput<'r>>) -> String { +/// format!("Your value: {}", user_input.get().value) +/// } +/// # fn main() { } +/// ``` +/// +/// Note that the ``r` lifetime is used _twice_ in the handler's signature: this +/// is necessary to tie the lifetime of the structure to the lifetime of the +/// request data. +/// +/// ## Without References +/// +/// The owned analog of the `UserInput` type above is: +/// +/// ```rust +/// # #![feature(plugin, custom_derive)] +/// # #![plugin(rocket_codegen)] +/// # extern crate rocket; +/// #[derive(FromForm)] +/// struct OwnedUserInput { +/// value: String +/// } +/// ``` +/// +/// The handler is written similarly: +/// +/// ```rust +/// # #![feature(plugin, custom_derive)] +/// # #![plugin(rocket_codegen)] +/// # extern crate rocket; +/// # use rocket::request::Form; +/// # #[derive(FromForm)] +/// # struct OwnedUserInput { +/// # value: String +/// # } +/// #[post("/submit", data = "")] +/// fn submit_task(user_input: Form) -> String { +/// let input: OwnedUserInput = user_input.into_inner(); +/// format!("Your value: {}", input.value) +/// } +/// # 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. +/// +/// ## Performance and Correctness Considerations +/// +/// Whether you should use a `str` 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 commmon +/// characters such as spaces. If so, then you must use `String`, whose +/// `FromFormValue` implementation deserializes the URL encoded string for you. +/// Because the `str` 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 `str` +/// will result in fewer allocation and is thus spreferred. pub struct Form<'f, T: FromForm<'f> + 'f> { object: T, form_string: String, @@ -38,14 +153,18 @@ pub struct Form<'f, T: FromForm<'f> + 'f> { } impl<'f, T: FromForm<'f> + 'f> Form<'f, T> { + /// Immutably borrow the parsed type. pub fn get(&'f self) -> &'f T { &self.object } + /// Mutably borrow the parsed type. pub fn get_mut(&'f mut self) -> &'f mut T { &mut self.object } + /// Returns the raw form string that was used to parse the encapsulated + /// object. pub fn raw_form_string(&self) -> &str { &self.form_string } @@ -87,6 +206,7 @@ impl<'f, T: FromForm<'f> + 'f> Form<'f, T> { } impl<'f, T: FromForm<'f> + 'static> Form<'f, T> { + /// Consume this object and move out the parsed object. pub fn into_inner(self) -> T { self.object } @@ -98,6 +218,15 @@ impl<'f, T: FromForm<'f> + Debug + 'f> Debug for Form<'f, T> { } } +/// Parses a `Form` from incoming form data. +/// +/// If the content type of the request data is not +/// `application/x-www-form-urlencoded`, `Forward`s the request. If the form +/// data cannot be parsed into a `T` or reading the incoming stream failed, +/// returns a `Failure` with the raw form string (if avaialble). +/// +/// All relevant warnings and errors are written to the console in Rocket +/// logging format. impl<'f, T: FromForm<'f>> FromData for Form<'f, T> where T::Error: Debug { type Error = Option; diff --git a/lib/src/request/from_request.rs b/lib/src/request/from_request.rs index 6cd3b2cf..0cb4393f 100644 --- a/lib/src/request/from_request.rs +++ b/lib/src/request/from_request.rs @@ -4,6 +4,7 @@ use request::Request; use outcome::Outcome; use http::{StatusCode, ContentType, Method, Cookies}; +/// Type alias for the `Outcome` of a `FromRequest` conversion. pub type RequestOutcome = Outcome; impl RequestOutcome { diff --git a/lib/src/request/mod.rs b/lib/src/request/mod.rs index d69c565e..b7d8eee3 100644 --- a/lib/src/request/mod.rs +++ b/lib/src/request/mod.rs @@ -1,4 +1,18 @@ -//! Types and traits that deal with request parsing and handling. +//! Types and traits for request parsing and handling. +//! +//! # Request and Data +//! +//! The [Request](struct.Request.html) and [Data](struct.Data.html) types +//! contain all of the available information for an incoming request. The +//! `Request` types contains all information _except_ the body, which is +//! contained in the `Data` type. +//! +//! # Code Generation Conversion Traits +//! +//! This module contains the core code generation data conversion traits. These +//! traits are used by Rocket's code generation facilities to automatically +//! derive values from incoming data based on the signature of a request +//! handler. mod request; mod param; diff --git a/lib/src/request/param.rs b/lib/src/request/param.rs index 43802633..6dae4b8c 100644 --- a/lib/src/request/param.rs +++ b/lib/src/request/param.rs @@ -5,8 +5,54 @@ use url; use http::uri::Segments; +/// Trait to convert a dynamic path segment string to a concrete value. +/// +/// This trait is used by Rocket's code generation facilities to parse dynamic +/// path segment string values into a given type. That is, when a path contains +/// a dynamic segment `` where `param` has some type `T` that +/// implements `FromParam`, `T::from_param` will be called. +/// +/// # Forwarding +/// +/// If the conversion fails, the incoming request will be forwarded to the next +/// matching route, if any. For instance, consider the following route and +/// handler for the dynamic `"/"` path: +/// +/// ```rust +/// #![feature(plugin)] +/// #![plugin(rocket_codegen)] +/// +/// extern crate rocket; +/// +/// #[get("/")] +/// fn hello(id: usize) -> String { +/// # /* +/// ... +/// # */ +/// # "".to_string() +/// } +/// # fn main() { } +/// ``` +/// +/// If `usize::from_param` returns an `Ok(usize)` variant, the encapsulated +/// value is used as the `id` function parameter. If not, the request is +/// forwarded to the next matching route. Since there are no additional matching +/// routes, this example will result in a 404 error for requests with invalid +/// `id` values. +/// +/// # `str` vs. `String` +/// +/// Paths are URL encoded. As a result, the `str` `FromParam` implementation +/// returns the raw, URL encoded version of the path segment string. On the +/// other hand, `String` decodes the path parameter, but requires an allocation +/// to do so. This tradeoff is similiar to that of form values, and you should +/// use whichever makes sense for your application. pub trait FromParam<'a>: Sized { + /// The associated error to be returned when parsing fails. type Error; + + /// Parses an instance of `Self` from a dynamic path parameter string or + /// returns an `Error` if one cannot be parsed. fn from_param(param: &'a str) -> Result; } @@ -40,8 +86,21 @@ impl_with_fromstr!(f32, f64, isize, i8, i16, i32, i64, usize, u8, u16, u32, u64, bool, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr); +/// Trait to convert _many_ dynamic path segment strings to a concrete value. +/// +/// This is the `..` analog to [FromParam](trait.FromParam.html), and its +/// functionality is identical to it with one exception: this trait applies to +/// segment parameters of the form ``, where `param` is of some type +/// `T` that implements `FromSegments`. `T::from_segments` is called to convert +/// the matched segments (via the +/// [Segments](/rocket/http/uri/struct.Segments.html) iterator) into the +/// implementing type. pub trait FromSegments<'a>: Sized { + /// The associated error to be returned when parsing fails. type Error; + + /// Parses an instance of `Self` from many dynamic path parameter strings or + /// returns an `Error` if one cannot be parsed. fn from_segments(segments: Segments<'a>) -> Result; } diff --git a/lib/src/request/request.rs b/lib/src/request/request.rs index 0d3c08e7..1b6e6925 100644 --- a/lib/src/request/request.rs +++ b/lib/src/request/request.rs @@ -12,29 +12,20 @@ use http::uri::{URI, URIBuf}; use http::hyper::{header, HyperCookie, HyperHeaders, HyperMethod, HyperRequestUri}; use http::{Method, ContentType, Cookies}; -/// The type for all incoming web requests. +/// The type of an incoming web request. /// /// This should be used sparingly in Rocket applications. In particular, it /// should likely only be used when writing /// [FromRequest](trait.FromRequest.html) implementations. It contains all of -/// the information for a given web request. This includes the HTTP method, URI, -/// cookies, headers, and more. +/// the information for a given web request except for the body data. This +/// includes the HTTP method, URI, cookies, headers, and more. pub struct Request { /// The HTTP method associated with the request. pub method: Method, - ///
- /// - /// Unstable - /// (#17): - /// The underlying HTTP library/types are likely to change before v1.0. - /// - ///
- /// - /// The data in the request. - uri: URIBuf, // FIXME: Should be URI (without Hyper). + uri: URIBuf, // FIXME: Should be URI (without hyper). params: RefCell>, cookies: Cookies, - headers: HyperHeaders, // This sucks. + headers: HyperHeaders, // Don't use hyper's headers. } impl Request { @@ -140,6 +131,14 @@ impl Request { hyp_ct.map_or(ContentType::any(), |ct| ContentType::from(&ct.0)) } + ///
+ /// + /// Unstable + /// (#17): + /// The underlying HTTP library/types are likely to change before v1.0. + /// + ///
+ /// /// Returns the first content-type accepted by this request. pub fn accepts(&self) -> ContentType { let accept = self.headers().get::(); diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index 02e92811..cdcb8283 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -19,6 +19,8 @@ use http::{Method, StatusCode}; use http::hyper::{HyperRequest, FreshHyperResponse}; use http::hyper::{HyperServer, HyperHandler, HyperSetCookie, header}; +/// The Rocket type used to mount routes and catchers and launch the +/// application. pub struct Rocket { address: String, port: usize,