Update and fix guide tests.

This commit is contained in:
Jeb Rosen 2020-07-11 09:41:53 -07:00
parent 08b34e8263
commit 2c82b3e1d5
9 changed files with 74 additions and 72 deletions

View File

@ -268,13 +268,13 @@ Here's our version (in `src/main.rs`):
# fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Ok(()) } # fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Ok(()) }
# } # }
use std::path::Path; use std::path::PathBuf;
use rocket::Data; use rocket::Data;
use rocket::response::Debug; use rocket::response::Debug;
#[post("/", data = "<paste>")] #[post("/", data = "<paste>")]
fn upload(paste: Data) -> Result<String, Debug<std::io::Error>> { async fn upload(paste: Data) -> Result<String, Debug<std::io::Error>> {
let id = PasteId::new(3); let id = PasteId::new(3);
let filename = format!("upload/{id}", id = id); let filename = format!("upload/{id}", id = id);
let url = format!("{host}/{id}\n", host = "http://localhost:8000", id = id); let url = format!("{host}/{id}\n", host = "http://localhost:8000", id = id);

View File

@ -259,11 +259,14 @@ stalls, or sometimes even deadlocks can occur.
`tokio::task::spawn_blocking`: `tokio::task::spawn_blocking`:
```rust ```rust
# #[macro_use] extern crate rocket;
use rocket::tokio::task::spawn_blocking;
#[get("/blocking_task")] #[get("/blocking_task")]
async fn blocking_task() -> String { async fn blocking_task() -> Vec<u8> {
// In a real application, we would use rocket::response::NamedFile // In a real application, we would use rocket::response::NamedFile
tokio::task::spawn_blocking(|| { spawn_blocking(|| {
std::fs::read_file("data.txt") std::fs::read("data.txt").expect("failed to read file")
}).await }).await.unwrap()
} }
``` ```

View File

@ -166,8 +166,8 @@ this, a safe and secure static file server can be implemented in 4 lines:
use rocket::response::NamedFile; use rocket::response::NamedFile;
#[get("/<file..>")] #[get("/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> { async fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok() NamedFile::open(Path::new("static/").join(file)).await.ok()
} }
``` ```
@ -1058,8 +1058,8 @@ use rocket::Data;
use rocket::response::Debug; use rocket::response::Debug;
#[post("/upload", format = "plain", data = "<data>")] #[post("/upload", format = "plain", data = "<data>")]
fn upload(data: Data) -> Result<String, Debug<std::io::Error>> { async fn upload(data: Data) -> Result<String, Debug<std::io::Error>> {
Ok(data.stream_to_file("/tmp/upload.txt").map(|n| n.to_string())?) Ok(data.stream_to_file("/tmp/upload.txt").await.map(|n| n.to_string())?)
} }
``` ```
@ -1083,24 +1083,18 @@ returned. The handler above is complete. It really is that simple! See the
Rocket makes it easy to use `async/await` in routes. Rocket makes it easy to use `async/await` in routes.
```rust ```rust
#[get("/weather")] # #[macro_use] extern crate rocket;
async fn weather() -> String { use rocket::tokio::time::{delay_for, Duration};
let response = reqwest::get("https://www.example.com").await; #[get("/delay/<seconds>")]
response.text().await async fn delay(seconds: u64) -> String {
delay_for(Duration::from_secs(seconds)).await;
format!("Waited for {} seconds", seconds)
} }
``` ```
First, notice that the route function is an `async fn`. This enables First, notice that the route function is an `async fn`. This enables
the use of `await` inside the handler. `reqwest` is an asynchronous the use of `await` inside the handler. `delay_for` is an asynchronous
HTTP client, so we must `await` the response. Finally, we call function, so we must `await` it.
the `text()` function, which asynchronously downloads the full
response data from the server.
! warning: You should _always_ set limits when reading incoming data.
Just like with client input, you should usually limit the amount
of data read from external APIs. The exact method will depend
on the library you are using to make requests.
## Error Catchers ## Error Catchers

View File

