From 95c981de7955408f43328571aef97a42790bc4fb Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sat, 15 Feb 2020 03:43:47 -0800 Subject: [PATCH] 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. --- Cargo.toml | 1 + core/codegen/Cargo.toml | 1 + core/codegen/src/bang/mod.rs | 7 + core/codegen/src/bang/test_guide.rs | 45 +++++ core/codegen/src/lib.rs | 6 + core/lib/tests/guide.rs | 14 ++ site/guide/10-pastebin.md | 121 ++++++++++--- site/guide/2-getting-started.md | 4 +- site/guide/3-overview.md | 30 +++- site/guide/4-requests.md | 257 ++++++++++++++++++++++++---- site/guide/5-responses.md | 160 ++++++++++++++--- site/guide/6-state.md | 83 +++++++-- site/guide/7-fairings.md | 20 ++- site/guide/8-testing.md | 110 ++++++++++-- site/guide/9-configuration.md | 30 +++- site/tests/Cargo.toml | 12 ++ site/tests/src/lib.rs | 3 + 17 files changed, 779 insertions(+), 125 deletions(-) create mode 100644 core/codegen/src/bang/test_guide.rs create mode 100644 core/lib/tests/guide.rs create mode 100644 site/tests/Cargo.toml create mode 100644 site/tests/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index d6b0e638..3a24d25e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "core/http/", "contrib/lib", "contrib/codegen", + "site/tests", "examples/cookies", "examples/errors", "examples/form_validation", diff --git a/core/codegen/Cargo.toml b/core/codegen/Cargo.toml index 97031e88..e03fe12c 100644 --- a/core/codegen/Cargo.toml +++ b/core/codegen/Cargo.toml @@ -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" diff --git a/core/codegen/src/bang/mod.rs b/core/codegen/src/bang/mod.rs index 899c33df..ca633c0c 100644 --- a/core/codegen/src/bang/mod.rs +++ b/core/codegen/src/bang/mod.rs @@ -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()) +} diff --git a/core/codegen/src/bang/test_guide.rs b/core/codegen/src/bang/test_guide.rs new file mode 100644 index 00000000..15295cb7 --- /dev/null +++ b/core/codegen/src/bang/test_guide.rs @@ -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 { + let root = syn::parse::(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, Box> { + 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) +} diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs index 34d6538d..996715ed 100644 --- a/core/codegen/src/lib.rs +++ b/core/codegen/src/lib.rs @@ -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)) +} diff --git a/core/lib/tests/guide.rs b/core/lib/tests/guide.rs new file mode 100644 index 00000000..b352d9f8 --- /dev/null +++ b/core/lib/tests/guide.rs @@ -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; +} + diff --git a/site/guide/10-pastebin.md b/site/guide/10-pastebin.md index bcfe46ee..f8da2544 100644 --- a/site/guide/10-pastebin.md +++ b/site/guide/10-pastebin.md @@ -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::() % 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 = "")] -fn upload(paste: Data) -> io::Result +fn upload(paste: Data) -> Result> { + # 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 = "")] -fn upload(paste: Data) -> io::Result { - let id = PasteID::new(3); +fn upload(paste: Data) -> Result> { + 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 { 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 { 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("/")] 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, &'a RawStr> { + fn from_param(param: &'a RawStr) -> Result, &'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 `` 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 `` 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("/")] -fn retrieve(id: PasteID) -> Option { +fn retrieve(id: PasteId) -> Option { 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 /` route. Use - `PasteID` to validate ``. + `PasteId` to validate ``. * **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. diff --git a/site/guide/2-getting-started.md b/site/guide/2-getting-started.md index 730555dc..e481abb1 100644 --- a/site/guide/2-getting-started.md +++ b/site/guide/2-getting-started.md @@ -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(); + # } } ``` diff --git a/site/guide/3-overview.md b/site/guide/3-overview.md index 1dedfcb1..4c670f88 100644 --- a/site/guide/3-overview.md +++ b/site/guide/3-overview.md @@ -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(); + # } } ``` diff --git a/site/guide/4-requests.md b/site/guide/4-requests.md index c1916b52..53cafd41 100644 --- a/site/guide/4-requests.md +++ b/site/guide/4-requests.md @@ -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/")] 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///")] 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/")] -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("/")] fn files(file: PathBuf) -> Option { 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///")] -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/")] -fn user(id: usize) -> T { ... } +fn user(id: usize) { /* ... */ } #[get("/user/", rank = 2)] -fn user_int(id: isize) -> T { ... } +fn user_int(id: isize) { /* ... */ } #[get("/user/", 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&")] 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` looks as follows: ```rust +# #[macro_use] extern crate rocket; +# fn main() {} + #[get("/hello?wave&")] -fn hello(name: Option<&RawStr>) -> String { +fn hello(name: Option) -> 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?&")] fn item(id: usize, user: Option>) { /* ... */ } ``` @@ -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("/")] -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 { @@ -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 = "")] -fn new_user(user: Json) -> 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/", format = "json")] -fn user(id: usize) -> Json { ... } +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 = "")] -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 = "")] -fn new(task: Form) -> String { ... } +fn new(task: Form) { /* .. */ } ``` 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 = "")] -fn new(task: Option>) -> String { ... } +fn new(task: Option>) { /* .. */ } ``` [`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 = "")] -fn new(task: LenientForm) { .. } +fn new(task: LenientForm) { /* .. */ } ``` [`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 @@ -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 = "")] -fn new(task: Json) -> String { ... } +fn new(task: Json) { /* .. */ } ``` 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 = "")] -fn upload(data: Data) -> io::Result { - data.stream_to_file("/tmp/upload.txt").map(|n| n.to_string()) +fn upload(data: Data) -> Result> { + 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 diff --git a/site/guide/5-responses.md b/site/guide/5-responses.md index 984ca819..c9c86c11 100644 --- a/site/guide/5-responses.md +++ b/site/guide/5-responses.md @@ -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("/")] @@ -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, 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("/")] fn files(file: PathBuf) -> Option { 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("/")] fn files(file: PathBuf) -> Result> { 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> { - UnixStream::connect("/path/to/my/socket").map(|s| Stream::from(s)) +fn stream() -> Result, Debug> { + 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 { ... } +fn todo() -> Json { + 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/?")] -fn person(name: String, age: Option) -> T +fn person(name: String, age: Option) { /* .. */ } ``` URIs to `person` can be created as follows: ```rust +# #![feature(proc_macro_hygiene)] +# #[macro_use] extern crate rocket; + +# #[get("/person/?")] +# fn person(name: String, age: Option) { /* .. */ } + // 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 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, @@ -458,7 +544,7 @@ struct UserDetails<'r> { } #[post("/user/?")] -fn add_user(id: usize, details: Form) { .. } +fn add_user(id: usize, details: Form) { /* .. */ } ``` By deriving using `UriDisplayQuery`, an implementation of `UriDisplay` 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, +# nickname: &'r RawStr, +# } + +# #[post("/user/?")] +# fn add_user(id: usize, details: Form) { /* .. */ } + +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` and `FromUriParam`. ### Conversions -The [`FromUriParam`] is used to perform a conversion for each value passed to -`uri!` before it is displayed with `UriDisplay`. If a `FromUriParam` +[`FromUriParam`] is used to perform a conversion for each value passed to `uri!` +before it is displayed with `UriDisplay`. If a `FromUriParam` 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 FromUriParam for String { .. } +# use rocket::http::uri::{FromUriParam, UriPart}; +# struct S; +# type String = S; +impl<'a, P: UriPart> FromUriParam 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>` is expected: ```rust -#[get("/person/?
")] -fn person(id: usize, details: Option>) -> 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, nickname: &'r RawStr, } + +#[get("/person/?")] +fn person(id: usize, details: Option>) { /* .. */ } + +uri!(person: id = 100, details = UserDetails { age: Some(20), nickname: "Bob".into() }); ``` See the [`FromUriParam`] documentation for further details. diff --git a/site/guide/6-state.md b/site/guide/6-state.md index c6188bee..f9dad433 100644 --- a/site/guide/6-state.md +++ b/site/guide/6-state.md @@ -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) -> 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, config: State) -> T { ... } +fn state(hit_count: State, config: State) { /* .. */ } ``` ! warning @@ -99,10 +117,25 @@ implementation. To do so, simply invoke `State` as a guard using the [`Request::guard()`] method. ```rust -fn from_request(req: &'a Request<'r>) -> request::Outcome { - let hit_count_state = req.guard::>()?; - 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 { + let hit_count_state = try_outcome!(req.guard::>()); + 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 { // 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/")] -fn get_logs(conn: LogsDbConn, id: usize) -> Result { +fn get_logs(conn: LogsDbConn, id: usize) -> Logs { + # /* logs::filter(id.eq(log_id)).load(&*conn) + # */ } ``` diff --git a/site/guide/7-fairings.md b/site/guide/7-fairings.md index a3aa59d2..eefa477a 100644 --- a/site/guide/7-fairings.md +++ b/site/guide/7-fairings.md @@ -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 diff --git a/site/guide/8-testing.md b/site/guide/8-testing.md index 59bf87e5..a7be018e 100644 --- a/site/guide/8-testing.md +++ b/site/guide/8-testing.md @@ -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 | diff --git a/site/guide/9-configuration.md b/site/guide/9-configuration.md index 585d8148..cae83659 100644 --- a/site/guide/9-configuration.md +++ b/site/guide/9-configuration.md @@ -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("/")] @@ -197,6 +206,7 @@ fn assets(asset: PathBuf, assets_dir: State) -> Option { } 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 { 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" } ``` diff --git a/site/tests/Cargo.toml b/site/tests/Cargo.toml new file mode 100644 index 00000000..1f36db1c --- /dev/null +++ b/site/tests/Cargo.toml @@ -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" diff --git a/site/tests/src/lib.rs b/site/tests/src/lib.rs new file mode 100644 index 00000000..d62512ca --- /dev/null +++ b/site/tests/src/lib.rs @@ -0,0 +1,3 @@ +#![feature(external_doc)] + +rocket::rocket_internal_guide_tests!("../guide/*.md");