diff --git a/README.md b/README.md index e9f2fdcf..c5785276 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ simple. * Core: `cd lib && cargo build` * Codegen: `cd codegen && cargo build` - * Contrib: `cd contrib && cargo build` + * Contrib: `cd contrib && cargo build --all-features` ### Examples diff --git a/lib/src/response/mod.rs b/lib/src/response/mod.rs index c4d650d7..4d20569c 100644 --- a/lib/src/response/mod.rs +++ b/lib/src/response/mod.rs @@ -30,7 +30,7 @@ mod failure; pub mod content; pub mod status; -pub use self::response::{Response, Body, DEFAULT_CHUNK_SIZE}; +pub use self::response::{Response, ResponseBuilder, Body, DEFAULT_CHUNK_SIZE}; pub use self::responder::Responder; pub use self::redirect::Redirect; pub use self::flash::Flash; diff --git a/lib/src/response/responder.rs b/lib/src/response/responder.rs index 0a3d47fb..27c4c2c7 100644 --- a/lib/src/response/responder.rs +++ b/lib/src/response/responder.rs @@ -166,7 +166,7 @@ pub trait Responder<'r> { /// let body_string = response.body().and_then(|b| b.into_string()); /// assert_eq!(body_string, Some("Hello".to_string())); /// -/// let content_type: Vec<_> = response.get_header_values("Content-Type").collect(); +/// let content_type: Vec<_> = response.header_values("Content-Type").collect(); /// assert_eq!(content_type.len(), 1); /// assert_eq!(content_type[0], ContentType::Plain.to_string()); /// ``` diff --git a/lib/src/response/response.rs b/lib/src/response/response.rs index 8c9cb652..bbc92919 100644 --- a/lib/src/response/response.rs +++ b/lib/src/response/response.rs @@ -18,7 +18,7 @@ pub enum Body { } impl Body { - /// Returns a mutable borrow to the inner type. + /// Returns a new `Body` with a mutable borrow to `self`'s inner type. pub fn as_mut(&mut self) -> Body<&mut T> { match *self { Body::Sized(ref mut b, n) => Body::Sized(b, n), @@ -64,11 +64,80 @@ impl fmt::Debug for Body { } } +/// Type for easily building `Response`s. +/// +/// Building a [Response](struct.Response.html) can be a low-level ordeal; this +/// structure presents a higher-level API that simplified building `Response`s. +/// +/// # Usage +/// +/// `ResponseBuilder` follows the builder pattern and is usually obtained by +/// calling [build](struct.Response.html#method.build) on `Response`. Almost all +/// methods take the current builder as a mutable reference and return the same +/// mutable reference with field(s) modified in the `Responder` being built. +/// These method calls can be chained: `build.a().b()`. +/// +/// To finish building and retrieve the built `Response`, use the +/// [finalize](#method.finalize) or [ok](#method.ok) methods. +/// +/// ## Headers +/// +/// When building a `Response`, headers can either be _replaced_ or _adjoined_; +/// the default behavior (using `header(..)`) is to _replace_. When a header is +/// _replaced_, any existing values for headers with the same name are removed, +/// and the new value is set. If no header exists, the header is simply added. +/// On the other hand, when a header is `adjoined`, all existing values will +/// remain, and the `value` of the adjoined header will be added to the set of +/// existing values, if any. Adjoining maintains order: headers adjoined first +/// will appear first in the `Response`. +/// +/// ## Joining and Merging +/// +/// It is often necessary to combine multiple `Response`s in some way. The +/// [merge](#method.merge) and [join](#method.join) methods facilitate this. The +/// `merge` method replaces all of the fields in `self` with those present in +/// `other`. The `join` method sets any fields not set in `self` to the value in +/// `other`. See their documentation for more details. +/// ## Example +/// +/// The following example builds a `Response` with: +/// +/// * **Status**: `418 I'm a teapot` +/// * **Content-Type** header: `text/plain; charset=utf-8` +/// * **X-Teapot-Make** header: `Rocket` +/// * **X-Teapot-Model** headers: `Utopia`, `Series 1` +/// * **Body**: fixed-size string `"Brewing the best coffee!"` +/// +/// ```rust +/// use std::io::Cursor; +/// use rocket::response::Response; +/// use rocket::http::{Status, ContentType}; +/// +/// let response = Response::build() +/// .status(Status::ImATeapot) +/// .header(ContentType::Plain) +/// .raw_header("X-Teapot-Make", "Rocket") +/// .raw_header("X-Teapot-Model", "Utopia") +/// .raw_header_adjoin("X-Teapot-Model", "Series 1") +/// .sized_body(Cursor::new("Brewing the best coffee!")) +/// .finalize(); +/// ``` +/// pub struct ResponseBuilder<'r> { response: Response<'r> } impl<'r> ResponseBuilder<'r> { + /// Creates a new `ResponseBuilder` that will build on top of the `base` + /// `Response`. + /// + /// # Example + /// + /// ```rust + /// use rocket::response::{ResponseBuilder, Response}; + /// + /// let builder = ResponseBuilder::new(Response::new()); + /// ``` #[inline(always)] pub fn new(base: Response<'r>) -> ResponseBuilder<'r> { ResponseBuilder { @@ -76,12 +145,36 @@ impl<'r> ResponseBuilder<'r> { } } + /// Sets the status of the `Response` being built to `status`. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// use rocket::http::Status; + /// + /// let response = Response::build() + /// .status(Status::NotFound) + /// .finalize(); + /// ``` #[inline(always)] pub fn status(&mut self, status: Status) -> &mut ResponseBuilder<'r> { self.response.set_status(status); self } + /// Sets the status of the `Response` being built to a custom status + /// constructed from the `code` and `reason` phrase. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// + /// let response = Response::build() + /// .raw_status(699, "Alien Encounter") + /// .finalize(); + /// ``` #[inline(always)] pub fn raw_status(&mut self, code: u16, reason: &'static str) -> &mut ResponseBuilder<'r> { @@ -89,6 +182,24 @@ impl<'r> ResponseBuilder<'r> { self } + /// Adds `header` to the `Response`, replacing any header with the same name + /// that already exists in the response. If multiple headers with + /// the same name exist, they are all removed, and only the new header and + /// value will remain. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// use rocket::http::ContentType; + /// + /// let response = Response::build() + /// .header(ContentType::JSON) + /// .header(ContentType::HTML) + /// .finalize(); + /// + /// assert_eq!(response.header_values("Content-Type").count(), 1); + /// ``` #[inline(always)] pub fn header<'h: 'r, H>(&mut self, header: H) -> &mut ResponseBuilder<'r> where H: Into> @@ -97,6 +208,24 @@ impl<'r> ResponseBuilder<'r> { self } + /// Adds `header` to the `Response` by adjoining the header with any + /// existing headers with the same name that already exist in the + /// `Response`. This allow for multiple headers with the same name and + /// potentially different values to be present in the `Response`. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// use rocket::http::hyper::header::Accept; + /// + /// let response = Response::build() + /// .header_adjoin(Accept::json()) + /// .header_adjoin(Accept::text()) + /// .finalize(); + /// + /// assert_eq!(response.header_values("Accept").count(), 2); + /// ``` #[inline(always)] pub fn header_adjoin<'h: 'r, H>(&mut self, header: H) -> &mut ResponseBuilder<'r> where H: Into> @@ -105,6 +234,24 @@ impl<'r> ResponseBuilder<'r> { self } + /// Adds custom a header to the `Response` with the given name and value, + /// replacing any header with the same name that already exists in the + /// response. If multiple headers with the same name exist, they are all + /// removed, and only the new header and value will remain. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// use rocket::http::ContentType; + /// + /// let response = Response::build() + /// .raw_header("X-Custom", "first") + /// .raw_header("X-Custom", "second") + /// .finalize(); + /// + /// assert_eq!(response.header_values("X-Custom").count(), 1); + /// ``` #[inline(always)] pub fn raw_header<'a: 'r, 'b: 'r, N, V>(&mut self, name: N, value: V) -> &mut ResponseBuilder<'r> @@ -114,6 +261,24 @@ impl<'r> ResponseBuilder<'r> { self } + /// Adds custom header to the `Response` with the given name and value, + /// adjoining the header with any existing headers with the same name that + /// already exist in the `Response`. This allow for multiple headers with + /// the same name and potentially different values to be present in the + /// `Response`. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// + /// let response = Response::build() + /// .raw_header_adjoin("X-Custom", "first") + /// .raw_header_adjoin("X-Custom", "second") + /// .finalize(); + /// + /// assert_eq!(response.header_values("X-Custom").count(), 2); + /// ``` #[inline(always)] pub fn raw_header_adjoin<'a: 'r, 'b: 'r, N, V>(&mut self, name: N, value: V) -> &mut ResponseBuilder<'r> @@ -123,6 +288,22 @@ impl<'r> ResponseBuilder<'r> { self } + /// Sets the body of the `Response` to be the fixed-sized `body`. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// use std::fs::File; + /// # use std::io; + /// + /// # fn test() -> io::Result<()> { + /// let response = Response::build() + /// .sized_body(File::open("body.txt")?) + /// .finalize(); + /// # Ok(()) + /// # } + /// ``` #[inline(always)] pub fn sized_body(&mut self, body: B) -> &mut ResponseBuilder<'r> where B: io::Read + io::Seek + 'r @@ -131,6 +312,22 @@ impl<'r> ResponseBuilder<'r> { self } + /// Sets the body of the `Response` to be the streamed `body`. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// use std::fs::File; + /// # use std::io; + /// + /// # fn test() -> io::Result<()> { + /// let response = Response::build() + /// .streamed_body(File::open("body.txt")?) + /// .finalize(); + /// # Ok(()) + /// # } + /// ``` #[inline(always)] pub fn streamed_body(&mut self, body: B) -> &mut ResponseBuilder<'r> where B: io::Read + 'r @@ -139,6 +336,23 @@ impl<'r> ResponseBuilder<'r> { self } + /// Sets the body of the `Response` to be the streamed `body` with a custom + /// chunk size, in bytes. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// use std::fs::File; + /// # use std::io; + /// + /// # fn test() -> io::Result<()> { + /// let response = Response::build() + /// .chunked_body(File::open("body.txt")?, 8096) + /// .finalize(); + /// # Ok(()) + /// # } + /// ``` #[inline(always)] pub fn chunked_body(&mut self, body: B, chunk_size: u64) -> &mut ResponseBuilder<'r> @@ -147,23 +361,122 @@ impl<'r> ResponseBuilder<'r> { self } + /// Merges the `other` `Response` into `self` by setting any fields in + /// `self` to the corresponding value in `other` if they are set in `other`. + /// Fields in `self` are unchanged if they are not set in `other`. If a + /// header is set in both `self` and `other`, the values in `other` are + /// kept. Headers set only in `self` remain. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// use rocket::http::{Status, ContentType}; + /// + /// let base = Response::build() + /// .status(Status::NotFound) + /// .header(ContentType::HTML) + /// .raw_header("X-Custom", "value 1") + /// .finalize(); + /// + /// let response = Response::build() + /// .status(Status::ImATeapot) + /// .raw_header("X-Custom", "value 2") + /// .raw_header_adjoin("X-Custom", "value 3") + /// .merge(base) + /// .finalize(); + /// + /// assert_eq!(response.status(), Status::NotFound); + /// + /// # { + /// let ctype: Vec<_> = response.header_values("Content-Type").collect(); + /// assert_eq!(ctype, vec![ContentType::HTML.to_string()]); + /// # } + /// + /// # { + /// let custom_values: Vec<_> = response.header_values("X-Custom").collect(); + /// assert_eq!(custom_values, vec!["value 1"]); + /// # } + /// ``` #[inline(always)] pub fn merge(&mut self, other: Response<'r>) -> &mut ResponseBuilder<'r> { self.response.merge(other); self } + /// Joins the `other` `Response` into `self` by setting any fields in `self` + /// to the corresponding value in `other` if they are set in `self`. Fields + /// in `self` are unchanged if they are already set. If a header is set in + /// both `self` and `other`, the values are adjoined, with the values in + /// `self` coming first. Headers only in `self` or `other` are set in + /// `self`. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// use rocket::http::{Status, ContentType}; + /// + /// let other = Response::build() + /// .status(Status::NotFound) + /// .header(ContentType::HTML) + /// .raw_header("X-Custom", "value 1") + /// .finalize(); + /// + /// let response = Response::build() + /// .status(Status::ImATeapot) + /// .raw_header("X-Custom", "value 2") + /// .raw_header_adjoin("X-Custom", "value 3") + /// .join(other) + /// .finalize(); + /// + /// assert_eq!(response.status(), Status::ImATeapot); + /// + /// # { + /// let ctype: Vec<_> = response.header_values("Content-Type").collect(); + /// assert_eq!(ctype, vec![ContentType::HTML.to_string()]); + /// # } + /// + /// # { + /// let custom_values: Vec<_> = response.header_values("X-Custom").collect(); + /// assert_eq!(custom_values, vec!["value 2", "value 3", "value 1"]); + /// # } + /// ``` #[inline(always)] pub fn join(&mut self, other: Response<'r>) -> &mut ResponseBuilder<'r> { self.response.join(other); self } + /// Retrieve the built `Response`. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// + /// let response = Response::build() + /// // build the response + /// .finalize(); + /// ``` #[inline(always)] pub fn finalize(&mut self) -> Response<'r> { ::std::mem::replace(&mut self.response, Response::new()) } + /// Retrieve the built `Response` wrapped in `Ok`. + /// + /// # Example + /// + /// ```rust + /// use rocket::Response; + /// + /// let response: Result = Response::build() + /// // build the response + /// .ok(); + /// + /// assert!(response.is_ok()); + /// ``` #[inline(always)] pub fn ok(&mut self) -> Result, T> { Ok(self.finalize()) @@ -173,7 +486,7 @@ impl<'r> ResponseBuilder<'r> { // `join`? Maybe one does one thing, the other does another? IE: `merge` // replaces, `join` adds. One more thing that could be done: we could make it // some that _some_ headers default to replacing, and other to joining. -/// An HTTP response. +/// An HTTP/Rocket response, returned by `Responder`s. #[derive(Default)] pub struct Response<'r> { status: Option, @@ -222,8 +535,7 @@ impl<'r> Response<'r> { } #[inline(always)] - pub fn get_header_values<'h>(&'h self, name: &str) - -> impl Iterator { + pub fn header_values<'h>(&'h self, name: &str) -> impl Iterator { self.headers.get(name) } diff --git a/lib/tests/head_handling.rs b/lib/tests/head_handling.rs index 7ab5e30a..f0537967 100644 --- a/lib/tests/head_handling.rs +++ b/lib/tests/head_handling.rs @@ -51,7 +51,7 @@ fn auto_head() { } - let content_type: Vec<_> = response.get_header_values("Content-Type").collect(); + let content_type: Vec<_> = response.header_values("Content-Type").collect(); assert_eq!(content_type, vec![ContentType::Plain.to_string()]); let mut req = MockRequest::new(Head, "/empty"); @@ -67,6 +67,6 @@ fn user_head() { assert_eq!(response.status(), Status::Ok); - let content_type: Vec<_> = response.get_header_values("Content-Type").collect(); + let content_type: Vec<_> = response.header_values("Content-Type").collect(); assert_eq!(content_type, vec![ContentType::JSON.to_string()]); }