Improve 'Responder' API docs.

The improvements are:

  * Point directly and immediately to the 'Responder' derive.
  * Provide more discussion on lifetimes.
  * Format documentation for easier scanning.
This commit is contained in:
Sergio Benitez 2021-06-26 12:28:39 -07:00
parent d34195fe11
commit 770f332832

View File

@ -7,29 +7,47 @@ use crate::request::Request;
/// Trait implemented by types that generate responses for clients. /// Trait implemented by types that generate responses for clients.
/// ///
/// Types that implement this trait can be used as the return type of a handler, /// Any type that implements `Responder` can be used as the return type of a
/// as illustrated below with `T`: /// handler:
/// ///
/// ```rust /// ```rust
/// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket;
/// # type T = (); /// # type T = ();
/// # /// #
/// // This works for any `T` that implements `Responder`.
/// #[get("/")] /// #[get("/")]
/// fn index() -> T { /* ... */ } /// fn index() -> T { /* ... */ }
/// ``` /// ```
/// ///
/// In this example, `T` can be any type, as long as it implements `Responder`. /// # Deriving
/// ///
/// # Return Value /// This trait can, and largely _should_, be automatically derived. The derive
/// can handle all simple cases and most complex cases, too. When deriving
/// `Responder`, the first field of the annotated structure (or of each variant
/// if an `enum`) is used to generate a response while the remaining fields are
/// used as response headers:
/// ///
/// A `Responder` returns an `Ok(Response)` or an `Err(Status)`: /// ```rust
/// # #[macro_use] extern crate rocket;
/// # #[cfg(feature = "json")] mod _main {
/// # type Template = String;
/// use rocket::http::ContentType;
/// use rocket::serde::{Serialize, json::Json};
/// ///
/// * An `Ok` variant means that the `Responder` was successful in generating /// #[derive(Responder)]
/// a `Response`. The `Response` will be written out to the client. /// #[response(bound = "T: Serialize")]
/// enum Error<T> {
/// #[response(status = 400)]
/// Unauthorized(Json<T>),
/// #[response(status = 404)]
/// NotFound(Template, ContentType),
/// }
/// # }
/// ```
/// ///
/// * An `Err` variant means that the `Responder` could not or did not /// For full details on deriving `Responder`, see the [`Responder` derive].
/// generate a `Response`. The contained `Status` will be used to find the ///
/// relevant error catcher which then generates an error response. /// [`Responder` derive]: derive@crate::Responder
/// ///
/// # Provided Implementations /// # Provided Implementations
/// ///
@ -86,37 +104,97 @@ use crate::request::Request;
/// to the client. If the `Result` is `Err`, the wrapped `Err` responder is /// to the client. If the `Result` is `Err`, the wrapped `Err` responder is
/// used to respond to the client. /// used to respond to the client.
/// ///
/// # Return Value
///
/// A `Responder` returns a `Future` whose output type is a `Result<Response,
/// Status>`.
///
/// * An `Ok(Response)` indicates success. The `Response` will be written out
/// to the client.
///
/// * An `Err(Status)` indicates failure. The error catcher for `Status` will
/// be invoked to generate a response.
///
/// # Implementation Tips /// # Implementation Tips
/// ///
/// This section describes a few best practices to take into account when /// This section describes a few best practices to take into account when
/// implementing `Responder`. /// implementing `Responder`.
/// ///
/// ## Joining and Merging /// 1. Avoid Manual Implementations
/// ///
/// When chaining/wrapping other `Responder`s, use the /// The [`Responder` derive] is a powerful mechanism that eliminates the need
/// [`merge()`](Response::merge()) or [`join()`](Response::join()) methods on /// to implement `Responder` in almost all cases. We encourage you to explore
/// the `Response` or `ResponseBuilder` struct. Ensure that you document the /// using the derive _before_ attempting to implement `Responder` directly.
/// merging or joining behavior appropriately. /// It allows you to leverage existing `Responder` implementations through
/// composition, decreasing the opportunity for mistakes or performance
/// degradation.
/// ///
/// ## Inspecting Requests /// 2. Joining and Merging
/// ///
/// A `Responder` has access to the request it is responding to. Even so, you /// When chaining/wrapping other `Responder`s, start with
/// should avoid using the `Request` value as much as possible. This is because /// [`Response::build_from()`] and/or use the [`merge()`](Response::merge())
/// using the `Request` object makes your responder _impure_, and so the use of /// or [`join()`](Response::join()) methods on the `Response` or
/// the type as a `Responder` has less intrinsic meaning associated with it. If /// `ResponseBuilder` struct. Ensure that you document merging or joining
/// the `Responder` were pure, however, it would always respond in the same manner, /// behavior appropriatse.
/// regardless of the incoming request. Thus, knowing the type is sufficient to ///
/// fully determine its functionality. /// 3. Inspecting Requests
///
/// While tempting, a `Responder` that varies its functionality based on the
/// incoming request sacrifices its functionality being understood based
/// purely on its type. By implication, gleaming the functionality of a
/// _handler_ from its type signature also becomes more difficult. You should
/// avoid varying responses based on the `Request` value as much as possible.
/// ///
/// ## Lifetimes /// ## Lifetimes
/// ///
/// `Responder` has two lifetimes: `Responder<'r, 'o: 'r>`. The first lifetime, /// `Responder` has two lifetimes: `Responder<'r, 'o: 'r>`.
/// `'r`, refers to the reference to the `&'r Request`, while the second ///
/// lifetime refers to the returned `Response<'o>`. The bound `'o: 'r` allows /// * `'r` bounds the reference to the `&'r Request`.
/// `'o` to be any lifetime that lives at least as long as the `Request`. In ///
/// particular, this includes borrows from the `Request` itself (where `'o` would /// * `'o` bounds the returned `Response<'o>` to values that live at least as
/// be `'r` as in `impl<'r> Responder<'r, 'r>`) as well as `'static` data (where /// long as the request.
/// `'o` would be `'static` as in `impl<'r> Responder<'r, 'static>`). ///
/// This includes borrows from the `Request` itself (where `'o` would be
/// `'r` as in `impl<'r> Responder<'r, 'r>`) as well as `'static` data
/// (where `'o` would be `'static` as in `impl<'r> Responder<'r, 'static>`).
///
/// In practice, you are likely choosing between four signatures:
///
/// ```rust
/// # use rocket::request::Request;
/// # use rocket::response::{self, Responder};
/// # struct A;
/// // If the response contains no borrowed data.
/// impl<'r> Responder<'r, 'static> for A {
/// fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
/// todo!()
/// }
/// }
///
/// # struct B<'r>(&'r str);
/// // If the response borrows from the request.
/// impl<'r> Responder<'r, 'r> for B<'r> {
/// fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
/// todo!()
/// }
/// }
///
/// # struct C;
/// // If the response is or wraps a borrow that may outlive the request.
/// impl<'r, 'o: 'r> Responder<'r, 'o> for &'o C {
/// fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
/// todo!()
/// }
/// }
///
/// # struct D<R>(R);
/// // If the response wraps an existing responder.
/// impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for D<R> {
/// fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
/// todo!()
/// }
/// }
/// ```
/// ///
/// # Example /// # Example
/// ///
@ -162,10 +240,9 @@ use crate::request::Request;
/// use rocket::http::ContentType; /// use rocket::http::ContentType;
/// ///
/// impl<'r> Responder<'r, 'static> for Person { /// impl<'r> Responder<'r, 'static> for Person {
/// fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> { /// fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
/// let person_string = format!("{}:{}", self.name, self.age); /// let string = format!("{}:{}", self.name, self.age);
/// Response::build() /// Response::build_from(string.respond_to(req)?)
/// .sized_body(person_string.len(), Cursor::new(person_string))
/// .raw_header("X-Person-Name", self.name) /// .raw_header("X-Person-Name", self.name)
/// .raw_header("X-Person-Age", self.age.to_string()) /// .raw_header("X-Person-Age", self.age.to_string())
/// .header(ContentType::new("application", "x-person")) /// .header(ContentType::new("application", "x-person"))
@ -177,6 +254,35 @@ use crate::request::Request;
/// # fn person() -> Person { Person { name: "a".to_string(), age: 20 } } /// # fn person() -> Person { Person { name: "a".to_string(), age: 20 } }
/// # fn main() { } /// # fn main() { }
/// ``` /// ```
///
/// Note that the implementation could have instead been derived if structured
/// in a slightly different manner:
///
/// ```rust
/// use rocket::http::Header;
/// use rocket::response::Responder;
///
/// #[derive(Responder)]
/// #[response(content_type = "application/x-person")]
/// struct Person {
/// text: String,
/// name: Header<'static>,
/// age: Header<'static>,
/// }
///
/// impl Person {
/// fn new(name: &str, age: usize) -> Person {
/// Person {
/// text: format!("{}:{}", name, age),
/// name: Header::new("X-Person-Name", name.to_string()),
/// age: Header::new("X-Person-Age", age.to_string())
/// }
/// }
/// }
/// #
/// # #[rocket::get("/person")]
/// # fn person() -> Person { Person::new("Bob", 29) }
/// ```
pub trait Responder<'r, 'o: 'r> { pub trait Responder<'r, 'o: 'r> {
/// Returns `Ok` if a `Response` could be generated successfully. Otherwise, /// Returns `Ok` if a `Response` could be generated successfully. Otherwise,
/// returns an `Err` with a failing `Status`. /// returns an `Err` with a failing `Status`.