mirror of https://github.com/rwf2/Rocket.git
751 lines
26 KiB
Markdown
751 lines
26 KiB
Markdown
# Responses
|
|
|
|
You may have noticed that the return type of a handler appears to be arbitrary,
|
|
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`.
|
|
|
|
[`Responder`]: @api/rocket/response/trait.Responder.html
|
|
|
|
## Responder
|
|
|
|
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.
|
|
|
|
[`Response`]: @api/rocket/response/struct.Response.html
|
|
|
|
### Wrapping
|
|
|
|
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);
|
|
```
|
|
|
|
A wrapping responder modifies the response returned by `R` before responding
|
|
with that same response. For instance, Rocket provides `Responder`s in the
|
|
[`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() {}
|
|
|
|
use rocket::response::status;
|
|
|
|
#[post("/<id>")]
|
|
fn new(id: usize) -> status::Accepted<String> {
|
|
status::Accepted(Some(format!("id: '{}'", id)))
|
|
}
|
|
```
|
|
|
|
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, as well as setting the status code to an
|
|
arbitrary one like `418 I'm a teapot`, combine [`content::RawJson`] with
|
|
[`status::Custom`]:
|
|
|
|
```rust
|
|
# #[macro_use] extern crate rocket;
|
|
use rocket::http::Status;
|
|
use rocket::response::{content, status};
|
|
|
|
#[get("/")]
|
|
fn json() -> status::Custom<content::RawJson<&'static str>> {
|
|
status::Custom(Status::ImATeapot, content::RawJson("{ \"hi\": \"world\" }"))
|
|
}
|
|
```
|
|
|
|
! warning: This is _not_ the same as [`serde::json::Json`]!
|
|
|
|
The built-in `(Status, R)` and `(ContentType, R)` responders, where `R:
|
|
Responder`, also override the `Status` and `Content-Type` of responses,
|
|
respectively:
|
|
|
|
```rust
|
|
# #[macro_use] extern crate rocket;
|
|
use rocket::http::{Status, ContentType};
|
|
|
|
#[get("/")]
|
|
fn json() -> (Status, (ContentType, &'static str)) {
|
|
(Status::ImATeapot, (ContentType::JSON, "{ \"hi\": \"world\" }"))
|
|
}
|
|
```
|
|
|
|
For pithy reusability, it is advisable to derive a [custom responder]:
|
|
|
|
```rust
|
|
# #[macro_use] extern crate rocket;
|
|
|
|
#[derive(Responder)]
|
|
#[response(status = 418, content_type = "json")]
|
|
struct RawTeapotJson(&'static str);
|
|
|
|
#[get("/")]
|
|
fn json() -> RawTeapotJson {
|
|
RawTeapotJson("{ \"hi\": \"world\" }")
|
|
}
|
|
```
|
|
|
|
[`Accepted`]: @api/rocket/response/status/struct.Accepted.html
|
|
[`content::Json`]: @api/rocket/response/content/struct.Json.html
|
|
[`status::Custom`]: @api/rocket/response/status/struct.Custom.html
|
|
[`serde::json::Json`]: @api/rocket/serde/json/struct.Json.html
|
|
[custom responder]: #custom-responders
|
|
|
|
### Errors
|
|
|
|
Responders may fail instead of generating a response by returning an `Err` with
|
|
a status code. When this happens, Rocket forwards the request to the [error
|
|
catcher](../requests/#error-catchers) for that 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
|
|
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. |
|
|
|
|
[`Status`]: https://api.rocket.rs/v0.5-rc/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/derive.Responder.html
|
|
|
|
## Implementations
|
|
|
|
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.
|
|
|
|
### Strings
|
|
|
|
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);
|
|
#[rocket::async_trait]
|
|
impl<'r> Responder<'r, 'static> for String {
|
|
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
|
|
Response::build()
|
|
.header(ContentType::Plain)
|
|
# /*
|
|
.sized_body(self.len(), Cursor::new(self))
|
|
# */
|
|
# .sized_body(self.0.len(), Cursor::new(self.0))
|
|
.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() {}
|
|
#[get("/string")]
|
|
fn handler() -> &'static str {
|
|
"Hello there! I'm a string!"
|
|
}
|
|
```
|
|
|
|
### `Option`
|
|
|
|
`Option` is a _wrapping_ responder: an `Option<T>` can only be returned when `T`
|
|
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::fs::NamedFile;
|
|
|
|
#[get("/<file..>")]
|
|
async fn files(file: PathBuf) -> Option<NamedFile> {
|
|
NamedFile::open(Path::new("static/").join(file)).await.ok()
|
|
}
|
|
```
|
|
|
|
### `Result`
|
|
|
|
`Result` is another _wrapping_ responder: a `Result<T, E>` can only be returned
|
|
when `T` implements `Responder` and `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 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::fs::NamedFile;
|
|
use rocket::response::status::NotFound;
|
|
|
|
#[get("/<file..>")]
|
|
async fn files(file: PathBuf) -> Result<NamedFile, NotFound<String>> {
|
|
let path = Path::new("static/").join(file);
|
|
NamedFile::open(&path).await.map_err(|e| NotFound(e.to_string()))
|
|
}
|
|
```
|
|
|
|
## Rocket Responders
|
|
|
|
Some of Rocket's best features are implemented through responders. Among these
|
|
are:
|
|
|
|
* [`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.
|
|
* [`content`] - Contains types that override the Content-Type a response.
|
|
* [`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.
|
|
|
|
[`status`]: @api/rocket/response/status/
|
|
[`content`]: @api/rocket/response/content/
|
|
[`response`]: @api/rocket/response/
|
|
[`NamedFile`]: @api/rocket/fs/struct.NamedFile.html
|
|
[`Redirect`]: @api/rocket/response/struct.Redirect.html
|
|
[`Flash`]: @api/rocket/response/struct.Flash.html
|
|
[`MsgPack`]: @api/rocket/serde/msgpack/struct.MsgPack.html
|
|
[`Template`]: @api/rocket_dyn_templates/struct.Template.html
|
|
|
|
### Async Streams
|
|
|
|
The [`stream`] responders allow serving potentially infinite [async `Stream`]s.
|
|
A stream can be created from any async `Stream` or `AsyncRead` type, or via
|
|
generator syntax using the [`stream!`] macro and its typed equivalents. Streams
|
|
are the building blocks for unidirectional real-time communication. For
|
|
instance, the [`chat` example] uses an [`EventStream`] to implement a real-time,
|
|
multi-room chat application using Server-Sent Events (SSE).
|
|
|
|
The simplest version creates a [`ReaderStream`] from a single `AsyncRead` type.
|
|
For example, to stream from a TCP connection, we might write:
|
|
|
|
```rust
|
|
# use rocket::*;
|
|
use std::io;
|
|
use std::net::SocketAddr;
|
|
|
|
use rocket::tokio::net::TcpStream;
|
|
use rocket::response::stream::ReaderStream;
|
|
|
|
#[get("/stream")]
|
|
async fn stream() -> io::Result<ReaderStream![TcpStream]> {
|
|
let addr = SocketAddr::from(([127, 0, 0, 1], 9999));
|
|
let stream = TcpStream::connect(addr).await?;
|
|
Ok(ReaderStream::one(stream))
|
|
}
|
|
```
|
|
|
|
Streams can also be created using generator syntax. The following example
|
|
returns an infinite [`TextStream`] that produces one `"hello"` every second:
|
|
|
|
```rust
|
|
# use rocket::get;
|
|
use rocket::tokio::time::{Duration, interval};
|
|
use rocket::response::stream::TextStream;
|
|
|
|
/// Produce an infinite series of `"hello"`s, one per second.
|
|
#[get("/infinite-hellos")]
|
|
fn hello() -> TextStream![&'static str] {
|
|
TextStream! {
|
|
let mut interval = interval(Duration::from_secs(1));
|
|
loop {
|
|
yield "hello";
|
|
interval.tick().await;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
See the [`stream`] docs for full details on creating streams including notes on
|
|
how to detect and handle graceful shutdown requests.
|
|
|
|
[`stream`]: @api/rocket/response/stream/index.html
|
|
[`stream!`]: @api/rocket/response/stream/macro.stream.html
|
|
[async `Stream`]: https://docs.rs/futures/0.3/futures/stream/trait.Stream.html
|
|
[`ReaderStream`]: @api/rocket/response/stream/struct.ReaderStream.html
|
|
[`TextStream`]: @api/rocket/response/stream/struct.TextStream.html
|
|
[`EventStream`]: @api/rocket/response/stream/struct.EventStream.html
|
|
[`chat` example]: @example/chat
|
|
|
|
### JSON
|
|
|
|
The [`Json`] responder in allows you to easily respond with well-formed JSON
|
|
data: simply return a value of type `Json<T>` where `T` is the 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
|
|
write:
|
|
|
|
```rust
|
|
# #[macro_use] extern crate rocket;
|
|
|
|
use rocket::serde::{Serialize, json::Json};
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(crate = "rocket::serde")]
|
|
struct Task { /* .. */ }
|
|
|
|
#[get("/todo")]
|
|
fn todo() -> Json<Task> {
|
|
Json(Task { /* .. */ })
|
|
}
|
|
```
|
|
|
|
The `Json` type serializes the structure into JSON, sets the Content-Type to
|
|
JSON, and emits the serialized data in a fixed-sized body. If serialization
|
|
fails, a **500 - Internal Server Error** is returned.
|
|
|
|
The [serialization example] provides further illustration.
|
|
|
|
[`Json`]: @api/rocket/serde/json/struct.Json.html
|
|
[`Serialize`]: https://docs.serde.rs/serde/trait.Serialize.html
|
|
[`serde`]: https://serde.rs
|
|
[serialization example]: @example/serialization
|
|
|
|
## Templates
|
|
|
|
Rocket has first-class templating support that works largely through a
|
|
[`Template`] responder in the `rocket_dyn_templates` contrib library. To render
|
|
a template named "index", for instance, you might return a value of type
|
|
`Template` as follows:
|
|
|
|
```rust
|
|
# #[macro_use] extern crate rocket;
|
|
# fn main() {}
|
|
|
|
use rocket_dyn_templates::Template;
|
|
|
|
#[get("/")]
|
|
fn index() -> Template {
|
|
# /*
|
|
let context = /* object-like value */;
|
|
# */ let context = ();
|
|
Template::render("index", &context)
|
|
}
|
|
```
|
|
|
|
Templates are rendered with the `render` method. The method takes in the name of
|
|
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.
|
|
|
|
You can also use [`context!`] to create ad-hoc templating contexts without
|
|
defining a new type:
|
|
|
|
```rust
|
|
# #[macro_use] extern crate rocket;
|
|
# #[macro_use] extern crate rocket_dyn_templates;
|
|
# fn main() {}
|
|
|
|
use rocket_dyn_templates::Template;
|
|
|
|
#[get("/")]
|
|
fn index() -> Template {
|
|
Template::render("index", context! {
|
|
foo: 123,
|
|
})
|
|
}
|
|
```
|
|
|
|
For a template to be renderable, it must first be registered. The `Template`
|
|
fairing automatically registers all discoverable templates when attached. The
|
|
[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
|
|
# #[macro_use] extern crate rocket;
|
|
|
|
use rocket_dyn_templates::Template;
|
|
|
|
#[launch]
|
|
fn rocket() -> _ {
|
|
rocket::build()
|
|
.mount("/", routes![/* .. */])
|
|
.attach(Template::fairing())
|
|
}
|
|
```
|
|
|
|
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`.
|
|
|
|
[`context`]: @api/rocket_dyn_templates/macro.context.html
|
|
|
|
### Live Reloading
|
|
|
|
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.
|
|
The [templating example](@example/templating) uses both Tera and Handlebars
|
|
templating to implement the same application.
|
|
|
|
[configurable]: ../configuration
|
|
|
|
## Typed URIs
|
|
|
|
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.
|
|
|
|
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("/<id>/<name>?<age>")]
|
|
fn person(id: Option<usize>, name: &str, age: Option<u8>) { /* .. */ }
|
|
```
|
|
|
|
URIs to `person` can be created as follows:
|
|
|
|
```rust
|
|
# #[macro_use] extern crate rocket;
|
|
|
|
# #[get("/<id>/<name>?<age>")]
|
|
# fn person(id: Option<usize>, name: &str, age: Option<u8>) { /* .. */ }
|
|
|
|
// with unnamed parameters, in route path declaration order
|
|
let mike = uri!(person(101, "Mike Smith", Some(28)));
|
|
assert_eq!(mike.to_string(), "/101/Mike%20Smith?age=28");
|
|
|
|
// with named parameters, order irrelevant
|
|
let mike = uri!(person(name = "Mike", id = 101, age = Some(28)));
|
|
assert_eq!(mike.to_string(), "/101/Mike?age=28");
|
|
let mike = uri!(person(id = 101, age = Some(28), name = "Mike"));
|
|
assert_eq!(mike.to_string(), "/101/Mike?age=28");
|
|
|
|
// with a specific mount-point
|
|
let mike = uri!("/api", person(id = 101, name = "Mike", age = Some(28)));
|
|
assert_eq!(mike.to_string(), "/api/101/Mike?age=28");
|
|
|
|
// with optional (defaultable) query parameters ignored
|
|
let mike = uri!(person(101, "Mike", _));
|
|
assert_eq!(mike.to_string(), "/101/Mike");
|
|
let mike = uri!(person(id = 101, name = "Mike", age = _));
|
|
assert_eq!(mike.to_string(), "/101/Mike");
|
|
```
|
|
|
|
Rocket informs you of any mismatched parameters at compile-time:
|
|
|
|
```rust,ignore
|
|
error: `person` route uri expects 3 parameters but 1 was supplied
|
|
--> examples/uri/main.rs:7:26
|
|
|
|
|
7 | let x = uri!(person("Mike Smith"));
|
|
| ^^^^^^^^^^^^
|
|
|
|
|
= note: expected parameters: id: Option <usize>, name: &str, age: Option <u8>
|
|
```
|
|
|
|
Rocket also informs you of any type errors at compile-time:
|
|
|
|
```rust,ignore
|
|
--> examples/uri/src/main.rs:7:31
|
|
|
|
|
7 | let x = uri!(person(id = "10", name = "Mike Smith", age = Some(10)));
|
|
| ^^^^ `FromUriParam<Path, &str>` is not implemented for `usize`
|
|
```
|
|
|
|
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
|
|
# #[macro_use] extern crate rocket;
|
|
# fn main() {}
|
|
|
|
use rocket::form::Form;
|
|
|
|
#[derive(FromForm, UriDisplayQuery)]
|
|
struct UserDetails<'r> {
|
|
age: Option<usize>,
|
|
nickname: &'r str,
|
|
}
|
|
|
|
#[post("/user/<id>?<details..>")]
|
|
fn add_user(id: usize, details: 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
|
|
# #[macro_use] extern crate rocket;
|
|
|
|
# use rocket::form::Form;
|
|
|
|
# #[derive(FromForm, UriDisplayQuery)]
|
|
# struct UserDetails<'r> {
|
|
# age: Option<usize>,
|
|
# nickname: &'r str,
|
|
# }
|
|
|
|
# #[post("/user/<id>?<details..>")]
|
|
# fn add_user(id: usize, details: 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 [`Part`] trait categorizes types that mark a part of the URI as either a
|
|
[`Path`] or a [`Query`]. Said another way, types that implement `Part` 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 `Part`: `P:
|
|
Part`. 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>`.
|
|
|
|
This division has an effect on how the `uri!` macro can be invoked. In query
|
|
parts, for a route type of `Option<T>`, you _must_ supply a type of `Option`,
|
|
`Result`, or an ignored `_` to the `uri!` invocation. By contrast, you _cannot_
|
|
supply such a type in the path part. This ensures that a valid URI is _always_
|
|
generated.
|
|
|
|
```rust
|
|
# #[macro_use] extern crate rocket;
|
|
|
|
#[get("/<id>/<name>?<age>")]
|
|
fn person(id: Option<usize>, name: &str, age: Option<u8>) { /* .. */ }
|
|
|
|
// Note that `id` is `Option<usize>` in the route, but `id` in `uri!` _cannot_
|
|
// be an `Option`. `age`, on the other hand, _must_ be an `Option` (or `Result`
|
|
// or `_`) as its in the query part and is allowed to be ignored.
|
|
let mike = uri!(person(id = 101, name = "Mike", age = Some(28)));
|
|
assert_eq!(mike.to_string(), "/101/Mike?age=28");
|
|
```
|
|
|
|
### Conversions
|
|
|
|
[`FromUriParam`] is used to perform a conversion for each value passed to `uri!`
|
|
before it is displayed with `UriDisplay`. If a `T: 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::fmt::{FromUriParam, Part};
|
|
# struct S;
|
|
# type String = S;
|
|
impl<'a, P: Part> 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:
|
|
|
|
* `&T` to `T`
|
|
* `&mut T` to `T`
|
|
* `String` to `&str`
|
|
* `&str` to `&Path`
|
|
* `&str` to `PathBuf`
|
|
* `T` to `Form<T>`
|
|
|
|
The following conversions only apply to path parts:
|
|
|
|
* `T` to `Option<T>`
|
|
* `T` to `Result<T, E>`
|
|
|
|
The following conversions are implemented only in query parts:
|
|
|
|
* `Option<T>` to `Result<T, E>` (for any `E`)
|
|
* `Result<T, E>` to `Option<T>` (for any `E`)
|
|
|
|
Conversions are transitive. That is, a conversion from `A -> B` and a conversion
|
|
`B -> C` implies a conversion from `A -> C`. For instance, a value of type
|
|
`&str` can be supplied when a value of type `Option<PathBuf>` is expected:
|
|
|
|
```rust
|
|
# #[macro_use] extern crate rocket;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
#[get("/person/<id>/<details..>")]
|
|
fn person(id: usize, details: Option<PathBuf>) { /* .. */ }
|
|
|
|
uri!(person(id = 100, details = "a/b/c"));
|
|
```
|
|
|
|
See the [`FromUriParam`] documentation for further details.
|
|
|
|
[`Origin`]: @api/rocket/http/uri/struct.Origin.html
|
|
[`Part`]: @api/rocket/http/uri/fmt/trait.Part.html
|
|
[`Uri`]: @api/rocket/http/uri/enum.Uri.html
|
|
[`Redirect::to()`]: @api/rocket/response/struct.Redirect.html#method.to
|
|
[`uri!`]: @api/rocket/macro.uri.html
|
|
[`UriDisplay`]: @api/rocket/http/uri/fmt/trait.UriDisplay.html
|
|
[`FromUriParam`]: @api/rocket/http/uri/fmt/trait.FromUriParam.html
|
|
[`Path`]: @api/rocket/http/uri/fmt/enum.Path.html
|
|
[`Query`]: @api/rocket/http/uri/fmt/enum.Query.html
|
|
[`Ignorable`]: @api/rocket/http/uri/fmt/trait.Ignorable.html
|
|
[`UriDisplayPath`]: @api/rocket/derive.UriDisplayPath.html
|
|
[`UriDisplayQuery`]: @api/rocket/derive.UriDisplayQuery.html
|