Rocket/site/guide/5-responses.md

658 lines
23 KiB
Markdown
Raw Normal View History

# Responses
You may have noticed that the return type of a handler appears to be arbitrary,
2017-07-04 09:37:21 +00:00
and that's because it is! A value of any type that implements the [`Responder`]
trait can be returned, including your own. In this section, we describe the
`Responder` trait as well as several useful `Responder`s provided by Rocket.
We'll also briefly discuss how to implement your own `Responder`.
2018-10-16 05:50:35 +00:00
[`Responder`]: @api/rocket/response/trait.Responder.html
## Responder
2017-07-04 09:37:21 +00:00
Types that implement [`Responder`] know how to generate a [`Response`] from
their values. A `Response` includes an HTTP status, headers, and body. The body
may either be _fixed-sized_ or _streaming_. The given `Responder` implementation
decides which to use. For instance, `String` uses a fixed-sized body, while
`File` uses a streamed response. Responders may dynamically adjust their
responses according to the incoming `Request` they are responding to.
2018-10-16 05:50:35 +00:00
[`Response`]: @api/rocket/response/struct.Response.html
### Wrapping
2017-07-04 09:37:21 +00:00
Before we describe a few responders, we note that it is typical for responders
to _wrap_ other responders. That is, responders can be of the following form,
where `R` is some type that implements `Responder`:
```rust
struct WrappingResponder<R>(R);
```
2017-07-04 09:37:21 +00:00
A wrapping responder modifies the response returned by `R` before responding
with that same response. For instance, Rocket provides `Responder`s in the
2018-10-16 05:50:35 +00:00
[`status` module](@api/rocket/response/status/) that override the status code of
the wrapped `Responder`. As an example, the [`Accepted`] type sets the status to
`202 - Accepted`. It can be used as follows:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
2017-07-04 09:37:21 +00:00
use rocket::response::status;
#[post("/<id>")]
fn new(id: usize) -> status::Accepted<String> {
2017-07-10 11:59:55 +00:00
status::Accepted(Some(format!("id: '{}'", id)))
}
```
2018-10-16 05:50:35 +00:00
Similarly, the types in the [`content` module](@api/rocket/response/content/)
can be used to override the Content-Type of a response. For instance, to set the
Content-Type of `&'static str` to JSON, you can use the [`content::Json`] type
as follows:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
2017-07-04 09:37:21 +00:00
use rocket::response::content;
#[get("/")]
fn json() -> content::Json<&'static str> {
content::Json("{ 'hi': 'world' }")
}
```
! warning: This is _not_ the same as the [`Json`] in [`rocket_contrib`]!
2018-10-16 05:50:35 +00:00
[`Accepted`]: @api/rocket/response/status/struct.Accepted.html
[`content::Json`]: @api/rocket/response/content/struct.Json.html
2017-07-04 09:37:21 +00:00
### Errors
Responders may fail; they need not _always_ generate a response. Instead, they
can return an `Err` with a given status code. When this happens, Rocket forwards
2018-10-16 05:50:35 +00:00
the request to the [error catcher](../requests/#error-catchers) for the
given status code.
If an error catcher has been registered for the given status code, Rocket will
invoke it. The catcher creates and returns a response to the client. If no error
catcher has been registered and the error status code is one of the standard
HTTP status code, a default error catcher will be used. Default error catchers
2017-07-04 09:37:21 +00:00
return an HTML page with the status code and description. If there is no catcher
for a custom status code, Rocket uses the **500** error catcher to return a
response.
### Status
While not encouraged, you can also forward a request to a catcher manually by
returning a [`Status`] directly. For instance, to forward to the catcher for
**406: Not Acceptable**, you would write:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::http::Status;
#[get("/")]
fn just_fail() -> Status {
Status::NotAcceptable
}
```
The response generated by `Status` depends on the status code itself. As
indicated above, for error status codes (in range [400, 599]), `Status` forwards
to the corresponding error catcher. The table below summarizes responses
generated by `Status` for these and other codes:
| Status Code Range | Response |
|-------------------|---------------------------------------|
| [400, 599] | Forwards to catcher for given status. |
| 100, [200, 205] | Empty with given status. |
| All others. | Invalid. Errors to `500` catcher. |
2019-05-13 23:18:48 +00:00
[`Status`]: https://api.rocket.rs/v0.5/rocket/http/struct.Status.html
## Custom Responders
The [`Responder`] trait documentation details how to implement your own custom
responders by explicitly implementing the trait. For most use cases, however,
Rocket makes it possible to automatically derive an implementation of
`Responder`. In particular, if your custom responder wraps an existing
responder, headers, or sets a custom status or content-type, `Responder` can be
automatically derived:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::http::{Header, ContentType};
# type OtherResponder = ();
# type MyType = u8;
#[derive(Responder)]
#[response(status = 500, content_type = "json")]
struct MyResponder {
inner: OtherResponder,
header: ContentType,
more: Header<'static>,
#[response(ignore)]
unrelated: MyType,
}
```
For the example above, Rocket generates a `Responder` implementation that:
* Set the response's status to `500: Internal Server Error`.
* Sets the Content-Type to `application/json`.
* Adds the headers `self.header` and `self.more` to the response.
* Completes the response using `self.inner`.
Note that the _first_ field is used as the inner responder while all remaining
fields (unless ignored with `#[response(ignore)]`) are added as headers to the
response. The optional `#[response]` attribute can be used to customize the
status and content-type of the response. Because `ContentType` and `Status` are
themselves headers, you can also dynamically set the content-type and status by
simply including fields of these types.
For more on using the `Responder` derive, see the [`Responder` derive]
documentation.
[`Responder` derive]: @api/rocket_codegen/derive.Responder.html
2017-07-10 11:59:55 +00:00
## Implementations
2017-07-04 09:37:21 +00:00
Rocket implements `Responder` for many types in Rust's standard library
including `String`, `&str`, `File`, `Option`, and `Result`. The [`Responder`]
documentation describes these in detail, but we briefly cover a few here.
2017-07-10 11:59:55 +00:00
### Strings
2017-07-04 09:37:21 +00:00
The `Responder` implementations for `&str` and `String` are straight-forward:
the string is used as a sized body, and the Content-Type of the response is set
to `text/plain`. To get a taste for what such a `Responder` implementation looks
like, here's the implementation for `String`:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use std::io::Cursor;
use rocket::request::Request;
use rocket::response::{self, Response, Responder};
use rocket::http::ContentType;
# struct String(std::string::String);
impl<'a> Responder<'a> for String {
fn respond_to(self, _: &Request) -> response::Result<'a> {
2017-07-04 09:37:21 +00:00
Response::build()
.header(ContentType::Plain)
# /*
2017-07-04 09:37:21 +00:00
.sized_body(Cursor::new(self))
# */
# .sized_body(Cursor::new(self.0))
2017-07-04 09:37:21 +00:00
.ok()
}
}
```
Because of these implementations, you can directly return an `&str` or `String`
type from a handler:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
2017-07-04 09:37:21 +00:00
#[get("/string")]
fn handler() -> &'static str {
"Hello there! I'm a string!"
}
```
2017-07-10 11:59:55 +00:00
### `Option`
2017-07-04 09:37:21 +00:00
2018-08-09 02:06:52 +00:00
`Option` is a _wrapping_ responder: an `Option<T>` can only be returned when `T`
2017-07-04 09:37:21 +00:00
implements `Responder`. If the `Option` is `Some`, the wrapped responder is used
to respond to the client. Otherwise, a error of **404 - Not Found** is returned
to the client.
This implementation makes `Option` a convenient type to return when it is not
known until process-time whether content exists. For example, because of
`Option`, we can implement a file server that returns a `200` when a file is
found and a `404` when a file is not found in just 4, idiomatic lines:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use std::path::{Path, PathBuf};
use rocket::response::NamedFile;
2017-07-04 09:37:21 +00:00
#[get("/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok()
}
```
2017-07-10 11:59:55 +00:00
### `Result`
2017-07-04 09:37:21 +00:00
`Result` is a special kind of wrapping responder: its functionality depends on
whether the error type `E` implements `Responder`.
When the error type `E` implements `Responder`, the wrapped `Responder` in `Ok`
or `Err`, whichever it might be, is used to respond to the client. This means
that the responder can be chosen dynamically at run-time, and two different
kinds of responses can be used depending on the circumstances. Revisiting our
2017-07-04 09:37:21 +00:00
file server, for instance, we might wish to provide more feedback to the user
when a file isn't found. We might do this as follows:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use std::path::{Path, PathBuf};
use rocket::response::NamedFile;
2017-07-04 09:37:21 +00:00
use rocket::response::status::NotFound;
#[get("/<file..>")]
fn files(file: PathBuf) -> Result<NamedFile, NotFound<String>> {
let path = Path::new("static/").join(file);
NamedFile::open(&path).map_err(|e| NotFound(e.to_string()))
2017-07-04 09:37:21 +00:00
}
```
If the error type `E` _does not_ implement `Responder`, then the error is simply
logged to the console, using its `Debug` implementation, and a `500` error is
returned to the client.
## Rocket Responders
Some of Rocket's best features are implemented through responders. You can find
many of these responders in the [`response`] module and [`rocket_contrib`]
library. Among these are:
2017-07-04 09:37:21 +00:00
* [`Content`] - Used to override the Content-Type of a response.
* [`NamedFile`] - Streams a file to the client; automatically sets the
Content-Type based on the file's extension.
* [`Redirect`] - Redirects the client to a different URI.
* [`Stream`] - Streams a response to a client from an arbitrary `Read`er type.
* [`status`] - Contains types that override the status code of a response.
* [`Flash`] - Sets a "flash" cookie that is removed when accessed.
* [`Json`] - Automatically serializes values into JSON.
* [`MsgPack`] - Automatically serializes values into MessagePack.
* [`Template`] - Renders a dynamic template using handlebars or Tera.
* [`Compress`] - Compresses a response at the HTTP layer.
2017-07-04 09:37:21 +00:00
2018-10-16 05:50:35 +00:00
[`status`]: @api/rocket/response/status/
[`response`]: @api/rocket/response/
[`NamedFile`]: @api/rocket/response/struct.NamedFile.html
[`Content`]: @api/rocket/response/struct.Content.html
[`Redirect`]: @api/rocket/response/struct.Redirect.html
[`Stream`]: @api/rocket/response/struct.Stream.html
[`Flash`]: @api/rocket/response/struct.Flash.html
[`MsgPack`]: @api/rocket_contrib/msgpack/struct.MsgPack.html
[`Compress`]: @api/rocket_contrib/compression/struct.Compress.html
2017-07-04 09:37:21 +00:00
### Streaming
The `Stream` type deserves special attention. When a large amount of data needs
to be sent to the client, it is better to stream the data to the client to avoid
2017-07-10 11:59:55 +00:00
consuming large amounts of memory. Rocket provides the [`Stream`] type, making
2017-07-04 09:37:21 +00:00
this easy. The `Stream` type can be created from any `Read` type. For example,
to stream from a local Unix stream, we might write:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# #[cfg(unix)]
# mod test {
use std::os::unix::net::UnixStream;
use rocket::response::{Stream, Debug};
2017-07-04 09:37:21 +00:00
#[get("/stream")]
fn stream() -> Result<Stream<UnixStream>, Debug<std::io::Error>> {
Ok(UnixStream::connect("/path/to/my/socket").map(Stream::from)?)
2017-07-04 09:37:21 +00:00
}
# }
2017-07-04 09:37:21 +00:00
```
2018-10-16 05:50:35 +00:00
[`rocket_contrib`]: @api/rocket_contrib/
2017-07-04 09:37:21 +00:00
### JSON
2018-10-16 06:24:23 +00:00
The [`Json`] responder in [`rocket_contrib`] allows you to easily respond with
well-formed JSON data: simply return a value of type `Json<T>` where `T` is the
2017-07-04 09:37:21 +00:00
type of a structure to serialize into JSON. The type `T` must implement the
[`Serialize`] trait from [`serde`], which can be automatically derived.
As an example, to respond with the JSON value of a `Task` structure, we might
2017-07-04 09:37:21 +00:00
write:
```rust
# #[macro_use] extern crate rocket;
# #[macro_use] extern crate rocket_contrib;
# fn main() {}
use serde::Serialize;
2018-10-16 06:24:23 +00:00
use rocket_contrib::json::Json;
2017-07-04 09:37:21 +00:00
#[derive(Serialize)]
struct Task { /* .. */ }
#[get("/todo")]
fn todo() -> Json<Task> {
Json(Task { /* .. */ })
}
```
2018-10-16 06:24:23 +00:00
The `Json` type serializes the structure into JSON, sets the Content-Type to
2017-07-04 09:37:21 +00:00
JSON, and emits the serialized data in a fixed-sized body. If serialization
fails, a **500 - Internal Server Error** is returned.
2017-07-04 09:37:21 +00:00
The [JSON example on GitHub] provides further illustration.
2018-10-16 06:24:23 +00:00
[`Json`]: @api/rocket_contrib/json/struct.Json.html
2017-07-04 09:37:21 +00:00
[`Serialize`]: https://docs.serde.rs/serde/trait.Serialize.html
[`serde`]: https://docs.serde.rs/serde/
2018-10-16 05:50:35 +00:00
[JSON example on GitHub]: @example/json
## Templates
2017-07-04 09:37:21 +00:00
Rocket includes built-in templating support that works largely through a
[`Template`] responder in `rocket_contrib`. To render a template named "index",
for instance, you might return a value of type `Template` as follows:
```rust
# #[macro_use] extern crate rocket;
# #[macro_use] extern crate rocket_contrib;
# fn main() {}
use rocket_contrib::templates::Template;
#[get("/")]
fn index() -> Template {
# /*
2017-07-04 09:37:21 +00:00
let context = /* object-like value */;
# */ let context = ();
2017-07-04 09:37:21 +00:00
Template::render("index", &context)
2017-06-06 17:23:46 +00:00
}
```
Templates are rendered with the `render` method. The method takes in the name of
2017-07-04 09:37:21 +00:00
a template and a context to render the template with. The context can be any
type that implements `Serialize` and serializes into an `Object` value, such as
structs, `HashMaps`, and others.
2018-10-27 03:52:06 +00:00
For a template to be renderable, it must first be registered. The `Template`
fairing automatically registers all discoverable templates when attached. The
2018-10-27 03:52:06 +00:00
[Fairings](../fairings) sections of the guide provides more information on
fairings. To attach the template fairing, simply call
`.attach(Template::fairing())` on an instance of `Rocket` as follows:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# use rocket_contrib::templates::Template;
2017-07-04 09:37:21 +00:00
fn main() {
rocket::ignite()
.mount("/", routes![/* .. */])
.attach(Template::fairing());
}
```
2018-10-27 03:52:06 +00:00
Rocket discovers templates in the [configurable] `template_dir` directory.
Templating support in Rocket is engine agnostic. The engine used to render a
template depends on the template file's extension. For example, if a file ends
with `.hbs`, Handlebars is used, while if a file ends with `.tera`, Tera is
used.
! note: The name of the template _does not_ include its extension.
For a template file named `index.html.tera`, call `render("index")` and use
the name `"index"` in templates, i.e, `{% extends "index" %}` or `{% extends
"base" %}` for `base.html.tera`.
### Live Reloading
2018-10-27 03:52:06 +00:00
When your application is compiled in `debug` mode (without the `--release` flag
passed to `cargo`), templates are automatically reloaded when they are modified
on supported platforms. This means that you don't need to rebuild your
application to observe template changes: simply refresh! In release builds,
reloading is disabled.
The [`Template`] API documentation contains more information about templates,
including how to customize a template engine to add custom helpers and filters.
2018-10-27 03:52:06 +00:00
The [Handlebars templates example](@example/handlebars_templates) is a
fully composed application that makes use of Handlebars templates, while the
[Tera templates example](@example/tera_templates) does the same for Tera.
2017-07-04 09:37:21 +00:00
2018-10-16 06:24:23 +00:00
[`Template`]: @api/rocket_contrib/templates/struct.Template.html
2018-10-16 05:50:35 +00:00
[configurable]: ../configuration/#extras
## Typed URIs
2018-10-27 03:52:06 +00:00
Rocket's [`uri!`] macro allows you to build URIs to routes in your application
in a robust, type-safe, and URI-safe manner. Type or route parameter mismatches
are caught at compile-time, and changes to route URIs are automatically
reflected in the generated URIs.
2018-10-27 03:52:06 +00:00
The `uri!` macro returns an [`Origin`] structure with the URI of the supplied
route interpolated with the given values. Each value passed into `uri!` is
rendered in its appropriate place in the URI using the [`UriDisplay`]
implementation for the value's type. The `UriDisplay` implementation ensures
that the rendered value is URI-safe.
Note that `Origin` implements `Into<Uri>` (and by extension, `TryInto<Uri>`), so
it can be converted into a [`Uri`] using `.into()` as needed and passed into
methods such as [`Redirect::to()`].
For example, given the following route:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
#[get("/person/<name>?<age>")]
fn person(name: String, age: Option<u8>) { /* .. */ }
```
URIs to `person` can be created as follows:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# #[get("/person/<name>?<age>")]
# fn person(name: String, age: Option<u8>) { /* .. */ }
// with unnamed parameters, in route path declaration order
let mike = uri!(person: "Mike Smith", 28);
assert_eq!(mike.to_string(), "/person/Mike%20Smith?age=28");
// with named parameters, order irrelevant
let mike = uri!(person: name = "Mike", age = 28);
let mike = uri!(person: age = 28, name = "Mike");
assert_eq!(mike.to_string(), "/person/Mike?age=28");
// with a specific mount-point
let mike = uri!("/api", person: name = "Mike", age = 28);
assert_eq!(mike.to_string(), "/api/person/Mike?age=28");
// with optional (defaultable) query parameters ignored
let mike = uri!(person: "Mike", _);
let mike = uri!(person: name = "Mike", age = _);
assert_eq!(mike.to_string(), "/person/Mike");
```
Rocket informs you of any mismatched parameters at compile-time:
```rust,ignore
error: person route uri expects 2 parameters but 1 was supplied
--> examples/uri/src/main.rs:9:29
|
9 | uri!(person: "Mike Smith");
| ^^^^^^^^^^^^
|
= note: expected parameters: name: String, age: Option<u8>
```
Rocket also informs you of any type errors at compile-time:
```rust,ignore
error: the trait bound u8: FromUriParam<Query, &str> is not satisfied
--> examples/uri/src/main.rs:9:35
|
9 | uri!(person: age = "10", name = "Mike");
| ^^^^ FromUriParam<Query, &str> is not implemented for u8
|
```
We recommend that you use `uri!` exclusively when constructing URIs to your
routes.
### Ignorables
As illustrated in the previous above, query parameters can be ignored using `_`
in place of an expression in a `uri!` invocation. The corresponding type in the
route URI must implement [`Ignorable`]. Ignored parameters are not interpolated
into the resulting `Origin`. Path parameters are not ignorable.
### Deriving `UriDisplay`
The `UriDisplay` trait can be derived for custom types. For types that appear in
the path part of a URI, derive using [`UriDisplayPath`]; for types that appear
in the query part of a URI, derive using [`UriDisplayQuery`].
As an example, consider the following form structure and route:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::http::RawStr;
use rocket::request::Form;
#[derive(FromForm, UriDisplayQuery)]
struct UserDetails<'r> {
age: Option<usize>,
nickname: &'r RawStr,
}
#[post("/user/<id>?<details..>")]
fn add_user(id: usize, details: Form<UserDetails>) { /* .. */ }
```
By deriving using `UriDisplayQuery`, an implementation of `UriDisplay<Query>` is
automatically generated, allowing for URIs to `add_user` to be generated using
`uri!`:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# use rocket::http::RawStr;
# use rocket::request::Form;
# #[derive(FromForm, UriDisplayQuery)]
# struct UserDetails<'r> {
# age: Option<usize>,
# nickname: &'r RawStr,
# }
# #[post("/user/<id>?<details..>")]
# fn add_user(id: usize, details: Form<UserDetails>) { /* .. */ }
let link = uri!(add_user: 120, UserDetails { age: Some(20), nickname: "Bob".into() });
assert_eq!(link.to_string(), "/user/120?age=20&nickname=Bob");
```
### Typed URI Parts
The [`UriPart`] trait categorizes types that mark a part of the URI as either a
[`Path`] or a [`Query`]. Said another way, types that implement `UriPart` are
marker types that represent a part of a URI at the type-level. Traits such as
[`UriDisplay`] and [`FromUriParam`] bound a generic parameter by `UriPart`: `P:
UriPart`. This creates two instances of each trait: `UriDisplay<Query>` and
`UriDisplay<Path>`, and `FromUriParam<Query>` and `FromUriParam<Path>`.
As the names might imply, the `Path` version of the traits is used when
displaying parameters in the path part of the URI while the `Query` version is
used when displaying parameters in the query part of the URI. These distinct
versions of the traits exist exactly to differentiate, at the type-level, where
in the URI a value is to be written to, allowing for type safety in the face of
differences between the two locations. For example, while it is valid to use a
value of `None` in the query part, omitting the parameter entirely, doing so is
_not_ valid in the path part. By differentiating in the type system, both of
these conditions can be enforced appropriately through distinct implementations
of `FromUriParam<Path>` and `FromUriParam<Query>`.
### Conversions
[`FromUriParam`] is used to perform a conversion for each value passed to `uri!`
before it is displayed with `UriDisplay`. If a `FromUriParam<P, S>`
implementation exists for a type `T` for part URI part `P`, then a value of type
`S` can be used in `uri!` macro for a route URI parameter declared with a type
of `T` in part `P`. For example, the following implementation, provided by
Rocket, allows an `&str` to be used in a `uri!` invocation for route URI
parameters declared as `String`:
```rust
# use rocket::http::uri::{FromUriParam, UriPart};
# struct S;
# type String = S;
impl<'a, P: UriPart> FromUriParam<P, &'a str> for String {
type Target = &'a str;
# fn from_uri_param(s: &'a str) -> Self::Target { "hi" }
}
```
Other conversions to be aware of are:
* `&str` to `RawStr`
* `String` to `&str`
* `String` to `RawStr`
* `T` to `Option<T>`
* `T` to `Result<T, E>`
* `T` to `Form<T>`
* `&str` to `&Path`
* `&str` to `PathBuf`
Conversions _nest_. For instance, a value of type `T` can be supplied when a
value of type `Option<Form<T>>` is expected:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# use rocket::http::RawStr;
# use rocket::request::Form;
# #[derive(FromForm, UriDisplayQuery)]
# struct UserDetails<'r> { age: Option<usize>, nickname: &'r RawStr, }
#[get("/person/<id>?<details..>")]
fn person(id: usize, details: Option<Form<UserDetails>>) { /* .. */ }
uri!(person: id = 100, details = UserDetails { age: Some(20), nickname: "Bob".into() });
```
See the [`FromUriParam`] documentation for further details.
[`Origin`]: @api/rocket/http/uri/struct.Origin.html
[`UriPart`]: @api/rocket/http/uri/trait.UriPart.html
[`Uri`]: @api/rocket/http/uri/enum.Uri.html
[`Redirect::to()`]: @api/rocket/response/struct.Redirect.html#method.to
[`uri!`]: @api/rocket_codegen/macro.uri.html
[`UriDisplay`]: @api/rocket/http/uri/trait.UriDisplay.html
[`FromUriParam`]: @api/rocket/http/uri/trait.FromUriParam.html
[`Path`]: @api/rocket/http/uri/enum.Path.html
[`Query`]: @api/rocket/http/uri/enum.Query.html
[`Ignorable`]: @api/rocket/http/uri/trait.Ignorable.html
[`UriDisplayPath`]: @api/rocket_codegen/derive.UriDisplayPath.html
[`UriDisplayQuery`]: @api/rocket_codegen/derive.UriDisplayQuery.html