Test all guide code examples.

Every code example is now fully runnable and testable. As a result, all
examples are now tested and include imports. Relevant imports are shown
by default. Code examples can be expanded to show all imports.

Fixes #432.
This commit is contained in:
Sergio Benitez 2020-02-15 03:43:47 -08:00
parent ee1a9903b6
commit 95c981de79
17 changed files with 779 additions and 125 deletions

View File

@ -8,6 +8,7 @@ members = [
"core/http/",
"contrib/lib",
"contrib/codegen",
"site/tests",
"examples/cookies",
"examples/errors",
"examples/form_validation",

View File

@ -20,6 +20,7 @@ indexmap = "1.0"
quote = "1.0"
rocket_http = { version = "0.5.0-dev", path = "../http/" }
devise = { git = "https://github.com/SergioBenitez/Devise.git", rev = "e58b3ac9a" }
glob = "0.3"
[build-dependencies]
yansi = "0.5"

View File

@ -8,6 +8,7 @@ use crate::{ROUTE_STRUCT_PREFIX, CATCH_STRUCT_PREFIX};
mod uri;
mod uri_parsing;
mod test_guide;
pub fn prefix_last_segment(path: &mut Path, prefix: &str) {
let mut last_seg = path.segments.last_mut().expect("syn::Path has segments");
@ -64,3 +65,9 @@ pub fn uri_internal_macro(input: TokenStream) -> TokenStream {
.map_err(|diag| diag.emit())
.unwrap_or_else(|_| quote!(()).into())
}
pub fn guide_tests_internal(input: TokenStream) -> TokenStream {
test_guide::_macro(input)
.map_err(|diag| diag.emit())
.unwrap_or_else(|_| quote!(()).into())
}

View File

@ -0,0 +1,45 @@
use std::path::Path;
use std::error::Error;
use proc_macro::TokenStream;
use devise::{syn::{self, Ident, LitStr}, Result};
use crate::syn_ext::syn_to_diag;
use crate::proc_macro2::TokenStream as TokenStream2;
pub fn _macro(input: TokenStream) -> Result<TokenStream> {
let root = syn::parse::<LitStr>(input.into()).map_err(syn_to_diag)?;
let modules = entry_to_modules(&root)
.map_err(|e| root.span().unstable().error(format!("failed to read: {}", e)))?;
Ok(quote_spanned!(root.span() =>
#[allow(dead_code)]
#[allow(non_camel_case_types)]
mod test_site_guide { #(#modules)* }
).into())
}
fn entry_to_modules(pat: &LitStr) -> std::result::Result<Vec<TokenStream2>, Box<dyn Error>> {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("MANIFEST_DIR");
let full_pat = Path::new(&manifest_dir).join(&pat.value()).display().to_string();
let mut modules = vec![];
for path in glob::glob(&full_pat).map_err(|e| Box::new(e))? {
let path = path.map_err(|e| Box::new(e))?;
let name = path.file_name()
.and_then(|f| f.to_str())
.map(|name| name.trim_matches(|c| char::is_numeric(c) || c == '-')
.replace('-', "_")
.replace('.', "_"))
.ok_or_else(|| "invalid file name".to_string())?;
let ident = Ident::new(&name, pat.span());
let full_path = Path::new(&manifest_dir).join(&path).display().to_string();
modules.push(quote_spanned!(pat.span() =>
#[doc(include = #full_path)]
struct #ident;
))
}
Ok(modules)
}

View File

@ -990,3 +990,9 @@ pub fn uri(input: TokenStream) -> TokenStream {
pub fn rocket_internal_uri(input: TokenStream) -> TokenStream {
emit!(bang::uri_internal_macro(input))
}
#[doc(hidden)]
#[proc_macro]
pub fn rocket_internal_guide_tests(input: TokenStream) -> TokenStream {
emit!(bang::guide_tests_internal(input))
}

14
core/lib/tests/guide.rs Normal file
View File

@ -0,0 +1,14 @@
#![feature(proc_macro_hygiene)]
#![feature(external_doc)]
#[allow(dead_code)]
mod test_guide {
#[doc(include = "../../../site/guide/2-getting-started.md")]
pub struct GuideGettingStart;
/// ```rust
/// assert_eq!(0, 1);
/// ```
struct Foo;
}

View File

@ -34,7 +34,7 @@ The finished product is composed of the following routes:
Let's get started! First, create a fresh Cargo binary project named
`rocket-pastebin`:
```rust
```sh
cargo new --bin rocket-pastebin
cd rocket-pastebin
```
@ -55,7 +55,9 @@ And finally, create a skeleton Rocket application to work off of in
#[macro_use] extern crate rocket;
fn main() {
# if false {
rocket::ignite().launch();
# }
}
```
@ -77,6 +79,8 @@ of the form `GET /`. We declare the route and its handler by adding the `index`
function below to `src/main.rs`:
```rust
# #[macro_use] extern crate rocket;
#[get("/")]
fn index() -> &'static str {
"
@ -105,8 +109,14 @@ Remember that routes first need to be mounted before Rocket dispatches requests
to them. To mount the `index` route, modify the main function so that it reads:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# #[get("/")] fn index() { }
fn main() {
# if false {
rocket::ignite().mount("/", routes![index]).launch();
# }
}
```
@ -124,7 +134,7 @@ at a time, beginning with generating IDs.
### Unique IDs
Generating a unique and useful ID is an interesting topic, but it is outside the
scope of this tutorial. Instead, we simply provide the code for a `PasteID`
scope of this tutorial. Instead, we simply provide the code for a `PasteId`
structure that represents a _probably_ unique ID. Read through the code, then
copy/paste it into a new file named `paste_id.rs` in the `src/` directory:
@ -138,25 +148,25 @@ use rand::{self, Rng};
const BASE62: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/// A _probably_ unique paste ID.
pub struct PasteID<'a>(Cow<'a, str>);
pub struct PasteId<'a>(Cow<'a, str>);
impl<'a> PasteID<'a> {
impl<'a> PasteId<'a> {
/// Generate a _probably_ unique ID with `size` characters. For readability,
/// the characters used are from the sets [0-9], [A-Z], [a-z]. The
/// probability of a collision depends on the value of `size` and the number
/// of IDs generated thus far.
pub fn new(size: usize) -> PasteID<'static> {
pub fn new(size: usize) -> PasteId<'static> {
let mut id = String::with_capacity(size);
let mut rng = rand::thread_rng();
for _ in 0..size {
id.push(BASE62[rng.gen::<usize>() % 62] as char);
}
PasteID(Cow::Owned(id))
PasteId(Cow::Owned(id))
}
}
impl<'a> fmt::Display for PasteID<'a> {
impl<'a> fmt::Display for PasteId<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
@ -166,11 +176,11 @@ impl<'a> fmt::Display for PasteID<'a> {
Then, in `src/main.rs`, add the following after `extern crate rocket`:
```rust
extern crate rand;
# /*
mod paste_id;
# */ mod paste_id { pub struct PasteId; }
use paste_id::PasteID;
use paste_id::PasteId;
```
Finally, add a dependency for the `rand` crate to the `Cargo.toml` file:
@ -223,24 +233,48 @@ you should attempt to write the route yourself. Here's a hint: a possible route
and handler signature look like this:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::Data;
use rocket::response::Debug;
#[post("/", data = "<paste>")]
fn upload(paste: Data) -> io::Result<String>
fn upload(paste: Data) -> Result<String, Debug<std::io::Error>> {
# unimplemented!()
/* .. */
}
```
Your code should:
1. Create a new `PasteID` of a length of your choosing.
2. Construct a filename inside `upload/` given the `PasteID`.
1. Create a new `PasteId` of a length of your choosing.
2. Construct a filename inside `upload/` given the `PasteId`.
3. Stream the `Data` to the file with the constructed filename.
4. Construct a URL given the `PasteID`.
4. Construct a URL given the `PasteId`.
5. Return the URL to the client.
Here's our version (in `src/main.rs`):
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use std::fmt;
# struct PasteId;
# impl PasteId { fn new(n: usize) -> Self { PasteId } }
# impl fmt::Display for PasteId {
# fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Ok(()) }
# }
use std::path::Path;
use rocket::Data;
use rocket::response::Debug;
#[post("/", data = "<paste>")]
fn upload(paste: Data) -> io::Result<String> {
let id = PasteID::new(3);
fn upload(paste: Data) -> Result<String, Debug<std::io::Error>> {
let id = PasteId::new(3);
let filename = format!("upload/{id}", id = id);
let url = format!("{host}/{id}\n", host = "http://localhost:8000", id = id);
@ -253,8 +287,16 @@ fn upload(paste: Data) -> io::Result<String> {
Ensure that the route is mounted at the root path:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# #[get("/")] fn index() {}
# #[post("/")] fn upload() {}
fn main() {
# if false {
rocket::ignite().mount("/", routes![index, upload]).launch();
# }
}
```
@ -293,6 +335,8 @@ as a **404** error, which is exactly what we want to return when the requested
paste doesn't exist.
```rust
# #[macro_use] extern crate rocket;
use std::fs::File;
use rocket::http::RawStr;
@ -306,8 +350,17 @@ fn retrieve(id: &RawStr) -> Option<File> {
Make sure that the route is mounted at the root path:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# #[get("/")] fn index() {}
# #[post("/")] fn upload() {}
# #[get("/<id>")] fn retrieve(id: String) {}
fn main() {
# if false {
rocket::ignite().mount("/", routes![index, upload, retrieve]).launch();
# }
}
```
@ -327,13 +380,19 @@ provides the tools to prevent this and other kinds of attacks from happening.
To prevent the attack, we need to _validate_ `id` before we use it. Since the
`id` is a dynamic parameter, we can use Rocket's
[FromParam](@api/rocket/request/trait.FromParam.html) trait to
implement the validation and ensure that the `id` is a valid `PasteID` before
using it. We do this by implementing `FromParam` for `PasteID` in
implement the validation and ensure that the `id` is a valid `PasteId` before
using it. We do this by implementing `FromParam` for `PasteId` in
`src/paste_id.rs`, as below:
```rust
use std::borrow::Cow;
use rocket::http::RawStr;
use rocket::request::FromParam;
/// A _probably_ unique paste ID.
pub struct PasteId<'a>(Cow<'a, str>);
/// Returns `true` if `id` is a valid paste ID and `false` otherwise.
fn valid_id(id: &str) -> bool {
id.chars().all(|c| {
@ -343,27 +402,33 @@ fn valid_id(id: &str) -> bool {
})
}
/// Returns an instance of `PasteID` if the path segment is a valid ID.
/// Returns an instance of `PasteId` if the path segment is a valid ID.
/// Otherwise returns the invalid ID as the `Err` value.
impl<'a> FromParam<'a> for PasteID<'a> {
impl<'a> FromParam<'a> for PasteId<'a> {
type Error = &'a RawStr;
fn from_param(param: &'a RawStr) -> Result<PasteID<'a>, &'a RawStr> {
fn from_param(param: &'a RawStr) -> Result<PasteId<'a>, &'a RawStr> {
match valid_id(param) {
true => Ok(PasteID(Cow::Borrowed(param))),
true => Ok(PasteId(Cow::Borrowed(param))),
false => Err(param)
}
}
}
```
Then, we simply need to change the type of `id` in the handler to `PasteID`.
Rocket will then ensure that `<id>` represents a valid `PasteID` before calling
Then, we simply need to change the type of `id` in the handler to `PasteId`.
Rocket will then ensure that `<id>` represents a valid `PasteId` before calling
the `retrieve` route, preventing attacks on the `retrieve` route:
```rust
# #[macro_use] extern crate rocket;
# use std::fs::File;
# type PasteId = usize;
#[get("/<id>")]
fn retrieve(id: PasteID) -> Option<File> {
fn retrieve(id: PasteId) -> Option<File> {
let filename = format!("upload/{id}", id = id);
File::open(&filename).ok()
}
@ -375,8 +440,8 @@ potentially blacklisting sensitive files as needed.
The wonderful thing about using `FromParam` and other Rocket traits is that they
centralize policies. For instance, here, we've centralized the policy for valid
`PasteID`s in dynamic parameters. At any point in the future, if other routes
are added that require a `PasteID`, no further work has to be done: simply use
`PasteId`s in dynamic parameters. At any point in the future, if other routes
are added that require a `PasteId`, no further work has to be done: simply use
the type in the signature and Rocket takes care of the rest.
## Conclusion
@ -390,7 +455,7 @@ through some of them to get a better feel for Rocket. Here are some ideas:
Accept the form at `POST /`. Use `format` and/or `rank` to specify which of
the two `POST /` routes should be called.
* Support **deletion** of pastes by adding a new `DELETE /<id>` route. Use
`PasteID` to validate `<id>`.
`PasteId` to validate `<id>`.
* **Limit the upload** to a maximum size. If the upload exceeds that size,
return a **206** partial status code. Otherwise, return a **201** created
status code.

View File

@ -48,7 +48,7 @@ cd hello-rocket
Now, add Rocket as a dependency in your `Cargo.toml`:
```
```toml
[dependencies]
rocket = "0.5.0-dev"
```
@ -67,7 +67,9 @@ fn index() -> &'static str {
}
fn main() {
# if false {
rocket::ignite().mount("/", routes![index]).launch();
# }
}
```

View File

@ -62,9 +62,12 @@ handler, with the set of parameters to match against. A complete route
declaration looks like this:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
#[get("/world")] // <- route attribute
fn world() -> &'static str { // <- request handler
"Hello, world!"
"hello, world!"
}
```
@ -79,6 +82,14 @@ constructing routes.
Before Rocket can dispatch requests to a route, the route needs to be _mounted_:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# #[get("/world")]
# fn world() -> &'static str {
# "hello, world!"
# }
fn main() {
rocket::ignite().mount("/hello", routes![world]);
}
@ -101,7 +112,10 @@ requests to `"/hello/world"` will be directed to the `world` function.
When a route is declared inside a module other than the root, you may find
yourself with unexpected errors when mounting:
```rust
```rust,compile_fail
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
mod other {
#[get("/world")]
pub fn world() -> &'static str {
@ -117,8 +131,8 @@ pub fn hello() -> &'static str {
use other::world;
fn main() {
// error[E0425]: cannot find value `static_rocket_route_info_for_world` in this scope
rocket::ignite().mount("/hello", routes![hello, world]);
// error[E0425]: cannot find value `static_rocket_route_info_for_world` in this scope
rocket::ignite().mount("/hello", routes![hello, world]);
}
```
@ -127,6 +141,12 @@ into the name of a structure generated by Rocket's code generation. The solution
is to refer to the route using a namespaced path instead:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# #[get("/")] pub fn hello() {}
# mod other { #[get("/world")] pub fn world() {} }
rocket::ignite().mount("/hello", routes![hello, other::world]);
```
@ -151,7 +171,9 @@ fn world() -> &'static str {
}
fn main() {
# if false {
rocket::ignite().mount("/hello", routes![world]).launch();
# }
}
```

View File

@ -5,8 +5,11 @@ about a request in order for the route's handler to be called. You've already
seen an example of this in action:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
#[get("/world")]
fn handler() { .. }
fn handler() { /* .. */ }
```
This route indicates that it only matches against `GET` requests to the `/world`
@ -36,7 +39,11 @@ against. For example, the following attribute will match against `POST` requests
to the root path:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
#[post("/")]
# fn handler() {}
```
The grammar for these attributes is defined formally in the
@ -68,6 +75,11 @@ names in a route's path. For example, if we want to say _Hello!_ to anything,
not just the world, we can declare a route like so:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use rocket::http::RawStr;
#[get("/hello/<name>")]
fn hello(name: &RawStr) -> String {
format!("Hello, {}!", name.as_str())
@ -87,6 +99,9 @@ the full list of provided implementations, see the [`FromParam` API docs].
Here's a more complete route to illustrate varied usage:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
#[get("/hello/<name>/<age>/<cool>")]
fn hello(name: String, age: u8, cool: bool) -> String {
if cool {
@ -130,10 +145,13 @@ As an example, the following route matches against all paths that begin with
`/page/`:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use std::path::PathBuf;
#[get("/page/<path..>")]
fn get_page(path: PathBuf) -> T { ... }
fn get_page(path: PathBuf) { /* ... */ }
```
The path after `/page/` will be available in the `path` parameter. The
@ -142,6 +160,12 @@ The path after `/page/` will be available in the `path` parameter. The
this, a safe and secure static file server can be implemented in 4 lines:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use std::path::{Path, PathBuf};
use rocket::response::NamedFile;
#[get("/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok()
@ -166,8 +190,11 @@ Let's take a closer look at the route attribute and signature pair from a
previous example:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
#[get("/hello/<name>/<age>/<cool>")]
fn hello(name: String, age: u8, cool: bool) -> String { ... }
fn hello(name: String, age: u8, cool: bool) { /* ... */ }
```
What if `cool` isn't a `bool`? Or, what if `age` isn't a `u8`? When a parameter
@ -182,19 +209,26 @@ be manually set with the `rank` attribute. To illustrate, consider the following
routes:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# use rocket::http::RawStr;
#[get("/user/<id>")]
fn user(id: usize) -> T { ... }
fn user(id: usize) { /* ... */ }
#[get("/user/<id>", rank = 2)]
fn user_int(id: isize) -> T { ... }
fn user_int(id: isize) { /* ... */ }
#[get("/user/<id>", rank = 3)]
fn user_str(id: &RawStr) -> T { ... }
fn user_str(id: &RawStr) { /* ... */ }
fn main() {
# if false {
rocket::ignite()
.mount("/", routes![user, user_int, user_str])
.launch();
# }
}
```
@ -257,6 +291,12 @@ Query segments can be declared static or dynamic in much the same way as path
segments:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# fn main() {}
# use rocket::http::RawStr;
#[get("/hello?wave&<name>")]
fn hello(name: &RawStr) -> String {
format!("Hello, {}!", name.as_str())
@ -293,8 +333,11 @@ parameter is missing in a request, `None` will be provided as the value. A
route using `Option<T>` looks as follows:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
#[get("/hello?wave&<name>")]
fn hello(name: Option<&RawStr>) -> String {
fn hello(name: Option<String>) -> String {
name.map(|name| format!("Hi, {}!", name))
.unwrap_or_else(|| "Hello!".into())
}
@ -328,6 +371,9 @@ these types allow you to use a structure with named fields to automatically
validate query/form parameters:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::request::Form;
#[derive(FromForm)]
@ -345,6 +391,12 @@ sets `id` to `100` and `user` to `User { name: "sandal", account: 400 }`. To
catch forms that fail to validate, use a type of `Option` or `Result`:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use rocket::request::Form;
# #[derive(FromForm)] struct User { name: String, account: usize, }
#[get("/item?<id>&<user..>")]
fn item(id: usize, user: Option<Form<User>>) { /* ... */ }
```
@ -374,8 +426,16 @@ For instance, the following dummy handler makes use of three request guards,
named in the route attribute.
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# fn main() {}
# type A = rocket::http::Method;
# type B = A;
# type C = A;
#[get("/<param>")]
fn index(param: isize, a: A, b: B, c: C) -> ... { ... }
fn index(param: isize, a: A, b: B, c: C) { /* ... */ }
```
Request guards always fire in left-to-right declaration order. In the example
@ -395,8 +455,13 @@ headers, you might create an `ApiKey` type that implements `FromRequest` and
then use it as a request guard:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# fn main() {}
# type ApiKey = rocket::http::Method;
#[get("/sensitive")]
fn sensitive(key: ApiKey) -> &'static str { ... }
fn sensitive(key: ApiKey) { /* .. */ }
```
You might also implement `FromRequest` for an `AdminUser` type that
@ -425,7 +490,9 @@ follows, access control violations against health records are guaranteed to be
prevented at _compile-time_:
```rust
fn health_records(user: &SuperUser) -> Records { ... }
# type Records = ();
# type SuperUser = ();
fn health_records(user: &SuperUser) -> Records { /* ... */ }
```
The reasoning is as follows:
@ -470,6 +537,19 @@ following three routes, each leading to an administrative control panel at
`/admin`:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# fn main() {}
# type Template = ();
# type AdminUser = rocket::http::Method;
# type User = rocket::http::Method;
use rocket::response::{Flash, Redirect};
#[get("/login")]
fn login() -> Template { /* .. */ }
#[get("/admin")]
fn admin_panel(admin: AdminUser) -> &'static str {
"Hello, administrator. This is the admin panel!"
@ -482,7 +562,7 @@ fn admin_panel_user(user: User) -> &'static str {
#[get("/admin", rank = 3)]
fn admin_panel_redirect() -> Redirect {
Redirect::to("/login")
Redirect::to(uri!(login))
}
```
@ -502,6 +582,8 @@ and remove cookies. Because `Cookies` is a request guard, an argument of its
type can simply be added to a handler:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::http::Cookies;
#[get("/")]
@ -536,6 +618,13 @@ methods are suffixed with `_private`. These methods are: [`get_private`],
[`add_private`], and [`remove_private`]. An example of their usage is below:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::http::{Cookie, Cookies};
use rocket::response::{Flash, Redirect};
/// Retrieve the user's ID, if any.
#[get("/user_id")]
fn user_id(mut cookies: Cookies) -> Option<String> {
@ -596,7 +685,7 @@ can be confusing to handle if it does crop up.
If this does happen, Rocket will emit messages to the console that look as
follows:
```
```text
=> Error: Multiple `Cookies` instances are active at once.
=> An instance of `Cookies` must be dropped before another can be retrieved.
=> Warning: The retrieved `Cookies` instance will be empty.
@ -609,8 +698,13 @@ due to the offending handler. A common error is to have a handler that uses a
`Cookies`, as so:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use rocket::http::Cookies;
# type Custom = rocket::http::Method;
#[get("/")]
fn bad(cookies: Cookies, custom: Custom) { .. }
fn bad(cookies: Cookies, custom: Custom) { /* .. */ }
```
Because the `cookies` guard will fire before the `custom` guard, the `custom`
@ -619,8 +713,13 @@ guard will retrieve an instance of `Cookies` when one already exists for
guards:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use rocket::http::Cookies;
# type Custom = rocket::http::Method;
#[get("/")]
fn good(custom: Custom, cookies: Cookies) { .. }
fn good(custom: Custom, cookies: Cookies) { /* .. */ }
```
When using request guards that modify cookies on-demand, such as
@ -628,6 +727,12 @@ When using request guards that modify cookies on-demand, such as
`Cookies` instance before accessing the `FlashMessage`.
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use rocket::http::Cookies;
use rocket::request::FlashMessage;
#[get("/")]
fn bad(cookies: Cookies, flash: FlashMessage) {
let msg = flash.msg();
@ -635,6 +740,12 @@ fn bad(cookies: Cookies, flash: FlashMessage) {
```
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use rocket::http::Cookies;
# use rocket::request::FlashMessage;
#[get("/")]
fn good(cookies: Cookies, flash: FlashMessage) {
std::mem::drop(cookies);
@ -657,8 +768,13 @@ When a route indicates a payload-supporting method (`PUT`, `POST`, `DELETE`, and
As an example, consider the following route:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# type User = rocket::data::Data;
#[post("/user", format = "application/json", data = "<user>")]
fn new_user(user: Json<User>) -> T { ... }
fn new_user(user: User) { /* ... */ }
```
The `format` parameter in the `post` attribute declares that only incoming
@ -669,17 +785,21 @@ for the most common `format` arguments. Instead of using the full Content-Type,
"json"`. For a full list of available shorthands, see the
[`ContentType::parse_flexible()`] documentation.
When a route indicates a non-payload-supporting method (`HEAD`, `OPTIONS`, and,
these purposes, `GET`) the `format` route parameter instructs Rocket to check
against the `Accept` header of the incoming request. Only requests where the
preferred media type in the `Accept` header matches the `format` parameter will
match to the route.
When a route indicates a non-payload-supporting method (`GET`, `HEAD`,
`OPTIONS`) the `format` route parameter instructs Rocket to check against the
`Accept` header of the incoming request. Only requests where the preferred media
type in the `Accept` header matches the `format` parameter will match to the
route.
As an example, consider the following route:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# type User = ();
#[get("/user/<id>", format = "json")]
fn user(id: usize) -> Json<User> { ... }
fn user(id: usize) -> User { /* .. */ }
```
The `format` parameter in the `get` attribute declares that only incoming
@ -698,11 +818,16 @@ an argument in the handler. The argument's type must implement the [`FromData`]
trait. It looks like this, where `T` is assumed to implement `FromData`:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# type T = rocket::data::Data;
#[post("/", data = "<input>")]
fn new(input: T) -> String { ... }
fn new(input: T) { /* .. */ }
```
Any type that implements [`FromData`] is also known as _data guard_.
Any type that implements [`FromData`] is also known as _a data guard_.
[`FromData`]: @api/rocket/data/trait.FromData.html
@ -715,6 +840,11 @@ checkbox, and `description`, a text field. You can easily handle the form
request in Rocket as follows:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::request::Form;
#[derive(FromForm)]
struct Task {
complete: bool,
@ -722,7 +852,7 @@ struct Task {
}
#[post("/todo", data = "<task>")]
fn new(task: Form<Task>) -> String { ... }
fn new(task: Form<Task>) { /* .. */ }
```
The [`Form`] type implements the `FromData` trait as long as its generic
@ -737,8 +867,14 @@ returned. As before, a forward or failure can be caught by using the `Option`
and `Result` types:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use rocket::request::Form;
# #[derive(FromForm)] struct Task { complete: bool, description: String, }
#[post("/todo", data = "<task>")]
fn new(task: Option<Form<Task>>) -> String { ... }
fn new(task: Option<Form<Task>>) { /* .. */ }
```
[`Form`]: @api/rocket/request/struct.Form.html
@ -766,11 +902,20 @@ is also required to implement `FromForm`. For instance, we can simply replace
`Form` with `LenientForm` above to get lenient parsing:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::request::LenientForm;
#[derive(FromForm)]
struct Task { .. }
struct Task {
/* .. */
# complete: bool,
# description: String,
}
#[post("/todo", data = "<task>")]
fn new(task: LenientForm<Task>) { .. }
fn new(task: LenientForm<Task>) { /* .. */ }
```
[`LenientForm`]: @api/rocket/request/struct.LenientForm.html
@ -789,6 +934,9 @@ Since `type` is a reserved keyword in Rust, it cannot be used as the name of a
field. To get around this, you can use field renaming as follows:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
#[derive(FromForm)]
struct External {
#[form(field = "type")]
@ -808,6 +956,12 @@ a field in a form structure, and implement `FromFormValue` so that it only
validates integers over that age:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::http::RawStr;
use rocket::request::FromFormValue;
struct AdultAge(usize);
impl<'v> FromFormValue<'v> for AdultAge {
@ -832,6 +986,11 @@ valid form for that structure. You can use `Option` or `Result` types for fields
to catch parse failures:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# type AdultAge = usize;
#[derive(FromForm)]
struct Person {
age: Option<AdultAge>
@ -841,6 +1000,9 @@ struct Person {
The `FromFormValue` trait can also be derived for enums with nullary fields:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
#[derive(FromFormValue)]
enum MyValue {
First,
@ -864,6 +1026,13 @@ Handling JSON data is no harder: simply use the
[`rocket_contrib`]:
```rust
# #[macro_use] extern crate rocket;
# extern crate rocket_contrib;
# fn main() {}
use serde::Deserialize;
use rocket_contrib::json::Json;
#[derive(Deserialize)]
struct Task {
description: String,
@ -871,7 +1040,7 @@ struct Task {
}
#[post("/todo", data = "<task>")]
fn new(task: Json<Task>) -> String { ... }
fn new(task: Json<Task>) { /* .. */ }
```
The only condition is that the generic type in `Json` implements the
@ -888,9 +1057,15 @@ possible via the [`Data`](@api/rocket/data/struct.Data.html)
type:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::Data;
use rocket::response::Debug;
#[post("/upload", format = "plain", data = "<data>")]
fn upload(data: Data) -> io::Result<String> {
data.stream_to_file("/tmp/upload.txt").map(|n| n.to_string())
fn upload(data: Data) -> Result<String, Debug<std::io::Error>> {
Ok(data.stream_to_file("/tmp/upload.txt").map(|n| n.to_string())?)
}
```
@ -934,14 +1109,24 @@ status code to catch. For instance, to declare a catcher for `404 Not Found`
errors, you'd write:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::Request;
#[catch(404)]
fn not_found(req: &Request) -> T { .. }
fn not_found(req: &Request) { /* .. */ }
```
As with routes, the return type (here `T`) must implement `Responder`. A
concrete implementation may look like:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use rocket::Request;
#[catch(404)]
fn not_found(req: &Request) -> String {
format!("Sorry, '{}' is not a valid path.", req.uri())
@ -955,7 +1140,15 @@ mounting a route: call the [`register()`] method with a list of catchers via the
looks like:
```rust
rocket::ignite().register(catchers![not_found])
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# use rocket::Request;
# #[catch(404)] fn not_found(req: &Request) { /* .. */ }
fn main() {
rocket::ignite().register(catchers![not_found]);
}
```
Unlike route request handlers, catchers take exactly zero or one parameter. If

View File

@ -36,6 +36,9 @@ 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>")]
@ -50,6 +53,8 @@ Content-Type of `&'static str` to JSON, you can use the [`content::Json`] type
as follows:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::response::content;
#[get("/")]
@ -85,6 +90,9 @@ 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("/")]
@ -116,12 +124,19 @@ 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: SomeHeader,
more: YetAnotherHeader,
header: ContentType,
more: Header<'static>,
#[response(ignore)]
unrelated: MyType,
}
@ -160,11 +175,24 @@ to `text/plain`. To get a taste for what such a `Responder` implementation looks
like, here's the implementation for `String`:
```rust
impl Responder<'static> for String {
fn respond_to(self, _: &Request) -> Result<Response<'static>, Status> {
# #[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> {
Response::build()
.header(ContentType::Plain)
# /*
.sized_body(Cursor::new(self))
# */
# .sized_body(Cursor::new(self.0))
.ok()
}
}
@ -174,6 +202,8 @@ 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!"
@ -193,6 +223,12 @@ known until process-time whether content exists. For example, because of
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;
#[get("/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok()
@ -212,12 +248,17 @@ 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;
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(|_| NotFound(format!("Bad path: {}", path)))
NamedFile::open(&path).map_err(|e| NotFound(e.to_string()))
}
```
@ -262,10 +303,19 @@ 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};
#[get("/stream")]
fn stream() -> io::Result<Stream<UnixStream>> {
UnixStream::connect("/path/to/my/socket").map(|s| Stream::from(s))
fn stream() -> Result<Stream<UnixStream>, Debug<std::io::Error>> {
Ok(UnixStream::connect("/path/to/my/socket").map(Stream::from)?)
}
# }
```
[`rocket_contrib`]: @api/rocket_contrib/
@ -281,13 +331,20 @@ As an example, to respond with the JSON value of a `Task` structure, we might
write:
```rust
# #[macro_use] extern crate rocket;
# #[macro_use] extern crate rocket_contrib;
# fn main() {}
use serde::Serialize;
use rocket_contrib::json::Json;
#[derive(Serialize)]
struct Task { ... }
struct Task { /* .. */ }
#[get("/todo")]
fn todo() -> Json<Task> { ... }
fn todo() -> Json<Task> {
Json(Task { /* .. */ })
}
```
The `Json` type serializes the structure into JSON, sets the Content-Type to
@ -308,9 +365,17 @@ Rocket includes built-in templating support that works largely through a
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 {
# /*
let context = /* object-like value */;
# */ let context = ();
Template::render("index", &context)
}
```
@ -327,9 +392,14 @@ 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;
fn main() {
rocket::ignite()
.mount("/", routes![...])
.mount("/", routes![ /* .. */])
.attach(Template::fairing());
}
```
@ -383,13 +453,22 @@ 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>) -> T
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");
@ -411,7 +490,7 @@ assert_eq!(mike.to_string(), "/person/Mike");
Rocket informs you of any mismatched parameters at compile-time:
```rust
```rust,ignore
error: person route uri expects 2 parameters but 1 was supplied
--> examples/uri/src/main.rs:9:29
|
@ -423,7 +502,7 @@ error: person route uri expects 2 parameters but 1 was supplied
Rocket also informs you of any type errors at compile-time:
```rust
```rust,ignore
error: the trait bound u8: FromUriParam<Query, &str> is not satisfied
--> examples/uri/src/main.rs:9:35
|
@ -451,6 +530,13 @@ 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>,
@ -458,7 +544,7 @@ struct UserDetails<'r> {
}
#[post("/user/<id>?<details..>")]
fn add_user(id: usize, details: Form<UserDetails>) { .. }
fn add_user(id: usize, details: Form<UserDetails>) { /* .. */ }
```
By deriving using `UriDisplayQuery`, an implementation of `UriDisplay<Query>` is
@ -466,8 +552,23 @@ automatically generated, allowing for URIs to `add_user` to be generated using
`uri!`:
```rust
uri!(add_user: 120, UserDetails { age: Some(20), nickname: "Bob".into() })
=> "/user/120?age=20&nickname=Bob"
# #![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
@ -492,8 +593,8 @@ of `FromUriParam<Path>` and `FromUriParam<Query>`.
### Conversions
The [`FromUriParam`] is used to perform a conversion for each value passed to
`uri!` before it is displayed with `UriDisplay`. If a `FromUriParam<P, S>`
[`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
@ -501,7 +602,13 @@ Rocket, allows an `&str` to be used in a `uri!` invocation for route URI
parameters declared as `String`:
```rust
impl<P: UriPart, 'a> FromUriParam<P, &'a str> for String { .. }
# 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:
@ -519,10 +626,19 @@ Conversions _nest_. For instance, a value of type `T` can be supplied when a
value of type `Option<Form<T>>` is expected:
```rust
#[get("/person/<id>?<details>")]
fn person(id: usize, details: Option<Form<UserDetails>>) -> T
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
uri!(person: id = 100, details = UserDetails { .. })
# 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.

View File

@ -50,6 +50,11 @@ refers to a value of a different type. For instance, to have Rocket manage both
a `HitCount` value and a `Config` value, we can write:
```rust
# use std::sync::atomic::AtomicUsize;
# struct HitCount { count: AtomicUsize }
# type Config = &'static str;
# let user_input = "input";
rocket::ignite()
.manage(HitCount { count: AtomicUsize::new(0) })
.manage(Config::from(user_input));
@ -65,6 +70,12 @@ the managed state. For example, we can retrieve and respond with the current
`HitCount` in a `count` route as follows:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use std::sync::atomic::{AtomicUsize, Ordering};
# struct HitCount { count: AtomicUsize }
use rocket::State;
#[get("/count")]
@ -77,8 +88,15 @@ fn count(hit_count: State<HitCount>) -> String {
You can retrieve more than one `State` type in a single route as well:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# struct HitCount;
# struct Config;
# use rocket::State;
#[get("/state")]
fn state(hit_count: State<HitCount>, config: State<Config>) -> T { ... }
fn state(hit_count: State<HitCount>, config: State<Config>) { /* .. */ }
```
! warning
@ -99,10 +117,25 @@ implementation. To do so, simply invoke `State<T>` as a guard using the
[`Request::guard()`] method.
```rust
fn from_request(req: &'a Request<'r>) -> request::Outcome<T, ()> {
let hit_count_state = req.guard::<State<HitCount>>()?;
let current_count = hit_count_state.count.load(Ordering::Relaxed);
...
# #[macro_use] extern crate rocket;
# fn main() {}
use rocket::State;
use rocket::request::{self, Request, FromRequest};
# use std::sync::atomic::{AtomicUsize, Ordering};
# struct T;
# struct HitCount { count: AtomicUsize }
# type ErrorType = ();
impl<'a, 'r> FromRequest<'a, 'r> for T {
type Error = ErrorType;
fn from_request(req: &'a Request<'r>) -> request::Outcome<T, Self::Error> {
let hit_count_state = try_outcome!(req.guard::<State<HitCount>>());
let current_count = hit_count_state.count.load(Ordering::Relaxed);
/* ... */
# request::Outcome::Success(T)
}
}
```
@ -125,23 +158,36 @@ As an example, consider the following request guard implementation for
integer ID per request:
```rust
# #[macro_use] extern crate rocket;
# fn main() {}
# use std::sync::atomic::{AtomicUsize, Ordering};
use rocket::request::{self, Request, FromRequest};
/// A global atomic counter for generating IDs.
static request_id_counter: AtomicUsize = AtomicUsize::new(0);
static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
/// A type that represents a request's ID.
struct RequestId(pub usize);
/// Returns the current request's ID, assigning one only as necessary.
impl<'a, 'r> FromRequest<'a, 'r> for RequestId {
fn from_request(request: &'a Request<'r>) -> request::Outcome {
impl<'a, 'r> FromRequest<'a, 'r> for &'a RequestId {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
// The closure passed to `local_cache` will be executed at most once per
// request: the first time the `RequestId` guard is used. If it is
// requested again, `local_cache` will return the same value.
Outcome::Success(request.local_cache(|| {
RequestId(request_id_counter.fetch_add(1, Ordering::Relaxed))
request::Outcome::Success(request.local_cache(|| {
RequestId(ID_COUNTER.fetch_add(1, Ordering::Relaxed))
}))
}
}
#[get("/")]
fn id(id: &RequestId) -> String {
format!("This is request #{}.", id.0)
}
```
Note that, without request-local state, it would not be possible to:
@ -246,6 +292,7 @@ Finally, attach the fairing returned by `YourType::fairing()`, which was
generated by the `#[database]` attribute:
```rust
# #[macro_use] extern crate rocket;
#[macro_use] extern crate rocket_contrib;
use rocket_contrib::databases::diesel;
@ -254,9 +301,11 @@ use rocket_contrib::databases::diesel;
struct LogsDbConn(diesel::SqliteConnection);
fn main() {
# if false {
rocket::ignite()
.attach(LogsDbConn::fairing())
.launch();
# }
}
```
@ -264,9 +313,21 @@ That's it! Whenever a connection to the database is needed, use your type as a
request guard:
```rust
# #[macro_use] extern crate rocket;
# #[macro_use] extern crate rocket_contrib;
# fn main() {}
# use rocket_contrib::databases::diesel;
# #[database("sqlite_logs")]
# struct LogsDbConn(diesel::SqliteConnection);
# type Logs = ();
#[get("/logs/<id>")]
fn get_logs(conn: LogsDbConn, id: usize) -> Result<Logs> {
fn get_logs(conn: LogsDbConn, id: usize) -> Logs {
# /*
logs::filter(id.eq(log_id)).load(&*conn)
# */
}
```

View File

@ -45,14 +45,19 @@ that can be used to solve problems in a clean, composable, and robust manner.
Fairings are registered with Rocket via the [`attach`] method on a [`Rocket`]
instance. Only when a fairing is attached will its callbacks fire. As an
example, the following snippet attached two fairings, `req_fairing` and
example, the following snippet attached two fairings, `req_fairing` and
`res_fairing`, to a new Rocket instance:
```rust
# let req_fairing = rocket::fairing::AdHoc::on_request("example", |_, _| {});
# let res_fairing = rocket::fairing::AdHoc::on_response("example", |_, _| {});
# if false {
rocket::ignite()
.attach(req_fairing)
.attach(res_fairing)
.launch();
# }
```
[`attach`]: @api/rocket/struct.Rocket.html#method.attach
@ -140,6 +145,13 @@ unrouted requests to the `/counts` path by returning the recorded number of
counts.
```rust
use std::io::Cursor;
use std::sync::atomic::{AtomicUsize, Ordering};
use rocket::{Request, Data, Response};
use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::{Method, ContentType, Status};
struct Counter {
get: AtomicUsize,
post: AtomicUsize,
@ -160,7 +172,7 @@ impl Fairing for Counter {
Method::Get => self.get.fetch_add(1, Ordering::Relaxed),
Method::Post => self.post.fetch_add(1, Ordering::Relaxed),
_ => return
}
};
}
fn on_response(&self, request: &Request, response: &mut Response) {
@ -183,8 +195,8 @@ impl Fairing for Counter {
}
```
For brevity, imports are not shown. The complete example can be found in the
[`Fairing` documentation](@api/rocket/fairing/trait.Fairing.html#example).
The complete example can be found in the [`Fairing`
documentation](@api/rocket/fairing/trait.Fairing.html#example).
## Ad-Hoc Fairings

View File

@ -14,27 +14,40 @@ instance. Usage is straightforward:
1. Construct a `Rocket` instance that represents the application.
```rust
let rocket = rocket::ignite();
```
```rust
let rocket = rocket::ignite();
# let _ = rocket;
```
2. Construct a `Client` using the `Rocket` instance.
```rust
let client = Client::new(rocket).expect("valid rocket instance");
```
```rust
# use rocket::local::Client;
# let rocket = rocket::ignite();
let client = Client::new(rocket).expect("valid rocket instance");
# let _ = client;
```
3. Construct requests using the `Client` instance.
```rust
let req = client.get("/");
```
```rust
# use rocket::local::Client;
# let rocket = rocket::ignite();
# let client = Client::new(rocket).unwrap();
let req = client.get("/");
# let _ = req;
```
4. Dispatch the request to retrieve the response.
```rust
let response = req.dispatch();
```
```rust
# use rocket::local::Client;
# let rocket = rocket::ignite();
# let client = Client::new(rocket).unwrap();
# let req = client.get("/");
let response = req.dispatch();
# let _ = response;
```
[`local`]: @api/rocket/local/
[`Client`]: @api/rocket/local/struct.Client.html
@ -69,14 +82,33 @@ These methods are typically used in combination with the `assert_eq!` or
`assert!` macros as follows:
```rust
let rocket = rocket::ignite();
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# use std::io::Cursor;
# use rocket::Response;
# use rocket::http::Header;
# #[get("/")]
# fn hello() -> Response<'static> {
# Response::build()
# .header(ContentType::Plain)
# .header(Header::new("X-Special", ""))
# .sized_body(Cursor::new("Expected Body"))
# .finalize()
# }
use rocket::local::Client;
use rocket::http::{ContentType, Status};
let rocket = rocket::ignite().mount("/", routes![hello]);
let client = Client::new(rocket).expect("valid rocket instance");
let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::Plain));
assert!(response.headers().get_one("X-Special").is_some());
assert_eq!(response.body_string(), Some("Expected Body.".into()));
assert_eq!(response.body_string(), Some("Expected Body".into()));
```
## Testing "Hello, world!"
@ -85,17 +117,22 @@ To solidify an intuition for how Rocket applications are tested, we walk through
how to test the "Hello, world!" application below:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
#[get("/")]
fn hello() -> &'static str {
"Hello, world!"
}
fn rocket() -> Rocket {
fn rocket() -> rocket::Rocket {
rocket::ignite().mount("/", routes![hello])
}
fn main() {
# if false {
rocket().launch();
# }
}
```
@ -116,7 +153,7 @@ mod test {
#[test]
fn hello_world() {
...
/* .. */
}
}
```
@ -135,6 +172,9 @@ To test our "Hello, world!" application, we first create a `Client` for our
testing: we _want_ our tests to panic when something goes wrong.
```rust
# fn rocket() -> rocket::Rocket { rocket::ignite() }
# use rocket::local::Client;
let client = Client::new(rocket()).expect("valid rocket instance");
```
@ -142,6 +182,9 @@ Then, we create a new `GET /` request and dispatch it, getting back our
application's response:
```rust
# fn rocket() -> rocket::Rocket { rocket::ignite() }
# use rocket::local::Client;
# let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/").dispatch();
```
@ -154,6 +197,19 @@ Here, we want to ensure two things:
We do this by checking the `Response` object directly:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
# #[get("/")]
# fn hello() -> &'static str { "Hello, world!" }
# use rocket::local::Client;
use rocket::http::{ContentType, Status};
# let rocket = rocket::ignite().mount("/", routes![hello]);
# let client = Client::new(rocket).expect("valid rocket instance");
# let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some("Hello, world!".into()));
```
@ -161,13 +217,29 @@ assert_eq!(response.body_string(), Some("Hello, world!".into()));
That's it! Altogether, this looks like:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
#[get("/")]
fn hello() -> &'static str {
"Hello, world!"
}
fn rocket() -> rocket::Rocket {
rocket::ignite().mount("/", routes![hello])
}
# /*
#[cfg(test)]
# */
mod test {
use super::rocket;
use rocket::local::Client;
use rocket::http::Status;
# /*
#[test]
# */ pub
fn hello_world() {
let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/").dispatch();
@ -175,6 +247,8 @@ mod test {
assert_eq!(response.body_string(), Some("Hello, world!".into()));
}
}
# fn main() { test::hello_world(); }
```
The tests can be run with `cargo test`. You can find the full source code to
@ -187,13 +261,13 @@ especially when you get a strange type error. To have Rocket log the code that
it is emitting to the console, set the `ROCKET_CODEGEN_DEBUG` environment
variable when compiling:
```rust
```sh
ROCKET_CODEGEN_DEBUG=1 cargo build
```
During compilation, you should see output like:
```rust
```rust,ignore
note: emitting Rocket code generation debug output
--> examples/hello_world/src/main.rs:7:1
|

View File

@ -102,7 +102,7 @@ overrides if already present, that parameter in every environment. For example,
given the following `Rocket.toml` file, the value of `address` will be
`"1.2.3.4"` in every environment:
```
```toml
[global]
address = "1.2.3.4"
@ -189,6 +189,15 @@ The following code will:
3. Retrieve the parameter in an `assets` route via the `State` guard.
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
use std::path::{Path, PathBuf};
use rocket::State;
use rocket::response::NamedFile;
use rocket::fairing::AdHoc;
struct AssetsDir(String);
#[get("/<asset..>")]
@ -197,6 +206,7 @@ fn assets(asset: PathBuf, assets_dir: State<AssetsDir>) -> Option<NamedFile> {
}
fn main() {
# if false {
rocket::ignite()
.mount("/", routes![assets])
.attach(AdHoc::on_attach("Assets Config", |rocket| {
@ -208,6 +218,7 @@ fn main() {
Ok(rocket.manage(AssetsDir(assets_dir)))
}))
.launch();
# }
}
```
@ -247,16 +258,25 @@ In addition to using environment variables or a config file, Rocket can also be
configured using the [`rocket::custom()`] method and [`ConfigBuilder`]:
```rust
# #![feature(proc_macro_hygiene)]
# #[macro_use] extern crate rocket;
use rocket::config::{Config, Environment};
# fn build_config() -> rocket::config::Result<Config> {
let config = Config::build(Environment::Staging)
.address("1.2.3.4")
.port(9234)
.finalize()?;
# Ok(config)
# }
# let config = build_config().expect("config okay");
# /*
rocket::custom(config)
.mount(..)
.mount("/", routes![/* .. */])
.launch();
# */
```
Configuration via `rocket::custom()` replaces calls to `rocket::ignite()` and
@ -277,7 +297,7 @@ Security). In order for TLS support to be enabled, Rocket must be compiled with
the `"tls"` feature. To do this, add the `"tls"` feature to the `rocket`
dependency in your `Cargo.toml` file:
```
```toml
[dependencies]
rocket = { version = "0.5.0-dev", features = ["tls"] }
```
@ -291,7 +311,7 @@ must be a table with two keys:
The recommended way to specify these parameters is via the `global` environment:
```
```toml
[global.tls]
certs = "/path/to/certs.pem"
key = "/path/to/key.pem"
@ -299,7 +319,7 @@ key = "/path/to/key.pem"
Of course, you can always specify the configuration values per environment:
```
```toml
[development]
tls = { certs = "/path/to/certs.pem", key = "/path/to/key.pem" }
```

12
site/tests/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "rocket_guide_tests"
version = "0.0.0"
workspace = "../../"
edition = "2018"
publish = false
[dependencies]
rocket = { path = "../../core/lib" }
rocket_contrib = { path = "../../contrib/lib", features = ["json", "tera_templates", "diesel_sqlite_pool"] }
serde = { version = "1.0", features = ["derive"] }
rand = "0.7"

3
site/tests/src/lib.rs Normal file
View File

@ -0,0 +1,3 @@
#![feature(external_doc)]
rocket::rocket_internal_guide_tests!("../guide/*.md");