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
1 changed files with 140 additions and 34 deletions

View File

@ -7,29 +7,47 @@ use crate::request::Request;
/// Trait implemented by types that generate responses for clients.
///
/// Types that implement this trait can be used as the return type of a handler,
/// as illustrated below with `T`:
/// Any type that implements `Responder` can be used as the return type of a
/// handler:
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// # type T = ();
/// #
/// // This works for any `T` that implements `Responder`.
/// #[get("/")]
/// 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
/// a `Response`. The `Response` will be written out to the client.
/// #[derive(Responder)]
/// #[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
/// generate a `Response`. The contained `Status` will be used to find the
/// relevant error catcher which then generates an error response.
/// For full details on deriving `Responder`, see the [`Responder` derive].
///
/// [`Responder` derive]: derive@crate::Responder
///
/// # Provided Implementations
///
@ -86,37 +104,97 @@ use crate::request::Request;
/// to the client. If the `Result` is `Err`, the wrapped `Err` responder is
/// 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
///
/// This section describes a few best practices to take into account when
/// implementing `Responder`.
///
/// ## Joining and Merging
/// 1. Avoid Manual Implementations
///
/// When chaining/wrapping other `Responder`s, use the
/// [`merge()`](Response::merge()) or [`join()`](Response::join()) methods on
/// the `Response` or `ResponseBuilder` struct. Ensure that you document the
/// merging or joining behavior appropriately.
/// The [`Responder` derive] is a powerful mechanism that eliminates the need
/// to implement `Responder` in almost all cases. We encourage you to explore
/// using the derive _before_ attempting to implement `Responder` directly.
/// 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
/// should avoid using the `Request` value as much as possible. This is because
/// using the `Request` object makes your responder _impure_, and so the use of
/// the type as a `Responder` has less intrinsic meaning associated with it. If
/// the `Responder` were pure, however, it would always respond in the same manner,
/// regardless of the incoming request. Thus, knowing the type is sufficient to
/// fully determine its functionality.
/// When chaining/wrapping other `Responder`s, start with
/// [`Response::build_from()`] and/or use the [`merge()`](Response::merge())
/// or [`join()`](Response::join()) methods on the `Response` or
/// `ResponseBuilder` struct. Ensure that you document merging or joining
/// behavior appropriatse.
///
/// 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
///
/// `Responder` has two lifetimes: `Responder<'r, 'o: 'r>`. The first lifetime,
/// `'r`, refers to the reference to the `&'r Request`, while the second
/// lifetime refers to the returned `Response<'o>`. The bound `'o: 'r` allows
/// `'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
/// 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>`).
/// `Responder` has two lifetimes: `Responder<'r, 'o: 'r>`.
///
/// * `'r` bounds the reference to the `&'r Request`.
///
/// * `'o` bounds the returned `Response<'o>` to values that live at least as
/// long as the request.
///
/// 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
///
@ -162,10 +240,9 @@ use crate::request::Request;
/// use rocket::http::ContentType;
///
/// impl<'r> Responder<'r, 'static> for Person {
/// fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
/// let person_string = format!("{}:{}", self.name, self.age);
/// Response::build()
/// .sized_body(person_string.len(), Cursor::new(person_string))
/// fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
/// let string = format!("{}:{}", self.name, self.age);
/// Response::build_from(string.respond_to(req)?)
/// .raw_header("X-Person-Name", self.name)
/// .raw_header("X-Person-Age", self.age.to_string())
/// .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 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> {
/// Returns `Ok` if a `Response` could be generated successfully. Otherwise,
/// returns an `Err` with a failing `Status`.