@ -185,14 +185,15 @@ use rocket::response::{self, Response, Responder};
use rocket::http::ContentType; use rocket::http::ContentType;
# struct String(std::string::String); # struct String(std::string::String);
impl<'a> Responder<'a> for String { #[rocket::async_trait]
fn respond_to(self, _: &Request) -> response::Result<'a> { impl<'r> Responder<'r, 'static> for String {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
Response::build() Response::build()
.header(ContentType::Plain) .header(ContentType::Plain)
# /* # /*
.sized_body(Cursor::new(self)) .sized_body(self.len(), Cursor::new(self))
# */ # */
# .sized_body(Cursor::new(self.0)) # .sized_body(self.0.len(), Cursor::new(self.0))
.ok() .ok()
} }
} }
@ -230,8 +231,8 @@ found and a `404` when a file is not found in just 4, idiomatic lines:
use rocket::response::NamedFile; use rocket::response::NamedFile;
#[get("/<file..>")] #[get("/<file..>")]
fn files(file: PathBuf) -> Option<NamedFile> { async fn files(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("static/").join(file)).ok() NamedFile::open(Path::new("static/").join(file)).await.ok()
} }
``` ```
@ -256,9 +257,9 @@ use rocket::response::NamedFile;
use rocket::response::status::NotFound; use rocket::response::status::NotFound;
#[get("/<file..>")] #[get("/<file..>")]
fn files(file: PathBuf) -> Result<NamedFile, NotFound<String>> { async fn files(file: PathBuf) -> Result<NamedFile, NotFound<String>> {
let path = Path::new("static/").join(file); let path = Path::new("static/").join(file);
NamedFile::open(&path).map_err(|e| NotFound(e.to_string())) NamedFile::open(&path).await.map_err(|e| NotFound(e.to_string()))
} }
``` ```
@ -306,14 +307,17 @@ example, to stream from a local Unix stream, we might write:
# #[macro_use] extern crate rocket; # #[macro_use] extern crate rocket;
# fn main() {} # fn main() {}
# #[cfg(unix)]
# mod test { # mod test {
use std::os::unix::net::UnixStream; use std::net::SocketAddr;
use rocket::response::{Stream, Debug}; use rocket::response::{Stream, Debug};
use rocket::tokio::net::TcpStream;
#[get("/stream")] #[get("/stream")]
fn stream() -> Result<Stream<UnixStream>, Debug<std::io::Error>> { async fn stream() -> Result<Stream<TcpStream>, Debug<std::io::Error>> {
Ok(UnixStream::connect("/path/to/my/socket").map(Stream::from)?) let addr = SocketAddr::from(([127, 0, 0, 1], 9999));
Ok(TcpStream::connect(addr).await.map(Stream::from)?)
} }
# } # }
``` ```

View File

