Update parts of contrib and associated tests and examples:

* json
  * templates
  * helmet
  * databases
  * serve
  * examples/cookies
  * examples/handlebars_templates
  * examples/json
  * examples/session
  * examples/static_files
  * examples/tera_templates
This commit is contained in:
Jeb Rosen 2019-07-27 09:15:23 -07:00 committed by Sergio Benitez
parent 7c34a3a93e
commit 6044961680
18 changed files with 105 additions and 79 deletions

View File

@ -42,6 +42,7 @@ memcache_pool = ["databases", "memcache", "r2d2-memcache"]
[dependencies]
# Global dependencies.
futures-preview = { version = "0.3.0-alpha.17" }
rocket_contrib_codegen = { version = "0.5.0-dev", path = "../codegen", optional = true }
rocket = { version = "0.5.0-dev", path = "../../core/lib/", default-features = false }
log = "0.4"

View File

@ -73,7 +73,7 @@
//! Whenever a connection to the database is needed:
//!
//! ```rust
//! # #![feature(proc_macro_hygiene)]
//! # #![feature(proc_macro_hygiene, async_await)]
//! #
//! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib;
@ -289,7 +289,7 @@
//! connection to a given database:
//!
//! ```rust
//! # #![feature(proc_macro_hygiene)]
//! # #![feature(proc_macro_hygiene, async_await)]
//! #
//! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib;
@ -311,7 +311,7 @@
//! connection type:
//!
//! ```rust
//! # #![feature(proc_macro_hygiene)]
//! # #![feature(proc_macro_hygiene, async_await)]
//! #
//! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib;

View File

