mirror of https://github.com/rwf2/Rocket.git
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:
parent
ee1a9903b6
commit
95c981de79
|
@ -8,6 +8,7 @@ members = [
|
|||
"core/http/",
|
||||
"contrib/lib",
|
||||
"contrib/codegen",
|
||||
"site/tests",
|
||||
"examples/cookies",
|
||||
"examples/errors",
|
||||
"examples/form_validation",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
# }
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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();
|
||||
# }
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
# */
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
||||
|
|
|
@ -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" }
|
||||
```
|
||||
|
|
|
@ -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"
|
|
@ -0,0 +1,3 @@
|
|||
#![feature(external_doc)]
|
||||
|
||||
rocket::rocket_internal_guide_tests!("../guide/*.md");
|
Loading…
Reference in New Issue