@ -127,11 +127,12 @@ use rocket::request::{self, Request, FromRequest};
# struct T; # struct T;
# struct HitCount { count: AtomicUsize } # struct HitCount { count: AtomicUsize }
# type ErrorType = (); # type ErrorType = ();
#[rocket::async_trait]
impl<'a, 'r> FromRequest<'a, 'r> for T { impl<'a, 'r> FromRequest<'a, 'r> for T {
type Error = ErrorType; type Error = ErrorType;
fn from_request(req: &'a Request<'r>) -> request::Outcome<T, Self::Error> { async fn from_request(req: &'a Request<'r>) -> request::Outcome<T, Self::Error> {
let hit_count_state = try_outcome!(req.guard::<State<HitCount>>()); let hit_count_state = try_outcome!(req.guard::<State<HitCount>>().await);
let current_count = hit_count_state.count.load(Ordering::Relaxed); let current_count = hit_count_state.count.load(Ordering::Relaxed);
/* ... */ /* ... */
# request::Outcome::Success(T) # request::Outcome::Success(T)
@ -171,10 +172,11 @@ static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
struct RequestId(pub usize); struct RequestId(pub usize);
/// Returns the current request's ID, assigning one only as necessary. /// Returns the current request's ID, assigning one only as necessary.
#[rocket::async_trait]
impl<'a, 'r> FromRequest<'a, 'r> for &'a RequestId { impl<'a, 'r> FromRequest<'a, 'r> for &'a RequestId {
type Error = (); type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> { async 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 // 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 // request: the first time the `RequestId` guard is used. If it is
// requested again, `local_cache` will return the same value. // requested again, `local_cache` will return the same value.

View File

@ -49,8 +49,8 @@ example, the following snippet attached two fairings, `req_fairing` and
`res_fairing`, to a new Rocket instance: `res_fairing`, to a new Rocket instance:
```rust ```rust
# let req_fairing = rocket::fairing::AdHoc::on_request("example", |_, _| {}); # let req_fairing = rocket::fairing::AdHoc::on_request("example", |_, _| Box::pin(async {}));
# let res_fairing = rocket::fairing::AdHoc::on_response("example", |_, _| {}); # let res_fairing = rocket::fairing::AdHoc::on_response("example", |_, _| Box::pin(async {}));
# if false { # if false {
rocket::ignite() rocket::ignite()
@ -157,6 +157,7 @@ struct Counter {
post: AtomicUsize, post: AtomicUsize,
} }
#[rocket::async_trait]
impl Fairing for Counter { impl Fairing for Counter {
// This is a request and response fairing named "GET/POST Counter". // This is a request and response fairing named "GET/POST Counter".
fn info(&self) -> Info { fn info(&self) -> Info {
@ -167,7 +168,7 @@ impl Fairing for Counter {
} }
// Increment the counter for `GET` and `POST` requests. // Increment the counter for `GET` and `POST` requests.
fn on_request(&self, request: &mut Request, _: &Data) { async fn on_request<'a>(&'a self, request: &'a mut Request<'_>, _: &'a Data) {
match request.method() { match request.method() {
Method::Get => self.get.fetch_add(1, Ordering::Relaxed), Method::Get => self.get.fetch_add(1, Ordering::Relaxed),
Method::Post => self.post.fetch_add(1, Ordering::Relaxed), Method::Post => self.post.fetch_add(1, Ordering::Relaxed),
@ -175,8 +176,7 @@ impl Fairing for Counter {
}; };
} }
fn on_response<'a>(&'a self, request: &'a Request<'_>, response: &'a mut Response<'_>) -> BoxFuture<'a, ()> { async fn on_response<'a>(&'a self, request: &'a Request<'_>, response: &'a mut Response<'_>) {
Box::pin(async move {
// Don't change a successful user's response, ever. // Don't change a successful user's response, ever.
if response.status() != Status::NotFound { if response.status() != Status::NotFound {
return return
@ -190,9 +190,8 @@ impl Fairing for Counter {
response.set_status(Status::Ok); response.set_status(Status::Ok);
response.set_header(ContentType::Plain); response.set_header(ContentType::Plain);
response.set_sized_body(Cursor::new(body)); response.set_sized_body(body.len(), Cursor::new(body));
} }
})
} }
} }
``` ```

View File

@ -22,7 +22,7 @@ instance. Usage is straightforward:
2. Construct a `Client` using the `Rocket` instance. 2. Construct a `Client` using the `Rocket` instance.
```rust ```rust
# use rocket::local::Client; # use rocket::local::blocking::Client;
# let rocket = rocket::ignite(); # let rocket = rocket::ignite();
let client = Client::new(rocket).expect("valid rocket instance"); let client = Client::new(rocket).expect("valid rocket instance");
# let _ = client; # let _ = client;
@ -31,7 +31,7 @@ instance. Usage is straightforward:
3. Construct requests using the `Client` instance. 3. Construct requests using the `Client` instance.
```rust ```rust
# use rocket::local::Client; # use rocket::local::blocking::Client;
# let rocket = rocket::ignite(); # let rocket = rocket::ignite();
# let client = Client::new(rocket).unwrap(); # let client = Client::new(rocket).unwrap();
let req = client.get("/"); let req = client.get("/");
@ -41,7 +41,7 @@ instance. Usage is straightforward:
4. Dispatch the request to retrieve the response. 4. Dispatch the request to retrieve the response.
```rust ```rust
# use rocket::local::Client; # use rocket::local::blocking::Client;
# let rocket = rocket::ignite(); # let rocket = rocket::ignite();
# let client = Client::new(rocket).unwrap(); # let client = Client::new(rocket).unwrap();
# let req = client.get("/"); # let req = client.get("/");
@ -94,11 +94,11 @@ These methods are typically used in combination with the `assert_eq!` or
# Response::build() # Response::build()
# .header(ContentType::Plain) # .header(ContentType::Plain)
# .header(Header::new("X-Special", "")) # .header(Header::new("X-Special", ""))
# .sized_body(Cursor::new("Expected Body")) # .sized_body("Expected Body".len(), Cursor::new("Expected Body"))
# .finalize() # .finalize()
# } # }
use rocket::local::Client; use rocket::local::blocking::Client;
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Status};
let rocket = rocket::ignite().mount("/", routes![hello]); let rocket = rocket::ignite().mount("/", routes![hello]);
@ -108,7 +108,7 @@ let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::Plain)); assert_eq!(response.content_type(), Some(ContentType::Plain));
assert!(response.headers().get_one("X-Special").is_some()); assert!(response.headers().get_one("X-Special").is_some());
assert_eq!(response.body_string(), Some("Expected Body".into())); assert_eq!(response.into_string(), Some("Expected Body".into()));
``` ```
## Testing "Hello, world!" ## Testing "Hello, world!"
@ -173,7 +173,7 @@ testing: we _want_ our tests to panic when something goes wrong.
```rust ```rust
# fn rocket() -> rocket::Rocket { rocket::ignite() } # fn rocket() -> rocket::Rocket { rocket::ignite() }
# use rocket::local::Client; # use rocket::local::blocking::Client;
let client = Client::new(rocket()).expect("valid rocket instance"); let client = Client::new(rocket()).expect("valid rocket instance");
``` ```
@ -183,7 +183,7 @@ application's response:
```rust ```rust
# fn rocket() -> rocket::Rocket { rocket::ignite() } # fn rocket() -> rocket::Rocket { rocket::ignite() }
# use rocket::local::Client; # use rocket::local::blocking::Client;
# let client = Client::new(rocket()).expect("valid rocket instance"); # let client = Client::new(rocket()).expect("valid rocket instance");
let mut response = client.get("/").dispatch(); let mut response = client.get("/").dispatch();
``` ```
@ -203,7 +203,7 @@ We do this by checking the `Response` object directly:
# #[get("/")] # #[get("/")]
# fn hello() -> &'static str { "Hello, world!" } # fn hello() -> &'static str { "Hello, world!" }
# use rocket::local::Client; # use rocket::local::blocking::Client;
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Status};
# #
# let rocket = rocket::ignite().mount("/", routes![hello]); # let rocket = rocket::ignite().mount("/", routes![hello]);

View File

@ -201,15 +201,15 @@ use rocket::fairing::AdHoc;
struct AssetsDir(String); struct AssetsDir(String);
#[get("/<asset..>")] #[get("/<asset..>")]
fn assets(asset: PathBuf, assets_dir: State<AssetsDir>) -> Option<NamedFile> { async fn assets(asset: PathBuf, assets_dir: State<'_, AssetsDir>) -> Option<NamedFile> {
NamedFile::open(Path::new(&assets_dir.0).join(asset)).ok() NamedFile::open(Path::new(&assets_dir.0).join(asset)).await.ok()
} }
fn main() { fn main() {
# if false { # if false {
rocket::ignite() rocket::ignite()
.mount("/", routes![assets]) .mount("/", routes![assets])
.attach(AdHoc::on_attach("Assets Config", |mut rocket| { .attach(AdHoc::on_attach("Assets Config", |mut rocket| async {
let assets_dir = rocket.config().await let assets_dir = rocket.config().await
.get_str("assets_dir") .get_str("assets_dir")
.unwrap_or("assets/") .unwrap_or("assets/")

View File

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