@ -196,8 +196,10 @@ impl Fairing for SpaceHelmet {
}
}
fn on_response(&self, _request: &Request<'_>, response: &mut Response<'_>) {
self.apply(response);
fn on_response<'a>(&'a self, _request: &'a Request<'_>, response: &'a mut Response<'_>) -> std::pin::Pin<Box<dyn std::future::Future<Output=()> + Send + 'a>> {
Box::pin(async move {
self.apply(response);
})
}
fn on_launch(&self, rocket: &Rocket) {

View File

@ -15,14 +15,17 @@
//! ```
use std::ops::{Deref, DerefMut};
use std::io::{self, Read};
use std::io;
use std::iter::FromIterator;
use futures::io::AsyncReadExt;
use rocket::request::Request;
use rocket::outcome::Outcome::*;
use rocket::data::{Outcome, Transform, Transform::*, Transformed, Data, FromData};
use rocket::data::{Transform::*, Transformed, Data, FromData, TransformFuture, FromDataFuture};
use rocket::response::{self, Responder, content};
use rocket::http::Status;
use rocket::AsyncReadExt as _;
use serde::{Serialize, Serializer};
use serde::de::{Deserialize, Deserializer};
@ -41,7 +44,7 @@ pub use serde_json::{json_internal, json_internal_vec};
/// or from [`serde`]. The data is parsed from the HTTP request body.
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # #![feature(proc_macro_hygiene, async_await)]
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// # type User = usize;
@ -65,7 +68,7 @@ pub use serde_json::{json_internal, json_internal_vec};
/// set to `application/json` automatically.
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # #![feature(proc_macro_hygiene, async_await)]
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// # type User = usize;
@ -133,42 +136,53 @@ impl<'a, T: Deserialize<'a>> FromData<'a> for Json<T> {
type Owned = String;
type Borrowed = str;
fn transform(r: &Request<'_>, d: Data) -> Transform<Outcome<Self::Owned, Self::Error>> {
fn transform(r: &Request<'_>, d: Data) -> TransformFuture<'a, Self::Owned, Self::Error> {
let size_limit = r.limits().get("json").unwrap_or(LIMIT);
let mut s = String::with_capacity(512);
match d.open().take(size_limit).read_to_string(&mut s) {
Ok(_) => Borrowed(Success(s)),
Err(e) => Borrowed(Failure((Status::BadRequest, JsonError::Io(e))))
}
Box::pin(async move {
let mut v = Vec::with_capacity(512);
let mut reader = d.open().take(size_limit);
match reader.read_to_end(&mut v).await {
Ok(_) => {
match String::from_utf8(v) {
Ok(s) => Borrowed(Success(s)),
Err(e) => Borrowed(Failure((Status::BadRequest, JsonError::Io(std::io::Error::new(std::io::ErrorKind::Other, e))))),
}
},
Err(e) => Borrowed(Failure((Status::BadRequest, JsonError::Io(e))))
}
})
}
fn from_data(_: &Request<'_>, o: Transformed<'a, Self>) -> Outcome<Self, Self::Error> {
let string = try_outcome!(o.borrowed());
match serde_json::from_str(&string) {
Ok(v) => Success(Json(v)),
Err(e) => {
error_!("Couldn't parse JSON body: {:?}", e);
if e.is_data() {
Failure((Status::UnprocessableEntity, JsonError::Parse(string, e)))
} else {
Failure((Status::BadRequest, JsonError::Parse(string, e)))
fn from_data(_: &Request<'_>, o: Transformed<'a, Self>) -> FromDataFuture<'a, Self, Self::Error> {
Box::pin(async move {
let string = try_outcome!(o.borrowed());
match serde_json::from_str(&string) {
Ok(v) => Success(Json(v)),
Err(e) => {
error_!("Couldn't parse JSON body: {:?}", e);
if e.is_data() {
Failure((Status::UnprocessableEntity, JsonError::Parse(string, e)))
} else {
Failure((Status::BadRequest, JsonError::Parse(string, e)))
}
}
}
}
})
}
}
/// Serializes the wrapped value into JSON. Returns a response with Content-Type
/// JSON and a fixed-size body with the serialized value. If serialization
/// fails, an `Err` of `Status::InternalServerError` is returned.
impl<'a, T: Serialize> Responder<'a> for Json<T> {
fn respond_to(self, req: &Request<'_>) -> response::Result<'a> {
serde_json::to_string(&self.0).map(|string| {
content::Json(string).respond_to(req).unwrap()
}).map_err(|e| {
error_!("JSON failed to serialize: {:?}", e);
Status::InternalServerError
})
impl<'r, T: Serialize> Responder<'r> for Json<T> {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
match serde_json::to_string(&self.0) {
Ok(string) => Box::pin(async move { Ok(content::Json(string).respond_to(req).await.unwrap()) }),
Err(e) => Box::pin(async move {
error_!("JSON failed to serialize: {:?}", e);
Err(Status::InternalServerError)
})
}
}
}
@ -210,7 +224,7 @@ impl<T> DerefMut for Json<T> {
/// fashion during request handling. This looks something like:
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # #![feature(proc_macro_hygiene, async_await)]
/// # #[macro_use] extern crate rocket;
/// # #[macro_use] extern crate rocket_contrib;
/// use rocket_contrib::json::JsonValue;
@ -283,9 +297,9 @@ impl<T> FromIterator<T> for JsonValue where serde_json::Value: FromIterator<T> {
/// Serializes the value into JSON. Returns a response with Content-Type JSON
/// and a fixed-size body with the serialized value.
impl<'a> Responder<'a> for JsonValue {
impl<'r> Responder<'r> for JsonValue {
#[inline]
fn respond_to(self, req: &Request<'_>) -> response::Result<'a> {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
content::Json(self.0.to_string()).respond_to(req)
}
}
@ -305,7 +319,7 @@ impl<'a> Responder<'a> for JsonValue {
/// value created with this macro can be returned from a handler as follows:
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # #![feature(proc_macro_hygiene, async_await)]
/// # #[macro_use] extern crate rocket;
/// # #[macro_use] extern crate rocket_contrib;
/// use rocket_contrib::json::JsonValue;

View File

@ -1,3 +1,4 @@
#![feature(async_await)]
#![doc(html_root_url = "https://api.rocket.rs/v0.5")]
#![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")]
#![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")]

View File

@ -18,7 +18,7 @@ use std::path::{PathBuf, Path};
use rocket::{Request, Data, Route};
use rocket::http::{Method, uri::Segments, ext::IntoOwned};
use rocket::handler::{Handler, Outcome};
use rocket::handler::{Handler, HandlerFuture, Outcome};
use rocket::response::{NamedFile, Redirect};
/// A bitset representing configurable options for the [`StaticFiles`] handler.
@ -290,18 +290,20 @@ impl Into<Vec<Route>> for StaticFiles {
}
impl Handler for StaticFiles {
fn handle<'r>(&self, req: &'r Request<'_>, data: Data) -> Outcome<'r> {
fn handle_dir<'r>(opt: Options, r: &'r Request<'_>, d: Data, path: &Path) -> Outcome<'r> {
fn handle<'r>(&self, req: &'r Request<'_>, data: Data) -> HandlerFuture<'r> {
fn handle_dir<'r>(opt: Options, r: &'r Request<'_>, d: Data, path: &Path) -> HandlerFuture<'r> {
if opt.contains(Options::NormalizeDirs) && !r.uri().path().ends_with('/') {
let new_path = r.uri().map_path(|p| p.to_owned() + "/")
.expect("adding a trailing slash to a known good path results in a valid path")
.into_owned();
return Outcome::from_or_forward(r, d, Redirect::permanent(new_path));
return Box::pin(async move {
Outcome::from_or_forward(r, d, Redirect::permanent(new_path))
});
}
if !opt.contains(Options::Index) {
return Outcome::forward(d);
return Box::pin(async move { Outcome::forward(d) });
}
let file = NamedFile::open(path.join("index.html")).ok();
@ -327,7 +329,7 @@ impl Handler for StaticFiles {
match &path {
Some(path) if path.is_dir() => handle_dir(self.options, req, data, path),
Some(path) => Outcome::from_or_forward(req, data, NamedFile::open(path).ok()),
None => Outcome::forward(data)
None => Box::pin(async move { Outcome::forward(data) }),
}
}
}

View File

@ -12,7 +12,7 @@ use crate::templates::ContextManager;
/// used as a request guard in any request handler.
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # #![feature(proc_macro_hygiene, async_await)]
/// # #[macro_use] extern crate rocket;
/// # #[macro_use] extern crate rocket_contrib;
/// use rocket_contrib::templates::{Template, Metadata};
@ -46,7 +46,7 @@ impl Metadata<'_> {
/// # Example
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # #![feature(proc_macro_hygiene, async_await)]
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// #
@ -67,7 +67,7 @@ impl Metadata<'_> {
/// # Example
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # #![feature(proc_macro_hygiene, async_await)]
/// # #[macro_use] extern crate rocket;
/// # extern crate rocket_contrib;
/// #

View File

@ -37,7 +37,7 @@
//! of the template file minus the last two extensions, from a handler.
//!
//! ```rust
//! # #![feature(proc_macro_hygiene)]
//! # #![feature(proc_macro_hygiene, async_await)]
//! # #[macro_use] extern crate rocket;
//! # #[macro_use] extern crate rocket_contrib;
//! # fn context() { }
@ -180,7 +180,7 @@ const DEFAULT_TEMPLATE_DIR: &str = "templates";
/// returned from a request handler directly:
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # #![feature(proc_macro_hygiene, async_await)]
/// # #[macro_use] extern crate rocket;
/// # #[macro_use] extern crate rocket_contrib;
/// # fn context() { }
@ -383,16 +383,21 @@ impl Template {
/// Returns a response with the Content-Type derived from the template's
/// extension and a fixed-size body containing the rendered template. If
/// rendering fails, an `Err` of `Status::InternalServerError` is returned.
impl Responder<'static> for Template {
fn respond_to(self, req: &Request<'_>) -> response::Result<'static> {
let ctxt = req.guard::<State<'_, ContextManager>>().succeeded().ok_or_else(|| {
error_!("Uninitialized template context: missing fairing.");
info_!("To use templates, you must attach `Template::fairing()`.");
info_!("See the `Template` documentation for more information.");
Status::InternalServerError
})?.inner().context();
impl<'r> Responder<'r> for Template {
fn respond_to(self, req: &'r Request<'_>) -> response::ResultFuture<'r> {
Box::pin(async move {
let (render, content_type) = {
let ctxt = req.guard::<State<'_, ContextManager>>().succeeded().ok_or_else(|| {
error_!("Uninitialized template context: missing fairing.");
info_!("To use templates, you must attach `Template::fairing()`.");
info_!("See the `Template` documentation for more information.");
Status::InternalServerError
})?.inner().context();
let (render, content_type) = self.finalize(&ctxt)?;
Content(content_type, render).respond_to(req)
self.finalize(&ctxt)?
};
Content(content_type, render).respond_to(req).await
})
}
}

View File

@ -1,4 +1,4 @@
#![feature(proc_macro_hygiene)]
#![feature(proc_macro_hygiene, async_await)]
#[macro_use]
#[cfg(feature = "helmet")]

View File

@ -1,4 +1,4 @@
#![feature(proc_macro_hygiene)]
#![feature(proc_macro_hygiene, async_await)]
#[cfg(feature = "serve")]
mod static_tests {
@ -59,7 +59,7 @@ mod static_tests {
let mut file = File::open(path).expect("open file");
let mut expected_contents = String::new();
file.read_to_string(&mut expected_contents).expect("read file");
assert_eq!(response.body_string(), Some(expected_contents));
assert_eq!(response.body_string_wait(), Some(expected_contents));
} else {
assert_eq!(response.status(), Status::NotFound);
}
@ -135,11 +135,11 @@ mod static_tests {
let mut response = client.get("/default/ireallydontexist").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().unwrap(), "ireallydontexist");
assert_eq!(response.body_string_wait().unwrap(), "ireallydontexist");
let mut response = client.get("/default/idont/exist").dispatch();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().unwrap(), "idont/exist");
assert_eq!(response.body_string_wait().unwrap(), "idont/exist");
assert_all(&client, "both", REGULAR_FILES, true);
assert_all(&client, "both", HIDDEN_FILES, true);

View File

@ -1,4 +1,4 @@
#![feature(proc_macro_hygiene)]
#![feature(proc_macro_hygiene, async_await)]
#[cfg(feature = "templates")]
#[macro_use] extern crate rocket;

View File

@ -137,6 +137,7 @@ pub use crate::router::Route;
pub use crate::request::{Request, State};
pub use crate::catcher::Catcher;
pub use crate::rocket::Rocket;
pub use ext::AsyncReadExt;
/// Alias to [`Rocket::ignite()`] Creates a new instance of `Rocket`.
pub fn ignite() -> Rocket {

View File

@ -30,7 +30,7 @@ fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) {
};
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some(expected_body));
assert_eq!(response.body_string_wait(), Some(expected_body));
}
#[test]

View File

@ -33,7 +33,7 @@ fn test_root() {
let expected = Template::show(client.rocket(), "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string(), Some(expected));
assert_eq!(response.body_string_wait(), Some(expected));
});
}
}
@ -51,7 +51,7 @@ fn test_name() {
let expected = Template::show(client.rocket(), "index", &context).unwrap();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some(expected));
assert_eq!(response.body_string_wait(), Some(expected));
});
}
@ -64,6 +64,6 @@ fn test_404() {
let expected = Template::show(client.rocket(), "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string(), Some(expected));
assert_eq!(response.body_string_wait(), Some(expected));
});
}

View File

@ -10,13 +10,13 @@ fn bad_get_put() {
let mut res = client.get("/message/99").header(ContentType::JSON).dispatch();
assert_eq!(res.status(), Status::NotFound);
let body = res.body_string().unwrap();
let body = res.body_string_wait().unwrap();
assert!(body.contains("error"));
assert!(body.contains("Resource was not found."));
// Try to get a message with an invalid ID.
let mut res = client.get("/message/hi").header(ContentType::JSON).dispatch();
let body = res.body_string().unwrap();
let body = res.body_string_wait().unwrap();
assert_eq!(res.status(), Status::NotFound);
assert!(body.contains("error"));
@ -52,7 +52,7 @@ fn post_get_put_get() {
// Check that the message exists with the correct contents.
let mut res = client.get("/message/1").header(ContentType::JSON).dispatch();
assert_eq!(res.status(), Status::Ok);
let body = res.body().unwrap().into_string().unwrap();
let body = res.body_string_wait().unwrap();
assert!(body.contains("Hello, world!"));
// Change the message contents.
@ -66,7 +66,7 @@ fn post_get_put_get() {
// Check that the message exists with the updated contents.
let mut res = client.get("/message/1").header(ContentType::JSON).dispatch();
assert_eq!(res.status(), Status::Ok);
let body = res.body().unwrap().into_string().unwrap();
let body = res.body_string_wait().unwrap();
assert!(!body.contains("Hello, world!"));
assert!(body.contains("Bye bye, world!"));
}

View File

@ -35,7 +35,7 @@ fn can_login() {
let client = Client::new(rocket()).unwrap();
let mut response = client.get("/login").dispatch();
let body = response.body_string().unwrap();
let body = response.body_string_wait().unwrap();
assert_eq!(response.status(), Status::Ok);
assert!(body.contains("Please login to continue."));
}
@ -54,7 +54,7 @@ fn login_logout_succeeds() {
// Ensure we're logged in.
let mut response = client.get("/").cookie(login_cookie.clone()).dispatch();
let body = response.body_string().unwrap();
let body = response.body_string_wait().unwrap();
assert_eq!(response.status(), Status::Ok);
assert!(body.contains("Logged in with user ID 1"));

View File

@ -13,7 +13,7 @@ fn test_query_file<T> (path: &str, file: T, status: Status)
let mut response = client.get(path).dispatch();
assert_eq!(response.status(), status);
let body_data = response.body().and_then(|body| body.into_bytes());
let body_data = response.body_bytes_wait();
if let Some(filename) = file.into() {
let expected_data = read_file_content(filename);
assert!(body_data.map_or(false, |s| s == expected_data));

View File

@ -32,7 +32,7 @@ fn test_root() {
let expected = Template::show(client.rocket(), "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string(), Some(expected));
assert_eq!(response.body_string_wait(), Some(expected));
});
}
}
@ -48,7 +48,7 @@ fn test_name() {
let expected = Template::show(client.rocket(), "index", &context).unwrap();
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string(), Some(expected));
assert_eq!(response.body_string_wait(), Some(expected));
});
}
@ -61,6 +61,6 @@ fn test_404() {
let expected = Template::show(client.rocket(), "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string(), Some(expected));
assert_eq!(response.body_string_wait(), Some(expected));
});
}