diff --git a/Cargo.toml b/Cargo.toml index cdd48faa..80842de1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,28 +8,25 @@ members = [ "contrib/", "examples/cookies", "examples/errors", - "examples/extended_validation", - "examples/forms", + "examples/form_validation", "examples/hello_person", "examples/query_params", "examples/hello_world", "examples/manual_routes", "examples/optional_redirect", - "examples/optional_result", "examples/redirect", "examples/static_files", "examples/todo", "examples/content_types", - "examples/hello_ranks", + "examples/ranking", "examples/testing", - "examples/from_request", + "examples/request_guard", "examples/stream", "examples/json", "examples/msgpack", "examples/handlebars_templates", "examples/form_kitchen_sink", "examples/config", - "examples/hello_alt_methods", "examples/raw_upload", "examples/pastebin", "examples/state", @@ -37,6 +34,6 @@ members = [ "examples/uuid", "examples/session", "examples/raw_sqlite", - "examples/hello_tls", + "examples/tls", "examples/fairings", ] diff --git a/README.md b/README.md index b395a2c4..1693d8f7 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ example, the tests for routing can be found at the bottom of the Code generation tests can be found in `codegen/tests`. We use the [compiletest](https://crates.io/crates/compiletest_rs) library, which was extracted from `rustc`, for testing. See the [compiler test -documentation](https://github.com/rust-lang/rust/blob/master/COMPILER_TESTS.md) +documentation](https://github.com/rust-lang/rust/blob/master/src/test/COMPILER_TESTS.md) for information on how to write compiler tests. ## Documentation diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 67cdb8a4..9db38190 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -22,5 +22,5 @@ log = "0.3" compiletest_rs = "0.2" [build-dependencies] -yansi = "0.2" +yansi = "0.3" version_check = "0.1.2" diff --git a/contrib/Cargo.toml b/contrib/Cargo.toml index ae7347aa..59bda380 100644 --- a/contrib/Cargo.toml +++ b/contrib/Cargo.toml @@ -33,6 +33,6 @@ serde_json = { version = "1.0", optional = true } rmp-serde = { version = "^0.13", optional = true } # Templating dependencies only. -handlebars = { version = "^0.26.1", optional = true } +handlebars = { version = "^0.27", optional = true } glob = { version = "^0.2", optional = true } tera = { version = "^0.10", optional = true } diff --git a/contrib/src/lib.rs b/contrib/src/lib.rs index 795a90e4..e307a10e 100644 --- a/contrib/src/lib.rs +++ b/contrib/src/lib.rs @@ -3,6 +3,9 @@ #![cfg_attr(feature = "templates", feature(associated_consts))] #![cfg_attr(feature = "templates", feature(struct_field_attributes))] +// TODO: Version URLs. +#![doc(html_root_url = "https://api.rocket.rs/rocket_contrib/")] + //! This crate contains officially sanctioned contributor libraries that provide //! functionality commonly used by Rocket applications. //! diff --git a/examples/config/tests/common/mod.rs b/examples/config/tests/common/mod.rs index 1149753a..5eb8f5f3 100644 --- a/examples/config/tests/common/mod.rs +++ b/examples/config/tests/common/mod.rs @@ -1,9 +1,9 @@ use rocket::{self, State}; use rocket::fairing::AdHoc; use rocket::config::{self, Config, Environment}; -use rocket::http::{Method, Status}; +use rocket::http::Status; use rocket::LoggingLevel; -use rocket::testing::MockRequest; +use rocket::local::Client; struct LocalConfig(Config); @@ -55,8 +55,6 @@ pub fn test_config(environment: Environment) { // environment in `ignite()`. We'll read this back in the handler to config. ::std::env::set_var("ROCKET_ENV", environment.to_string()); - // FIXME: launch fairings aren't run during tests since...the Rocket isn't - // being launch let rocket = rocket::ignite() .attach(AdHoc::on_attach(|rocket| { println!("Attaching local config."); @@ -65,7 +63,7 @@ pub fn test_config(environment: Environment) { })) .mount("/", routes![check_config]); - let mut request = MockRequest::new(Method::Get, "/check_config"); - let response = request.dispatch_with(&rocket); + let client = Client::new(rocket).unwrap(); + let response = client.get("/check_config").dispatch(); assert_eq!(response.status(), Status::Ok); } diff --git a/examples/content_types/src/tests.rs b/examples/content_types/src/tests.rs index c15ed0f6..15f7fa89 100644 --- a/examples/content_types/src/tests.rs +++ b/examples/content_types/src/tests.rs @@ -2,7 +2,7 @@ use super::rocket; use super::serde_json; use super::Person; use rocket::http::{Accept, ContentType, Header, MediaType, Method, Status}; -use rocket::testing::MockRequest; +use rocket::local::Client; fn test(method: Method, uri: &str, header: H, status: Status, body: String) where H: Into> @@ -10,9 +10,9 @@ fn test(method: Method, uri: &str, header: H, status: Status, body: String) let rocket = rocket::ignite() .mount("/hello", routes![super::get_hello, super::post_hello]) .catch(errors![super::not_found]); - let mut request = MockRequest::new(method, uri).header(header); - let mut response = request.dispatch_with(&rocket); + let client = Client::new(rocket).unwrap(); + let mut response = client.req(method, uri).header(header).dispatch(); assert_eq!(response.status(), status); assert_eq!(response.body_string(), Some(body)); } diff --git a/examples/cookies/src/tests.rs b/examples/cookies/src/tests.rs index 329facc9..95b63a7d 100644 --- a/examples/cookies/src/tests.rs +++ b/examples/cookies/src/tests.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use super::rocket; -use rocket::testing::MockRequest; +use rocket::local::Client; use rocket::http::*; use rocket_contrib::Template; @@ -9,11 +9,12 @@ const TEMPLATE_ROOT: &'static str = "templates/"; #[test] fn test_submit() { - let rocket = rocket(); - let mut request = MockRequest::new(Method::Post, "/submit") + let client = Client::new(rocket()).unwrap(); + let response = client.post("/submit") .header(ContentType::Form) - .body("message=Hello from Rocket!"); - let response = request.dispatch_with(&rocket); + .body("message=Hello from Rocket!") + .dispatch(); + let cookie_headers: Vec<_> = response.headers().get("Set-Cookie").collect(); let location_headers: Vec<_> = response.headers().get("Location").collect(); @@ -23,33 +24,26 @@ fn test_submit() { } fn test_body(optional_cookie: Option>, expected_body: String) { - let rocket = rocket(); - let mut request = MockRequest::new(Method::Get, "/"); - // Attach a cookie if one is given. - if let Some(cookie) = optional_cookie { - request = request.cookie(cookie); - } + let client = Client::new(rocket()).unwrap(); + let mut response = match optional_cookie { + Some(cookie) => client.get("/").cookie(cookie).dispatch(), + None => client.get("/").dispatch(), + }; - let mut response = request.dispatch_with(&rocket); assert_eq!(response.status(), Status::Ok); assert_eq!(response.body_string(), Some(expected_body)); } #[test] fn test_index() { - // Render the template with an empty context to test against. + // Render the template with an empty context. let mut context: HashMap<&str, &str> = HashMap::new(); let template = Template::show(TEMPLATE_ROOT, "index", &context).unwrap(); - - // Test the route without sending the "message" cookie. test_body(None, template); // Render the template with a context that contains the message. context.insert("message", "Hello from Rocket!"); - - // Test the route with the "message" cookie. - let cookie = Cookie::new("message", "Hello from Rocket!"); let template = Template::show(TEMPLATE_ROOT, "index", &context).unwrap(); - test_body(Some(cookie), template); + test_body(Some(Cookie::new("message", "Hello from Rocket!")), template); } diff --git a/examples/errors/src/tests.rs b/examples/errors/src/tests.rs index 9d12e8a2..fc0b5523 100644 --- a/examples/errors/src/tests.rs +++ b/examples/errors/src/tests.rs @@ -1,14 +1,14 @@ use super::rocket; -use rocket::testing::MockRequest; -use rocket::http::{Method, Status}; +use rocket::local::Client; +use rocket::http::Status; fn test(uri: &str, status: Status, body: String) { let rocket = rocket::ignite() .mount("/", routes![super::hello]) .catch(errors![super::not_found]); - let mut req = MockRequest::new(Method::Get, uri); - let mut response = req.dispatch_with(&rocket); + let client = Client::new(rocket).unwrap(); + let mut response = client.get(uri).dispatch(); assert_eq!(response.status(), status); assert_eq!(response.body_string(), Some(body)); } diff --git a/examples/extended_validation/Cargo.toml b/examples/extended_validation/Cargo.toml deleted file mode 100644 index dd719fa7..00000000 --- a/examples/extended_validation/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "extended_validation" -version = "0.0.0" -workspace = "../../" - -[dependencies] -rocket = { path = "../../lib" } -rocket_codegen = { path = "../../codegen" } diff --git a/examples/fairings/src/tests.rs b/examples/fairings/src/tests.rs index a50e2e4f..37622e50 100644 --- a/examples/fairings/src/tests.rs +++ b/examples/fairings/src/tests.rs @@ -1,46 +1,38 @@ use super::rocket; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; #[test] fn rewrite_get_put() { - let rocket = rocket(); - let mut req = MockRequest::new(Get, "/"); - let mut response = req.dispatch_with(&rocket); + let client = Client::new(rocket()).unwrap(); + let mut response = client.get("/").dispatch(); assert_eq!(response.body_string(), Some("Hello, fairings!".into())); } #[test] fn counts() { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); // Issue 1 GET request. - let mut req = MockRequest::new(Get, "/"); - req.dispatch_with(&rocket); + client.get("/").dispatch(); // Check the GET count, taking into account _this_ GET request. - let mut req = MockRequest::new(Get, "/counts"); - let mut response = req.dispatch_with(&rocket); + let mut response = client.get("/counts").dispatch(); assert_eq!(response.body_string(), Some("Get: 2\nPost: 0".into())); // Issue 1 more GET request and a POST. - let mut req = MockRequest::new(Get, "/"); - req.dispatch_with(&rocket); - let mut req = MockRequest::new(Post, "/"); - req.dispatch_with(&rocket); + client.get("/").dispatch(); + client.post("/").dispatch(); // Check the counts. - let mut req = MockRequest::new(Get, "/counts"); - let mut response = req.dispatch_with(&rocket); + let mut response = client.get("/counts").dispatch(); assert_eq!(response.body_string(), Some("Get: 4\nPost: 1".into())); } #[test] fn token() { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); // Ensure the token is '123', which is what we have in `Rocket.toml`. - let mut req = MockRequest::new(Get, "/token"); - let mut res = req.dispatch_with(&rocket); + let mut res = client.get("/token").dispatch(); assert_eq!(res.body_string(), Some("123".into())); } diff --git a/examples/form_kitchen_sink/src/main.rs b/examples/form_kitchen_sink/src/main.rs index d4f60df3..4d8a23ea 100644 --- a/examples/form_kitchen_sink/src/main.rs +++ b/examples/form_kitchen_sink/src/main.rs @@ -33,19 +33,19 @@ impl<'v> FromFormValue<'v> for FormOption { } #[derive(Debug, FromForm)] -struct FormInput { +struct FormInput<'r> { checkbox: bool, number: usize, #[form(field = "type")] radio: FormOption, - password: String, + password: &'r RawStr, #[form(field = "textarea")] text_area: String, select: FormOption, } #[post("/", data = "")] -fn sink(sink: Result, Option>) -> String { +fn sink<'r>(sink: Result>, Option>) -> String { match sink { Ok(form) => format!("{:?}", form.get()), Err(Some(f)) => format!("Invalid form input: {}", f), diff --git a/examples/form_kitchen_sink/src/tests.rs b/examples/form_kitchen_sink/src/tests.rs index 94e04a3d..4274d629 100644 --- a/examples/form_kitchen_sink/src/tests.rs +++ b/examples/form_kitchen_sink/src/tests.rs @@ -1,10 +1,8 @@ use std::fmt; use super::{rocket, FormInput, FormOption}; -use rocket::Rocket; -use rocket::testing::MockRequest; +use rocket::local::Client; use rocket::http::ContentType; -use rocket::http::Method::*; impl fmt::Display for FormOption { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -16,167 +14,169 @@ impl fmt::Display for FormOption { } } -fn assert_form_eq(rocket: &Rocket, form_str: &str, expected: String) { - let mut req = MockRequest::new(Post, "/") +fn assert_form_eq(client: &Client, form_str: &str, expected: String) { + let mut res = client.post("/") .header(ContentType::Form) - .body(form_str); - let mut res = req.dispatch_with(&rocket); + .body(form_str) + .dispatch(); + assert_eq!(res.body_string(), Some(expected)); } -fn assert_valid_form(rocket: &Rocket, input: &FormInput) { +fn assert_valid_form(client: &Client, input: &FormInput) { let f = format!("checkbox={}&number={}&type={}&password={}&textarea={}&select={}", input.checkbox, input.number, input.radio, input.password, input.text_area, input.select); - assert_form_eq(rocket, &f, format!("{:?}", input)); + assert_form_eq(client, &f, format!("{:?}", input)); } -fn assert_valid_raw_form(rocket: &Rocket, form_str: &str, input: &FormInput) { - assert_form_eq(rocket, form_str, format!("{:?}", input)); +fn assert_valid_raw_form(client: &Client, form_str: &str, input: &FormInput) { + assert_form_eq(client, form_str, format!("{:?}", input)); } #[test] fn test_good_forms() { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); let mut input = FormInput { checkbox: true, number: 310, radio: FormOption::A, - password: "beep".to_string(), + password: "beep".into(), text_area: "bop".to_string(), select: FormOption::B }; - assert_valid_form(&rocket, &input); + assert_valid_form(&client, &input); input.checkbox = false; - assert_valid_form(&rocket, &input); + assert_valid_form(&client, &input); input.number = 0; - assert_valid_form(&rocket, &input); + assert_valid_form(&client, &input); input.number = 120; - assert_valid_form(&rocket, &input); + assert_valid_form(&client, &input); input.number = 133; - assert_valid_form(&rocket, &input); + assert_valid_form(&client, &input); input.radio = FormOption::B; - assert_valid_form(&rocket, &input); + assert_valid_form(&client, &input); input.radio = FormOption::C; - assert_valid_form(&rocket, &input); + assert_valid_form(&client, &input); - input.password = "".to_string(); - assert_valid_form(&rocket, &input); - input.password = "----90138490285u2o3hndslkv".to_string(); - assert_valid_form(&rocket, &input); - input.password = "hi".to_string(); - assert_valid_form(&rocket, &input); + input.password = "".into(); + assert_valid_form(&client, &input); + input.password = "----90138490285u2o3hndslkv".into(); + assert_valid_form(&client, &input); + input.password = "hi".into(); + assert_valid_form(&client, &input); input.text_area = "".to_string(); - assert_valid_form(&rocket, &input); + assert_valid_form(&client, &input); input.text_area = "----90138490285u2o3hndslkv".to_string(); - assert_valid_form(&rocket, &input); + assert_valid_form(&client, &input); input.text_area = "hey".to_string(); - assert_valid_form(&rocket, &input); + assert_valid_form(&client, &input); input.select = FormOption::A; - assert_valid_form(&rocket, &input); + assert_valid_form(&client, &input); input.select = FormOption::C; - assert_valid_form(&rocket, &input); + assert_valid_form(&client, &input); // checkbox need not be present; defaults to false; accepts 'on' and 'off' - assert_valid_raw_form(&rocket, + assert_valid_raw_form(&client, "number=133&type=c&password=hi&textarea=hey&select=c", &input); - assert_valid_raw_form(&rocket, + assert_valid_raw_form(&client, "checkbox=off&number=133&type=c&password=hi&textarea=hey&select=c", &input); input.checkbox = true; - assert_valid_raw_form(&rocket, + assert_valid_raw_form(&client, "checkbox=on&number=133&type=c&password=hi&textarea=hey&select=c", &input); } -fn assert_invalid_form(rocket: &Rocket, vals: &mut [&str; 6]) { +fn assert_invalid_form(client: &Client, vals: &mut [&str; 6]) { let s = format!("checkbox={}&number={}&type={}&password={}&textarea={}&select={}", vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]); - assert_form_eq(rocket, &s, format!("Invalid form input: {}", s)); + assert_form_eq(client, &s, format!("Invalid form input: {}", s)); *vals = ["true", "1", "a", "hi", "hey", "b"]; } -fn assert_invalid_raw_form(rocket: &Rocket, form_str: &str) { - assert_form_eq(rocket, form_str, format!("Invalid form input: {}", form_str)); +fn assert_invalid_raw_form(client: &Client, form_str: &str) { + assert_form_eq(client, form_str, format!("Invalid form input: {}", form_str)); } #[test] fn check_semantically_invalid_forms() { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); let mut form_vals = ["true", "1", "a", "hi", "hey", "b"]; form_vals[0] = "not true"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[0] = "bing"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[0] = "true0"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[0] = " false"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[1] = "-1"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[1] = "1e10"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[1] = "-1-1"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[1] = "NaN"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[2] = "A"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[2] = "B"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[2] = "d"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[2] = "100"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[2] = ""; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); // password and textarea are always valid, so we skip them form_vals[5] = "A"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[5] = "b "; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[5] = "d"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[5] = "-a"; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); form_vals[5] = ""; - assert_invalid_form(&rocket, &mut form_vals); + assert_invalid_form(&client, &mut form_vals); // now forms with missing fields - assert_invalid_raw_form(&rocket, "number=10&type=a&password=hi&textarea=hey"); - assert_invalid_raw_form(&rocket, "number=10&radio=a&password=hi&textarea=hey&select=b"); - assert_invalid_raw_form(&rocket, "number=10&password=hi&select=b"); - assert_invalid_raw_form(&rocket, "number=10&select=b"); - assert_invalid_raw_form(&rocket, "password=hi&select=b"); - assert_invalid_raw_form(&rocket, "password=hi"); - assert_invalid_raw_form(&rocket, ""); + assert_invalid_raw_form(&client, "number=10&type=a&password=hi&textarea=hey"); + assert_invalid_raw_form(&client, "number=10&radio=a&password=hi&textarea=hey&select=b"); + assert_invalid_raw_form(&client, "number=10&password=hi&select=b"); + assert_invalid_raw_form(&client, "number=10&select=b"); + assert_invalid_raw_form(&client, "password=hi&select=b"); + assert_invalid_raw_form(&client, "password=hi"); + assert_invalid_raw_form(&client, ""); } #[test] fn check_structurally_invalid_forms() { - let rocket = rocket(); - assert_invalid_raw_form(&rocket, "==&&&&&&=="); - assert_invalid_raw_form(&rocket, "a&=b"); - assert_invalid_raw_form(&rocket, "="); + let client = Client::new(rocket()).unwrap(); + assert_invalid_raw_form(&client, "==&&&&&&=="); + assert_invalid_raw_form(&client, "a&=b"); + assert_invalid_raw_form(&client, "="); } #[test] fn check_bad_utf8() { + let client = Client::new(rocket()).unwrap(); unsafe { let bad_str = ::std::str::from_utf8_unchecked(b"a=\xff"); - assert_form_eq(&rocket(), bad_str, "Form input was invalid UTF8.".into()); + assert_form_eq(&client, bad_str, "Form input was invalid UTF8.".into()); } } diff --git a/examples/optional_result/Cargo.toml b/examples/form_validation/Cargo.toml similarity index 84% rename from examples/optional_result/Cargo.toml rename to examples/form_validation/Cargo.toml index f0bd8b4e..4687cb5c 100644 --- a/examples/optional_result/Cargo.toml +++ b/examples/form_validation/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "optional_result" +name = "form_validation" version = "0.0.0" workspace = "../../" diff --git a/examples/extended_validation/src/files.rs b/examples/form_validation/src/files.rs similarity index 100% rename from examples/extended_validation/src/files.rs rename to examples/form_validation/src/files.rs diff --git a/examples/extended_validation/src/main.rs b/examples/form_validation/src/main.rs similarity index 100% rename from examples/extended_validation/src/main.rs rename to examples/form_validation/src/main.rs diff --git a/examples/extended_validation/src/tests.rs b/examples/form_validation/src/tests.rs similarity index 82% rename from examples/extended_validation/src/tests.rs rename to examples/form_validation/src/tests.rs index db4f91ad..8ff1db82 100644 --- a/examples/extended_validation/src/tests.rs +++ b/examples/form_validation/src/tests.rs @@ -1,24 +1,20 @@ -use rocket::testing::MockRequest; -use rocket::http::Method::*; -use rocket::http::{ContentType, Status}; - use super::rocket; +use rocket::local::Client; +use rocket::http::{ContentType, Status}; fn test_login(user: &str, pass: &str, age: &str, status: Status, body: T) where T: Into> { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); let query = format!("username={}&password={}&age={}", user, pass, age); - - let mut req = MockRequest::new(Post, "/login") + let mut response = client.post("/login") .header(ContentType::Form) - .body(&query); + .body(&query) + .dispatch(); - let mut response = req.dispatch_with(&rocket); assert_eq!(response.status(), status); - - let body_str = response.body_string(); if let Some(expected_str) = body.into() { + let body_str = response.body_string(); assert!(body_str.map_or(false, |s| s.contains(expected_str))); } } @@ -48,12 +44,12 @@ fn test_invalid_age() { } fn check_bad_form(form_str: &str, status: Status) { - let rocket = rocket(); - let mut req = MockRequest::new(Post, "/login") + let client = Client::new(rocket()).unwrap(); + let response = client.post("/login") .header(ContentType::Form) - .body(form_str); + .body(form_str) + .dispatch(); - let response = req.dispatch_with(&rocket); assert_eq!(response.status(), status); } @@ -82,5 +78,6 @@ fn test_bad_form_missing_fields() { #[test] fn test_bad_form_additional_fields() { - check_bad_form("username=Sergio&password=pass&age=30&addition=1", Status::UnprocessableEntity); + check_bad_form("username=Sergio&password=pass&age=30&addition=1", + Status::UnprocessableEntity); } diff --git a/examples/extended_validation/static/index.html b/examples/form_validation/static/index.html similarity index 100% rename from examples/extended_validation/static/index.html rename to examples/form_validation/static/index.html diff --git a/examples/forms/src/files.rs b/examples/forms/src/files.rs deleted file mode 100644 index 903760b8..00000000 --- a/examples/forms/src/files.rs +++ /dev/null @@ -1,14 +0,0 @@ -use rocket::response::NamedFile; - -use std::io; -use std::path::{Path, PathBuf}; - -#[get("/")] -fn index() -> io::Result { - NamedFile::open("static/index.html") -} - -#[get("/", rank = 5)] -fn files(file: PathBuf) -> io::Result { - NamedFile::open(Path::new("static/").join(file)) -} diff --git a/examples/forms/src/main.rs b/examples/forms/src/main.rs deleted file mode 100644 index 5f7e4ed9..00000000 --- a/examples/forms/src/main.rs +++ /dev/null @@ -1,52 +0,0 @@ -#![feature(plugin, custom_derive)] -#![plugin(rocket_codegen)] - -extern crate rocket; - -mod files; -#[cfg(test)] mod tests; - -use rocket::request::Form; -use rocket::response::Redirect; -use rocket::http::RawStr; - -#[derive(FromForm)] -struct UserLogin<'r> { - username: &'r RawStr, - password: String, - age: Result, -} - -#[post("/login", data = "")] -fn login<'a>(user_form: Form<'a, UserLogin<'a>>) -> Result { - let user = user_form.get(); - match user.age { - Ok(age) if age < 21 => return Err(format!("Sorry, {} is too young!", age)), - Ok(age) if age > 120 => return Err(format!("Are you sure you're {}?", age)), - Err(e) => return Err(format!("'{}' is not a valid integer.", e)), - Ok(_) => { /* Move along, adult. */ } - }; - - if user.username == "Sergio" { - match user.password.as_str() { - "password" => Ok(Redirect::to("/user/Sergio")), - _ => Err("Wrong password!".to_string()) - } - } else { - Err(format!("Unrecognized user, '{}'.", user.username)) - } -} - -#[get("/user/")] -fn user_page(username: String) -> String { - format!("This is {}'s page.", username) -} - -fn rocket() -> rocket::Rocket { - rocket::ignite() - .mount("/", routes![files::index, files::files, user_page, login]) -} - -fn main() { - rocket().launch(); -} diff --git a/examples/forms/src/tests.rs b/examples/forms/src/tests.rs deleted file mode 100644 index 0ef8b559..00000000 --- a/examples/forms/src/tests.rs +++ /dev/null @@ -1,62 +0,0 @@ -use rocket::testing::MockRequest; -use rocket::http::Method::*; -use rocket::http::{ContentType, Status}; - -use super::rocket; - -fn test_login(username: &str, password: &str, age: isize, status: Status, - body: Option<&'static str>) { - let rocket = rocket(); - let mut req = MockRequest::new(Post, "/login") - .header(ContentType::Form) - .body(&format!("username={}&password={}&age={}", username, password, age)); - - let mut response = req.dispatch_with(&rocket); - let body_str = response.body_string(); - - println!("Checking: {:?}/{:?}/{:?}/{:?}", username, password, age, body_str); - assert_eq!(response.status(), status); - - if let Some(string) = body { - assert!(body_str.map_or(true, |s| s.contains(string))); - } -} - -#[test] -fn test_good_login() { - test_login("Sergio", "password", 30, Status::SeeOther, None); -} - -const OK: Status = self::Status::Ok; - -#[test] -fn test_bad_login() { - test_login("Sergio", "password", 20, OK, Some("Sorry, 20 is too young!")); - test_login("Sergio", "password", 200, OK, Some("Are you sure you're 200?")); - test_login("Sergio", "jk", -100, OK, Some("'-100' is not a valid integer.")); - test_login("Sergio", "ok", 30, OK, Some("Wrong password!")); - test_login("Mike", "password", 30, OK, Some("Unrecognized user, 'Mike'.")); -} - -fn check_bad_form(form_str: &str, status: Status) { - let rocket = rocket(); - let mut req = MockRequest::new(Post, "/login") - .header(ContentType::Form) - .body(form_str); - - let response = req.dispatch_with(&rocket); - assert_eq!(response.status(), status); -} - -#[test] -fn test_bad_form() { - check_bad_form("&", Status::BadRequest); - check_bad_form("=", Status::BadRequest); - check_bad_form("&&&===&", Status::BadRequest); - - check_bad_form("username=Sergio", Status::UnprocessableEntity); - check_bad_form("username=Sergio&", Status::UnprocessableEntity); - check_bad_form("username=Sergio&pass=something", Status::UnprocessableEntity); - check_bad_form("user=Sergio&password=something", Status::UnprocessableEntity); - check_bad_form("password=something", Status::UnprocessableEntity); -} diff --git a/examples/forms/static/index.html b/examples/forms/static/index.html deleted file mode 100644 index 66bf2339..00000000 --- a/examples/forms/static/index.html +++ /dev/null @@ -1,8 +0,0 @@ -

Login

- -
- Username: - Password: - Age: - -
diff --git a/examples/from_request/Cargo.toml b/examples/from_request/Cargo.toml deleted file mode 100644 index f6551ac2..00000000 --- a/examples/from_request/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "from_request" -version = "0.0.0" -workspace = "../../" - -[dependencies] -rocket = { path = "../../lib" } -rocket_codegen = { path = "../../codegen" } diff --git a/examples/handlebars_templates/src/tests.rs b/examples/handlebars_templates/src/tests.rs index 977142f1..9d17649b 100644 --- a/examples/handlebars_templates/src/tests.rs +++ b/examples/handlebars_templates/src/tests.rs @@ -1,17 +1,15 @@ use super::rocket; -use rocket::testing::MockRequest; +use rocket::local::{Client, LocalResponse}; use rocket::http::Method::*; use rocket::http::Status; -use rocket::Response; use rocket_contrib::Template; const TEMPLATE_ROOT: &'static str = "templates/"; macro_rules! dispatch { ($method:expr, $path:expr, $test_fn:expr) => ({ - let rocket = rocket(); - let mut req = MockRequest::new($method, $path); - $test_fn(req.dispatch_with(&rocket)); + let client = Client::new(rocket()).unwrap(); + $test_fn(client.req($method, $path).dispatch()); }) } @@ -19,7 +17,7 @@ macro_rules! dispatch { fn test_root() { // Check that the redirect works. for method in &[Get, Head] { - dispatch!(*method, "/", |mut response: Response| { + dispatch!(*method, "/", |mut response: LocalResponse| { assert_eq!(response.status(), Status::SeeOther); assert!(response.body().is_none()); @@ -30,7 +28,7 @@ fn test_root() { // Check that other request methods are not accepted (and instead caught). for method in &[Post, Put, Delete, Options, Trace, Connect, Patch] { - dispatch!(*method, "/", |mut response: Response| { + dispatch!(*method, "/", |mut response: LocalResponse| { let mut map = ::std::collections::HashMap::new(); map.insert("path", "/"); let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap(); @@ -44,7 +42,7 @@ fn test_root() { #[test] fn test_name() { // Check that the /hello/ route works. - dispatch!(Get, "/hello/Jack", |mut response: Response| { + dispatch!(Get, "/hello/Jack", |mut response: LocalResponse| { let context = super::TemplateContext { name: "Jack".to_string(), items: vec!["One", "Two", "Three"].iter().map(|s| s.to_string()).collect() @@ -59,7 +57,7 @@ fn test_name() { #[test] fn test_404() { // Check that the error catcher works. - dispatch!(Get, "/hello/", |mut response: Response| { + dispatch!(Get, "/hello/", |mut response: LocalResponse| { let mut map = ::std::collections::HashMap::new(); map.insert("path", "/hello/"); diff --git a/examples/hello_alt_methods/Cargo.toml b/examples/hello_alt_methods/Cargo.toml deleted file mode 100644 index d0731a51..00000000 --- a/examples/hello_alt_methods/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "hello_alt_methods" -version = "0.0.0" -workspace = "../../" - -[dependencies] -rocket = { path = "../../lib" } -rocket_codegen = { path = "../../codegen" } diff --git a/examples/hello_alt_methods/src/main.rs b/examples/hello_alt_methods/src/main.rs deleted file mode 100644 index fbc8a7b1..00000000 --- a/examples/hello_alt_methods/src/main.rs +++ /dev/null @@ -1,26 +0,0 @@ -#![feature(plugin)] -#![plugin(rocket_codegen)] - -extern crate rocket; - -use std::io; - -use rocket::response::NamedFile; - -#[cfg(test)] mod tests; - -#[get("/")] -fn index() -> io::Result { - NamedFile::open("static/index.html") -} - -#[put("/")] -fn put() -> &'static str { - "Hello, PUT request!" -} - -fn main() { - rocket::ignite() - .mount("/", routes![index, put]) - .launch(); -} diff --git a/examples/hello_alt_methods/src/tests.rs b/examples/hello_alt_methods/src/tests.rs deleted file mode 100644 index 47cd1d6e..00000000 --- a/examples/hello_alt_methods/src/tests.rs +++ /dev/null @@ -1,25 +0,0 @@ -use super::rocket; -use rocket::testing::MockRequest; -use rocket::http::Method; -use rocket::http::Status; - -fn test(method: Method, status: Status, body_prefix: Option<&str>) { - let rocket = rocket::ignite() - .mount("/", routes![super::index, super::put]); - - let mut req = MockRequest::new(method, "/"); - let mut response = req.dispatch_with(&rocket); - - assert_eq!(response.status(), status); - if let Some(expected_body_string) = body_prefix { - let body_str = response.body_string().unwrap(); - assert!(body_str.starts_with(expected_body_string)); - } -} - -#[test] -fn hello_world_alt_methods() { - test(Method::Get, Status::Ok, Some("")); - test(Method::Put, Status::Ok, Some("Hello, PUT request!")); - test(Method::Post, Status::NotFound, None); -} diff --git a/examples/hello_alt_methods/static/index.html b/examples/hello_alt_methods/static/index.html deleted file mode 100644 index d0836aa4..00000000 --- a/examples/hello_alt_methods/static/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - Hello Alt Methods - - -
- - -
- - diff --git a/examples/hello_person/src/tests.rs b/examples/hello_person/src/tests.rs index 2bd6dbc4..e6cd50e2 100644 --- a/examples/hello_person/src/tests.rs +++ b/examples/hello_person/src/tests.rs @@ -1,20 +1,19 @@ use super::rocket; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; use rocket::http::Status; +fn client() -> Client { + Client::new(rocket::ignite().mount("/", routes![super::hello, super::hi])).unwrap() +} + fn test(uri: &str, expected: String) { - let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]); - let mut req = MockRequest::new(Get, uri); - let mut response = req.dispatch_with(&rocket); - assert_eq!(response.body_string(), Some(expected)); + let client = client(); + assert_eq!(client.get(uri).dispatch().body_string(), Some(expected)); } fn test_404(uri: &str) { - let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]); - let mut req = MockRequest::new(Get, uri); - let response = req.dispatch_with(&rocket); - assert_eq!(response.status(), Status::NotFound); + let client = client(); + assert_eq!(client.get(uri).dispatch().status(), Status::NotFound); } #[test] diff --git a/examples/hello_world/src/tests.rs b/examples/hello_world/src/tests.rs index abb31f89..da0b732b 100644 --- a/examples/hello_world/src/tests.rs +++ b/examples/hello_world/src/tests.rs @@ -1,12 +1,10 @@ use super::rocket; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; #[test] fn hello_world() { let rocket = rocket::ignite().mount("/", routes![super::hello]); - let mut req = MockRequest::new(Get, "/"); - let mut response = req.dispatch_with(&rocket); - + let client = Client::new(rocket).unwrap(); + let mut response = client.get("/").dispatch(); assert_eq!(response.body_string(), Some("Hello, world!".into())); } diff --git a/examples/json/src/tests.rs b/examples/json/src/tests.rs index dc3a4cdb..8d22448b 100644 --- a/examples/json/src/tests.rs +++ b/examples/json/src/tests.rs @@ -1,95 +1,72 @@ use rocket; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; use rocket::http::{Status, ContentType}; -use rocket::Response; - -macro_rules! run_test { - ($rocket: expr, $req:expr, $test_fn:expr) => ({ - let mut req = $req; - $test_fn(req.dispatch_with($rocket)); - }) -} #[test] fn bad_get_put() { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); // Try to get a message with an ID that doesn't exist. - let req = MockRequest::new(Get, "/message/99").header(ContentType::JSON); - run_test!(&rocket, req, |mut response: Response| { - assert_eq!(response.status(), Status::NotFound); + let mut res = client.get("/message/99").header(ContentType::JSON).dispatch(); + assert_eq!(res.status(), Status::NotFound); - let body = response.body().unwrap().into_string().unwrap(); - assert!(body.contains("error")); - assert!(body.contains("Resource was not found.")); - }); + let body = res.body_string().unwrap(); + assert!(body.contains("error")); + assert!(body.contains("Resource was not found.")); // Try to get a message with an invalid ID. - let req = MockRequest::new(Get, "/message/hi").header(ContentType::JSON); - run_test!(&rocket, req, |mut response: Response| { - assert_eq!(response.status(), Status::NotFound); - let body = response.body().unwrap().into_string().unwrap(); - assert!(body.contains("error")); - }); + let mut res = client.get("/message/hi").header(ContentType::JSON).dispatch(); + let body = res.body_string().unwrap(); + assert_eq!(res.status(), Status::NotFound); + assert!(body.contains("error")); // Try to put a message without a proper body. - let req = MockRequest::new(Put, "/message/80").header(ContentType::JSON); - run_test!(&rocket, req, |response: Response| { - assert_eq!(response.status(), Status::BadRequest); - }); + let res = client.put("/message/80").header(ContentType::JSON).dispatch(); + assert_eq!(res.status(), Status::BadRequest); // Try to put a message for an ID that doesn't exist. - let req = MockRequest::new(Put, "/message/80") + let res = client.put("/message/80") .header(ContentType::JSON) - .body(r#"{ "contents": "Bye bye, world!" }"#); + .body(r#"{ "contents": "Bye bye, world!" }"#) + .dispatch(); - run_test!(&rocket, req, |response: Response| { - assert_eq!(response.status(), Status::NotFound); - }); + assert_eq!(res.status(), Status::NotFound); } #[test] fn post_get_put_get() { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); + // Check that a message with ID 1 doesn't exist. - let req = MockRequest::new(Get, "/message/1").header(ContentType::JSON); - run_test!(&rocket, req, |response: Response| { - assert_eq!(response.status(), Status::NotFound); - }); + let res = client.get("/message/1").header(ContentType::JSON).dispatch(); + assert_eq!(res.status(), Status::NotFound); // Add a new message with ID 1. - let req = MockRequest::new(Post, "/message/1") + let res = client.post("/message/1") .header(ContentType::JSON) - .body(r#"{ "contents": "Hello, world!" }"#); + .body(r#"{ "contents": "Hello, world!" }"#) + .dispatch(); - run_test!(&rocket, req, |response: Response| { - assert_eq!(response.status(), Status::Ok); - }); + assert_eq!(res.status(), Status::Ok); // Check that the message exists with the correct contents. - let req = MockRequest::new(Get, "/message/1") .header(ContentType::JSON); - run_test!(&rocket, req, |mut response: Response| { - assert_eq!(response.status(), Status::Ok); - let body = response.body().unwrap().into_string().unwrap(); - assert!(body.contains("Hello, world!")); - }); + 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(); + assert!(body.contains("Hello, world!")); // Change the message contents. - let req = MockRequest::new(Put, "/message/1") + let res = client.put("/message/1") .header(ContentType::JSON) - .body(r#"{ "contents": "Bye bye, world!" }"#); + .body(r#"{ "contents": "Bye bye, world!" }"#) + .dispatch(); - run_test!(&rocket, req, |response: Response| { - assert_eq!(response.status(), Status::Ok); - }); + assert_eq!(res.status(), Status::Ok); // Check that the message exists with the updated contents. - let req = MockRequest::new(Get, "/message/1") .header(ContentType::JSON); - run_test!(&rocket, req, |mut response: Response| { - assert_eq!(response.status(), Status::Ok); - let body = response.body().unwrap().into_string().unwrap(); - assert!(!body.contains("Hello, world!")); - assert!(body.contains("Bye bye, world!")); - }); + 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(); + assert!(!body.contains("Hello, world!")); + assert!(body.contains("Bye bye, world!")); } diff --git a/examples/managed_queue/src/tests.rs b/examples/managed_queue/src/tests.rs index c6382de7..db859d8f 100644 --- a/examples/managed_queue/src/tests.rs +++ b/examples/managed_queue/src/tests.rs @@ -1,18 +1,13 @@ -use super::rocket; - -use rocket::testing::MockRequest; +use rocket::local::Client; use rocket::http::Status; -use rocket::http::Method::*; #[test] fn test_push_pop() { - let rocket = rocket(); + let client = Client::new(super::rocket()).unwrap(); - let mut req = MockRequest::new(Put, "/push?description=test1"); - let response = req.dispatch_with(&rocket); + let response = client.put("/push?description=test1").dispatch(); assert_eq!(response.status(), Status::Ok); - let mut req = MockRequest::new(Get, "/pop"); - let mut response = req.dispatch_with(&rocket); + let mut response = client.get("/pop").dispatch(); assert_eq!(response.body_string(), Some("test1".to_string())); } diff --git a/examples/manual_routes/src/tests.rs b/examples/manual_routes/src/tests.rs index 2c964c91..98fde69c 100644 --- a/examples/manual_routes/src/tests.rs +++ b/examples/manual_routes/src/tests.rs @@ -1,13 +1,10 @@ use super::*; -use rocket::testing::MockRequest; +use rocket::local::Client; use rocket::http::{ContentType, Status}; -use rocket::http::Method::*; fn test(uri: &str, content_type: ContentType, status: Status, body: String) { - let rocket = rocket(); - let mut request = MockRequest::new(Get, uri).header(content_type); - let mut response = request.dispatch_with(&rocket); - + let client = Client::new(rocket()).unwrap();; + let mut response = client.get(uri).header(content_type).dispatch(); assert_eq!(response.status(), status); assert_eq!(response.body_string(), Some(body)); } @@ -34,21 +31,21 @@ fn test_echo() { #[test] fn test_upload() { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap();; let expected_body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, \ sed do eiusmod tempor incididunt ut labore et dolore \ magna aliqua".to_string(); // Upload the body. - let mut request = MockRequest::new(Post, "/upload") + let response = client.post("/upload") .header(ContentType::Plain) - .body(&expected_body); - let response = request.dispatch_with(&rocket); + .body(&expected_body) + .dispatch(); + assert_eq!(response.status(), Status::Ok); // Ensure we get back the same body. - let mut request = MockRequest::new(Get, "/upload"); - let mut response = request.dispatch_with(&rocket); + let mut response = client.get("/upload").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.body_string(), Some(expected_body)); } diff --git a/examples/msgpack/src/tests.rs b/examples/msgpack/src/tests.rs index 0419a075..de508ce4 100644 --- a/examples/msgpack/src/tests.rs +++ b/examples/msgpack/src/tests.rs @@ -1,8 +1,6 @@ use rocket; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; use rocket::http::{Status, ContentType}; -use rocket::Response; #[derive(Serialize, Deserialize)] struct Message { @@ -10,35 +8,26 @@ struct Message { contents: String } -macro_rules! run_test { - ($rocket: expr, $req:expr, $test_fn:expr) => ({ - let mut req = $req; - $test_fn(req.dispatch_with($rocket)); - }) -} - #[test] fn msgpack_get() { - let rocket = rocket(); - let req = MockRequest::new(Get, "/message/1").header(ContentType::MsgPack); - run_test!(&rocket, req, |mut response: Response| { - assert_eq!(response.status(), Status::Ok); - let body = response.body().unwrap().into_bytes().unwrap(); - // Represents a message of `[1, "Hello, world!"]` - assert_eq!(&body, &[146, 1, 173, 72, 101, 108, 108, 111, 44, 32, 119, 111, - 114, 108, 100, 33]); - }); + let client = Client::new(rocket()).unwrap(); + let mut res = client.get("/message/1").header(ContentType::MsgPack).dispatch(); + assert_eq!(res.status(), Status::Ok); + + // Check that the message is `[1, "Hello, world!"]` + assert_eq!(&res.body_bytes().unwrap(), + &[146, 1, 173, 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]); } #[test] fn msgpack_post() { - let rocket = rocket(); - let req = MockRequest::new(Post, "/message") + // Dispatch request with a message of `[2, "Goodbye, world!"]`. + let client = Client::new(rocket()).unwrap(); + let mut res = client.post("/message") .header(ContentType::MsgPack) - // Represents a message of `[2, "Goodbye, world!"]` - .body(&[146, 2, 175, 71, 111, 111, 100, 98, 121, 101, 44, 32, 119, 111, 114, 108, 100, 33]); - run_test!(&rocket, req, |mut response: Response| { - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.body().unwrap().into_string().unwrap(), "Goodbye, world!"); - }); + .body(&[146, 2, 175, 71, 111, 111, 100, 98, 121, 101, 44, 32, 119, 111, 114, 108, 100, 33]) + .dispatch(); + + assert_eq!(res.status(), Status::Ok); + assert_eq!(res.body_string(), Some("Goodbye, world!".into())); } diff --git a/examples/optional_redirect/src/tests.rs b/examples/optional_redirect/src/tests.rs index 6dc38077..0ce66cbb 100644 --- a/examples/optional_redirect/src/tests.rs +++ b/examples/optional_redirect/src/tests.rs @@ -1,24 +1,25 @@ use super::rocket; -use rocket::testing::MockRequest; -use rocket::http::{Method, Status}; +use rocket::local::Client; +use rocket::http::Status; -fn test_200(uri: &str, expected_body: &str) { +fn client() -> Client { let rocket = rocket::ignite() .mount("/", routes![super::root, super::user, super::login]); - let mut request = MockRequest::new(Method::Get, uri); - let mut response = request.dispatch_with(&rocket); + Client::new(rocket).unwrap() +} + +fn test_200(uri: &str, expected_body: &str) { + let client = client(); + let mut response = client.get(uri).dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.body_string(), Some(expected_body.to_string())); } fn test_303(uri: &str, expected_location: &str) { - let rocket = rocket::ignite() - .mount("/", routes![super::root, super::user, super::login]); - let mut request = MockRequest::new(Method::Get, uri); - let response = request.dispatch_with(&rocket); + let client = client(); + let response = client.get(uri).dispatch(); let location_headers: Vec<_> = response.headers().get("Location").collect(); - assert_eq!(response.status(), Status::SeeOther); assert_eq!(location_headers, vec![expected_location]); } diff --git a/examples/optional_result/src/main.rs b/examples/optional_result/src/main.rs deleted file mode 100644 index 8edba794..00000000 --- a/examples/optional_result/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![feature(plugin)] -#![plugin(rocket_codegen)] - -extern crate rocket; - -#[cfg(test)] mod tests; - -use rocket::http::RawStr; - -#[get("/users/")] -fn user(name: &RawStr) -> Option<&'static str> { - if name == "Sergio" { - Some("Hello, Sergio!") - } else { - None - } -} - -fn main() { - rocket::ignite().mount("/", routes![user]).launch(); -} diff --git a/examples/optional_result/src/tests.rs b/examples/optional_result/src/tests.rs deleted file mode 100644 index 5d63fcfd..00000000 --- a/examples/optional_result/src/tests.rs +++ /dev/null @@ -1,23 +0,0 @@ -use super::rocket; -use rocket::testing::MockRequest; -use rocket::http::{Method, Status}; - -#[test] -fn test_200() { - let rocket = rocket::ignite().mount("/", routes![super::user]); - let mut request = MockRequest::new(Method::Get, "/users/Sergio"); - let mut response = request.dispatch_with(&rocket); - - assert_eq!(response.status(), Status::Ok); - assert_eq!(response.body_string(), Some("Hello, Sergio!".into())); -} - -#[test] -fn test_404() { - let rocket = rocket::ignite().mount("/", routes![super::user]); - let mut request = MockRequest::new(Method::Get, "/users/unknown"); - let response = request.dispatch_with(&rocket); - - // Only test the status because the body is the default 404. - assert_eq!(response.status(), Status::NotFound); -} diff --git a/examples/pastebin/src/tests.rs b/examples/pastebin/src/tests.rs index 01f9a6e8..ecd0afc1 100644 --- a/examples/pastebin/src/tests.rs +++ b/examples/pastebin/src/tests.rs @@ -1,8 +1,6 @@ use super::{rocket, index}; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; use rocket::http::{Status, ContentType}; -use rocket::Rocket; fn extract_id(from: &str) -> Option { from.rfind('/').map(|i| &from[(i + 1)..]).map(|s| s.trim_right().to_string()) @@ -10,56 +8,52 @@ fn extract_id(from: &str) -> Option { #[test] fn check_index() { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); // Ensure the index returns what we expect. - let mut req = MockRequest::new(Get, "/"); - let mut response = req.dispatch_with(&rocket); + let mut response = client.get("/").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::Plain)); assert_eq!(response.body_string(), Some(index().into())) } -fn upload_paste(rocket: &Rocket, body: &str) -> String { - let mut req = MockRequest::new(Post, "/").body(body); - let mut response = req.dispatch_with(rocket); +fn upload_paste(client: &Client, body: &str) -> String { + let mut response = client.post("/").body(body).dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::Plain)); extract_id(&response.body_string().unwrap()).unwrap() } - -fn download_paste(rocket: &Rocket, id: &str) -> String { - let mut req = MockRequest::new(Get, format!("/{}", id)); - let mut response = req.dispatch_with(rocket); +fn download_paste(client: &Client, id: &str) -> String { + let mut response = client.get(format!("/{}", id)).dispatch(); assert_eq!(response.status(), Status::Ok); response.body_string().unwrap() } #[test] fn pasting() { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); // Do a trivial upload, just to make sure it works. let body_1 = "Hello, world!"; - let id_1 = upload_paste(&rocket, body_1); - assert_eq!(download_paste(&rocket, &id_1), body_1); + let id_1 = upload_paste(&client, body_1); + assert_eq!(download_paste(&client, &id_1), body_1); // Make sure we can keep getting that paste. - assert_eq!(download_paste(&rocket, &id_1), body_1); - assert_eq!(download_paste(&rocket, &id_1), body_1); - assert_eq!(download_paste(&rocket, &id_1), body_1); + assert_eq!(download_paste(&client, &id_1), body_1); + assert_eq!(download_paste(&client, &id_1), body_1); + assert_eq!(download_paste(&client, &id_1), body_1); // Upload some unicode. let body_2 = "こんにちは"; - let id_2 = upload_paste(&rocket, body_2); - assert_eq!(download_paste(&rocket, &id_2), body_2); + let id_2 = upload_paste(&client, body_2); + assert_eq!(download_paste(&client, &id_2), body_2); // Make sure we can get both pastes. - assert_eq!(download_paste(&rocket, &id_1), body_1); - assert_eq!(download_paste(&rocket, &id_2), body_2); - assert_eq!(download_paste(&rocket, &id_1), body_1); - assert_eq!(download_paste(&rocket, &id_2), body_2); + assert_eq!(download_paste(&client, &id_1), body_1); + assert_eq!(download_paste(&client, &id_2), body_2); + assert_eq!(download_paste(&client, &id_1), body_1); + assert_eq!(download_paste(&client, &id_2), body_2); // Now a longer upload. let body_3 = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed @@ -69,8 +63,8 @@ fn pasting() { in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; - let id_3 = upload_paste(&rocket, body_3); - assert_eq!(download_paste(&rocket, &id_3), body_3); - assert_eq!(download_paste(&rocket, &id_1), body_1); - assert_eq!(download_paste(&rocket, &id_2), body_2); + let id_3 = upload_paste(&client, body_3); + assert_eq!(download_paste(&client, &id_3), body_3); + assert_eq!(download_paste(&client, &id_1), body_1); + assert_eq!(download_paste(&client, &id_2), body_2); } diff --git a/examples/query_params/src/tests.rs b/examples/query_params/src/tests.rs index 2a194ed5..fd613aa2 100644 --- a/examples/query_params/src/tests.rs +++ b/examples/query_params/src/tests.rs @@ -1,16 +1,14 @@ use super::rocket; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::{Client, LocalResponse as Response}; use rocket::http::Status; -use rocket::Response; macro_rules! run_test { ($query:expr, $test_fn:expr) => ({ let rocket = rocket::ignite() .mount("/", routes![super::hello]); - let mut request = MockRequest::new(Get, format!("/hello{}", $query)); - $test_fn(request.dispatch_with(&rocket)); + let client = Client::new(rocket).unwrap(); + $test_fn(client.get(format!("/hello{}", $query)).dispatch()); }) } diff --git a/examples/forms/Cargo.toml b/examples/ranking/Cargo.toml similarity index 89% rename from examples/forms/Cargo.toml rename to examples/ranking/Cargo.toml index 1c4cc4ee..56587bf7 100644 --- a/examples/forms/Cargo.toml +++ b/examples/ranking/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "forms" +name = "ranking" version = "0.0.0" workspace = "../../" diff --git a/examples/hello_ranks/src/main.rs b/examples/ranking/src/main.rs similarity index 100% rename from examples/hello_ranks/src/main.rs rename to examples/ranking/src/main.rs diff --git a/examples/hello_ranks/src/tests.rs b/examples/ranking/src/tests.rs similarity index 85% rename from examples/hello_ranks/src/tests.rs rename to examples/ranking/src/tests.rs index 66e4afed..b4177cee 100644 --- a/examples/hello_ranks/src/tests.rs +++ b/examples/ranking/src/tests.rs @@ -1,11 +1,10 @@ use super::rocket; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; fn test(uri: &str, expected: String) { let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]); - let mut req = MockRequest::new(Get, uri); - let mut response = req.dispatch_with(&rocket); + let client = Client::new(rocket).unwrap(); + let mut response = client.get(uri).dispatch(); assert_eq!(response.body_string(), Some(expected)); } diff --git a/examples/raw_sqlite/Cargo.toml b/examples/raw_sqlite/Cargo.toml index 9252323e..4dbce296 100644 --- a/examples/raw_sqlite/Cargo.toml +++ b/examples/raw_sqlite/Cargo.toml @@ -6,4 +6,4 @@ workspace = "../../" [dependencies] rocket = { path = "../../lib" } rocket_codegen = { path = "../../codegen" } -rusqlite = "0.10" +rusqlite = "0.12" diff --git a/examples/raw_sqlite/src/tests.rs b/examples/raw_sqlite/src/tests.rs index 9fda4e0f..3fbb8062 100644 --- a/examples/raw_sqlite/src/tests.rs +++ b/examples/raw_sqlite/src/tests.rs @@ -1,12 +1,9 @@ use super::rocket; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; #[test] fn hello() { - let rocket = rocket(); - let mut req = MockRequest::new(Get, "/"); - let mut response = req.dispatch_with(&rocket); - + let client = Client::new(rocket()).unwrap(); + let mut response = client.get("/").dispatch(); assert_eq!(response.body_string(), Some("Rocketeer".into())); } diff --git a/examples/raw_upload/src/tests.rs b/examples/raw_upload/src/tests.rs index ebc2e2f9..08ee278c 100644 --- a/examples/raw_upload/src/tests.rs +++ b/examples/raw_upload/src/tests.rs @@ -1,30 +1,30 @@ -use rocket::testing::MockRequest; +use rocket::local::Client; use rocket::http::{Status, ContentType}; -use rocket::http::Method::*; use std::io::Read; -use std::fs::File; +use std::fs::{self, File}; + +const UPLOAD_CONTENTS: &str = "Hey! I'm going to be uploaded. :D Yay!"; #[test] fn test_index() { - let rocket = super::rocket(); - let mut req = MockRequest::new(Get, "/"); - let mut res = req.dispatch_with(&rocket); - + let client = Client::new(super::rocket()).unwrap(); + let mut res = client.get("/").dispatch(); assert_eq!(res.body_string(), Some(super::index().to_string())); } #[test] fn test_raw_upload() { - const UPLOAD_CONTENTS: &str = "Hey! I'm going to be uploaded. :D Yay!"; - - let rocket = super::rocket(); - let mut req = MockRequest::new(Post, "/upload") - .header(ContentType::Plain) - .body(UPLOAD_CONTENTS); + // Delete the upload file before we begin. + let _ = fs::remove_file("/tmp/upload.txt"); // Do the upload. Make sure we get the expected results. - let mut res = req.dispatch_with(&rocket); + let client = Client::new(super::rocket()).unwrap(); + let mut res = client.post("/upload") + .header(ContentType::Plain) + .body(UPLOAD_CONTENTS) + .dispatch(); + assert_eq!(res.status(), Status::Ok); assert_eq!(res.body_string(), Some(UPLOAD_CONTENTS.len().to_string())); diff --git a/examples/redirect/src/tests.rs b/examples/redirect/src/tests.rs index 9a80c074..7c32d785 100644 --- a/examples/redirect/src/tests.rs +++ b/examples/redirect/src/tests.rs @@ -1,38 +1,31 @@ use super::rocket; -use rocket::testing::MockRequest; -use rocket::Response; -use rocket::http::Method::*; +use rocket::local::Client; use rocket::http::Status; -macro_rules! run_test { - ($path:expr, $test_fn:expr) => ({ - let rocket = rocket::ignite().mount("/", routes![super::root, super::login]); - let mut request = MockRequest::new(Get, format!($path)); - - $test_fn(request.dispatch_with(&rocket)); - }) +fn client() -> Client { + let rocket = rocket::ignite().mount("/", routes![super::root, super::login]); + Client::new(rocket).unwrap() } #[test] fn test_root() { - run_test!("/", |mut response: Response| { - assert!(response.body().is_none()); - assert_eq!(response.status(), Status::SeeOther); - for h in response.headers().iter() { - match h.name.as_str() { - "Location" => assert_eq!(h.value, "/login"), - "Content-Length" => assert_eq!(h.value.parse::().unwrap(), 0), - _ => { /* let these through */ } - } + let client = client(); + let mut response = client.get("/").dispatch(); + + assert!(response.body().is_none()); + assert_eq!(response.status(), Status::SeeOther); + for h in response.headers().iter() { + match h.name.as_str() { + "Location" => assert_eq!(h.value, "/login"), + "Content-Length" => assert_eq!(h.value.parse::().unwrap(), 0), + _ => { /* let these through */ } } - }); + } } #[test] fn test_login() { - run_test!("/login", |mut response: Response| { - assert_eq!(response.body_string(), - Some("Hi! Please log in before continuing.".to_string())); - assert_eq!(response.status(), Status::Ok); - }); + let client = client(); + let mut r = client.get("/login").dispatch(); + assert_eq!(r.body_string(), Some("Hi! Please log in before continuing.".into())); } diff --git a/examples/hello_ranks/Cargo.toml b/examples/request_guard/Cargo.toml similarity index 85% rename from examples/hello_ranks/Cargo.toml rename to examples/request_guard/Cargo.toml index 99fd4aae..97c2e92f 100644 --- a/examples/hello_ranks/Cargo.toml +++ b/examples/request_guard/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "hello_ranks" +name = "request_guard" version = "0.0.0" workspace = "../../" diff --git a/examples/from_request/src/main.rs b/examples/request_guard/src/main.rs similarity index 71% rename from examples/from_request/src/main.rs rename to examples/request_guard/src/main.rs index ec613a7c..ef987d38 100644 --- a/examples/from_request/src/main.rs +++ b/examples/request_guard/src/main.rs @@ -28,27 +28,27 @@ fn header_count(header_count: HeaderCount) -> String { format!("Your request contained {} headers!", header_count) } +fn rocket() -> rocket::Rocket { + rocket::ignite().mount("/", routes![header_count]) +} + fn main() { - rocket::ignite().mount("/", routes![header_count]).launch(); + rocket().launch(); } #[cfg(test)] mod test { - use super::rocket; - use rocket::testing::MockRequest; - use rocket::http::Method::*; + use rocket::local::Client; use rocket::http::Header; fn test_header_count<'h>(headers: Vec>) { - let rocket = rocket::ignite() - .mount("/", routes![super::header_count]); - - let mut req = MockRequest::new(Get, "/"); + let client = Client::new(super::rocket()).unwrap(); + let mut req = client.get("/"); for header in headers.iter().cloned() { - req = req.header(header); + req.add_header(header); } - let mut response = req.dispatch_with(&rocket); + let mut response = req.dispatch(); let expect = format!("Your request contained {} headers!", headers.len()); assert_eq!(response.body_string(), Some(expect)); } @@ -56,7 +56,8 @@ mod test { #[test] fn test_n_headers() { for i in 0..50 { - let headers = (0..i).map(|n| Header::new(n.to_string(), n.to_string())) + let headers = (0..i) + .map(|n| Header::new(n.to_string(), n.to_string())) .collect(); test_header_count(headers); diff --git a/examples/session/src/main.rs b/examples/session/src/main.rs index 651cac87..454119a8 100644 --- a/examples/session/src/main.rs +++ b/examples/session/src/main.rs @@ -9,7 +9,7 @@ use std::collections::HashMap; use rocket::Outcome; use rocket::request::{self, Form, FlashMessage, FromRequest, Request}; use rocket::response::{Redirect, Flash}; -use rocket::http::{Cookie, Session}; +use rocket::http::{Cookie, Cookies}; use rocket_contrib::Template; #[derive(FromForm)] @@ -25,8 +25,8 @@ impl<'a, 'r> FromRequest<'a, 'r> for User { type Error = (); fn from_request(request: &'a Request<'r>) -> request::Outcome { - let user = request.session() - .get("user_id") + let user = request.cookies() + .get_private("user_id") .and_then(|cookie| cookie.value().parse().ok()) .map(|id| User(id)); @@ -38,9 +38,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for User { } #[post("/login", data = "")] -fn login(mut session: Session, login: Form) -> Flash { +fn login(mut cookies: Cookies, login: Form) -> Flash { if login.get().username == "Sergio" && login.get().password == "password" { - session.set(Cookie::new("user_id", 1.to_string())); + cookies.add_private(Cookie::new("user_id", 1.to_string())); Flash::success(Redirect::to("/"), "Successfully logged in.") } else { Flash::error(Redirect::to("/login"), "Invalid username/password.") @@ -48,8 +48,8 @@ fn login(mut session: Session, login: Form) -> Flash { } #[post("/logout")] -fn logout(mut session: Session) -> Flash { - session.remove(Cookie::named("user_id")); +fn logout(mut cookies: Cookies) -> Flash { + cookies.remove_private(Cookie::named("user_id")); Flash::success(Redirect::to("/login"), "Successfully logged out.") } @@ -82,6 +82,7 @@ fn index() -> Redirect { fn main() { rocket::ignite() + .attach(Template::fairing()) .mount("/", routes![index, user_index, login, logout, login_user, login_page]) .launch(); } diff --git a/examples/state/src/tests.rs b/examples/state/src/tests.rs index 5ab86781..f2b060a1 100644 --- a/examples/state/src/tests.rs +++ b/examples/state/src/tests.rs @@ -1,32 +1,28 @@ -use rocket::Rocket; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; use rocket::http::Status; -fn register_hit(rocket: &Rocket) { - let mut req = MockRequest::new(Get, "/"); - let response = req.dispatch_with(&rocket); +fn register_hit(client: &Client) { + let response = client.get("/").dispatch();; assert_eq!(response.status(), Status::Ok); } -fn get_count(rocket: &Rocket) -> usize { - let mut req = MockRequest::new(Get, "/count"); - let mut response = req.dispatch_with(&rocket); +fn get_count(client: &Client) -> usize { + let mut response = client.get("/count").dispatch(); response.body_string().and_then(|s| s.parse().ok()).unwrap() } #[test] fn test_count() { - let rocket = super::rocket(); + let client = Client::new(super::rocket()).unwrap(); // Count should start at 0. - assert_eq!(get_count(&rocket), 0); + assert_eq!(get_count(&client), 0); - for _ in 0..99 { register_hit(&rocket); } - assert_eq!(get_count(&rocket), 99); + for _ in 0..99 { register_hit(&client); } + assert_eq!(get_count(&client), 99); - register_hit(&rocket); - assert_eq!(get_count(&rocket), 100); + register_hit(&client); + assert_eq!(get_count(&client), 100); } // Cargo runs each test in parallel on different threads. We use all of these diff --git a/examples/static_files/src/tests.rs b/examples/static_files/src/tests.rs index 4b28b41f..95a5a4fa 100644 --- a/examples/static_files/src/tests.rs +++ b/examples/static_files/src/tests.rs @@ -1,8 +1,7 @@ use std::fs::File; use std::io::Read; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; use rocket::http::Status; use super::rocket; @@ -10,10 +9,8 @@ use super::rocket; fn test_query_file (path: &str, file: T, status: Status) where T: Into> { - let rocket = rocket(); - let mut req = MockRequest::new(Get, &path); - - let mut response = req.dispatch_with(&rocket); + let client = Client::new(rocket()).unwrap(); + let mut response = client.get(path).dispatch(); assert_eq!(response.status(), status); let body_data = response.body().and_then(|body| body.into_bytes()); diff --git a/examples/stream/src/tests.rs b/examples/stream/src/tests.rs index 5307b015..50b29762 100644 --- a/examples/stream/src/tests.rs +++ b/examples/stream/src/tests.rs @@ -1,14 +1,12 @@ use std::fs::{self, File}; use std::io::prelude::*; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; #[test] fn test_root() { - let rocket = super::rocket(); - let mut req = MockRequest::new(Get, "/"); - let mut res = req.dispatch_with(&rocket); + let client = Client::new(super::rocket()).unwrap(); + let mut res = client.get("/").dispatch(); // Check that we have exactly 25,000 'a'. let res_str = res.body_string().unwrap(); @@ -26,9 +24,8 @@ fn test_file() { file.write_all(CONTENTS.as_bytes()).expect("write to big_file"); // Get the big file contents, hopefully. - let rocket = super::rocket(); - let mut req = MockRequest::new(Get, "/big_file"); - let mut res = req.dispatch_with(&rocket); + let client = Client::new(super::rocket()).unwrap(); + let mut res = client.get("/big_file").dispatch(); assert_eq!(res.body_string(), Some(CONTENTS.into())); // Delete the 'big_file'. diff --git a/examples/testing/src/main.rs b/examples/testing/src/main.rs index e2eb6530..73bd0341 100644 --- a/examples/testing/src/main.rs +++ b/examples/testing/src/main.rs @@ -8,22 +8,24 @@ fn hello() -> &'static str { "Hello, world!" } +fn rocket() -> rocket::Rocket { + rocket::ignite().mount("/", routes![hello]) +} + fn main() { - rocket::ignite().mount("/", routes![hello]).launch(); + rocket().launch(); } #[cfg(test)] mod test { use super::rocket; - use rocket::testing::MockRequest; + use rocket::local::Client; use rocket::http::Status; - use rocket::http::Method::*; #[test] fn test_hello() { - let rocket = rocket::ignite().mount("/", routes![super::hello]); - let mut req = MockRequest::new(Get, "/"); - let mut response = req.dispatch_with(&rocket); + let client = Client::new(rocket()).unwrap(); + let mut response = client.get("/").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.body_string(), Some("Hello, world!".into())); } diff --git a/examples/hello_tls/Cargo.toml b/examples/tls/Cargo.toml similarity index 89% rename from examples/hello_tls/Cargo.toml rename to examples/tls/Cargo.toml index 1bfa6f1a..7db8792d 100644 --- a/examples/hello_tls/Cargo.toml +++ b/examples/tls/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "hello_tls" +name = "tls" version = "0.0.0" workspace = "../../" diff --git a/examples/hello_tls/Rocket.toml b/examples/tls/Rocket.toml similarity index 100% rename from examples/hello_tls/Rocket.toml rename to examples/tls/Rocket.toml diff --git a/examples/hello_tls/private/cert.pem b/examples/tls/private/cert.pem similarity index 100% rename from examples/hello_tls/private/cert.pem rename to examples/tls/private/cert.pem diff --git a/examples/hello_tls/private/key.pem b/examples/tls/private/key.pem similarity index 100% rename from examples/hello_tls/private/key.pem rename to examples/tls/private/key.pem diff --git a/examples/hello_tls/src/main.rs b/examples/tls/src/main.rs similarity index 100% rename from examples/hello_tls/src/main.rs rename to examples/tls/src/main.rs diff --git a/examples/hello_tls/src/tests.rs b/examples/tls/src/tests.rs similarity index 53% rename from examples/hello_tls/src/tests.rs rename to examples/tls/src/tests.rs index abb31f89..da0b732b 100644 --- a/examples/hello_tls/src/tests.rs +++ b/examples/tls/src/tests.rs @@ -1,12 +1,10 @@ use super::rocket; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; #[test] fn hello_world() { let rocket = rocket::ignite().mount("/", routes![super::hello]); - let mut req = MockRequest::new(Get, "/"); - let mut response = req.dispatch_with(&rocket); - + let client = Client::new(rocket).unwrap(); + let mut response = client.get("/").dispatch(); assert_eq!(response.body_string(), Some("Hello, world!".into())); } diff --git a/examples/todo/Cargo.toml b/examples/todo/Cargo.toml index ae2bac37..ac350e56 100644 --- a/examples/todo/Cargo.toml +++ b/examples/todo/Cargo.toml @@ -10,9 +10,9 @@ serde = "1.0" serde_json = "1.0" serde_derive = "1.0" r2d2 = "0.7" -diesel = { version = "0.12", features = ["sqlite"] } -diesel_codegen = { version = "0.12", features = ["sqlite"] } -r2d2-diesel = "0.12" +diesel = { version = "0.13", features = ["sqlite"] } +diesel_codegen = { version = "0.13", features = ["sqlite"] } +r2d2-diesel = "0.13" [dev-dependencies] parking_lot = {version = "0.4", features = ["nightly"]} diff --git a/examples/todo/src/tests.rs b/examples/todo/src/tests.rs index 72a2c7b3..854e9ebd 100644 --- a/examples/todo/src/tests.rs +++ b/examples/todo/src/tests.rs @@ -5,8 +5,7 @@ use super::task::Task; use self::parking_lot::Mutex; use self::rand::{Rng, thread_rng}; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; use rocket::http::{Status, ContentType}; // We use a lock to synchronize between tests so DB operations don't collide. @@ -15,9 +14,10 @@ use rocket::http::{Status, ContentType}; static DB_LOCK: Mutex<()> = Mutex::new(()); macro_rules! run_test { - (|$rocket:ident, $conn:ident| $block:expr) => ({ + (|$client:ident, $conn:ident| $block:expr) => ({ let _lock = DB_LOCK.lock(); - let ($rocket, db) = super::rocket(); + let (rocket, db) = super::rocket(); + let $client = Client::new(rocket).expect("Rocket client"); let $conn = db.expect("failed to get database connection for testing"); $block }) @@ -25,15 +25,15 @@ macro_rules! run_test { #[test] fn test_insertion_deletion() { - run_test!(|rocket, conn| { + run_test!(|client, conn| { // Get the tasks before making changes. let init_tasks = Task::all(&conn); // Issue a request to insert a new task. - let mut req = MockRequest::new(Post, "/todo") + client.post("/todo") .header(ContentType::Form) - .body("description=My+first+task"); - req.dispatch_with(&rocket); + .body("description=My+first+task") + .dispatch(); // Ensure we have one more task in the database. let new_tasks = Task::all(&conn); @@ -45,8 +45,7 @@ fn test_insertion_deletion() { // Issue a request to delete the task. let id = new_tasks[0].id.unwrap(); - let mut req = MockRequest::new(Delete, format!("/todo/{}", id)); - req.dispatch_with(&rocket); + client.delete(format!("/todo/{}", id)).dispatch(); // Ensure it's gone. let final_tasks = Task::all(&conn); @@ -59,24 +58,22 @@ fn test_insertion_deletion() { #[test] fn test_toggle() { - run_test!(|rocket, conn| { + run_test!(|client, conn| { // Issue a request to insert a new task; ensure it's not yet completed. - let mut req = MockRequest::new(Post, "/todo") + client.post("/todo") .header(ContentType::Form) - .body("description=test_for_completion"); - req.dispatch_with(&rocket); + .body("description=test_for_completion") + .dispatch(); let task = Task::all(&conn)[0].clone(); assert_eq!(task.completed, false); // Issue a request to toggle the task; ensure it is completed. - let mut req = MockRequest::new(Put, format!("/todo/{}", task.id.unwrap())); - req.dispatch_with(&rocket); + client.put(format!("/todo/{}", task.id.unwrap())).dispatch(); assert_eq!(Task::all(&conn)[0].completed, true); // Issue a request to toggle the task; ensure it's not completed again. - let mut req = MockRequest::new(Put, format!("/todo/{}", task.id.unwrap())); - req.dispatch_with(&rocket); + client.put(format!("/todo/{}", task.id.unwrap())).dispatch(); assert_eq!(Task::all(&conn)[0].completed, false); }) } @@ -86,7 +83,7 @@ fn test_many_insertions() { const ITER: usize = 100; let mut rng = thread_rng(); - run_test!(|rocket, conn| { + run_test!(|client, conn| { // Get the number of tasks initially. let init_num = Task::all(&conn).len(); let mut descs = Vec::new(); @@ -94,10 +91,10 @@ fn test_many_insertions() { for i in 0..ITER { // Issue a request to insert a new task with a random description. let desc: String = rng.gen_ascii_chars().take(12).collect(); - let mut req = MockRequest::new(Post, "/todo") + client.post("/todo") .header(ContentType::Form) - .body(format!("description={}", desc)); - req.dispatch_with(&rocket); + .body(format!("description={}", desc)) + .dispatch(); // Record the description we choose for this iteration. descs.insert(0, desc); @@ -115,32 +112,34 @@ fn test_many_insertions() { #[test] fn test_bad_form_submissions() { - run_test!(|rocket, _conn| { + run_test!(|client, _conn| { // Submit an empty form. We should get a 422 but no flash error. - let mut req = MockRequest::new(Post, "/todo").header(ContentType::Form); - let res = req.dispatch_with(&rocket); - assert_eq!(res.status(), Status::UnprocessableEntity); + let res = client.post("/todo") + .header(ContentType::Form) + .dispatch(); + let mut cookies = res.headers().get("Set-Cookie"); + assert_eq!(res.status(), Status::UnprocessableEntity); assert!(!cookies.any(|value| value.contains("error"))); - // Submit a form with an empty description. - let mut req = MockRequest::new(Post, "/todo") + // Submit a form with an empty description. We look for 'error' in the + // cookies which corresponds to flash message being set as an error. + let res = client.post("/todo") .header(ContentType::Form) - .body("description="); + .body("description=") + .dispatch(); - // We look for 'error' in the cookies which corresponds to flash message - // being set as an error. - let res = req.dispatch_with(&rocket); let mut cookies = res.headers().get("Set-Cookie"); assert!(cookies.any(|value| value.contains("error"))); // Submit a form without a description. Expect a 422 but no flash error. - let mut req = MockRequest::new(Post, "/todo") + let res = client.post("/todo") .header(ContentType::Form) - .body("evil=smile"); - let res = req.dispatch_with(&rocket); - assert_eq!(res.status(), Status::UnprocessableEntity); + .body("evil=smile") + .dispatch(); + let mut cookies = res.headers().get("Set-Cookie"); + assert_eq!(res.status(), Status::UnprocessableEntity); assert!(!cookies.any(|value| value.contains("error"))); }) } diff --git a/examples/uuid/src/main.rs b/examples/uuid/src/main.rs index 712db9bd..2e6c02ac 100644 --- a/examples/uuid/src/main.rs +++ b/examples/uuid/src/main.rs @@ -39,8 +39,11 @@ fn people(id: UUID) -> Result { .ok_or(format!("Person not found for UUID: {}", id))?) } -fn main() { +fn rocket() -> rocket::Rocket { rocket::ignite() .mount("/", routes![people]) - .launch(); +} + +fn main() { + rocket().launch(); } diff --git a/examples/uuid/src/tests.rs b/examples/uuid/src/tests.rs index 0d1640cc..fa31e31d 100644 --- a/examples/uuid/src/tests.rs +++ b/examples/uuid/src/tests.rs @@ -1,23 +1,16 @@ use super::rocket; -use rocket::testing::MockRequest; -use rocket::http::Method::*; +use rocket::local::Client; use rocket::http::Status; fn test(uri: &str, expected: &str) { - let rocket = rocket::ignite().mount("/", routes![super::people]); - - let mut req = MockRequest::new(Get, uri); - let mut res = req.dispatch_with(&rocket); - + let client = Client::new(rocket()).unwrap(); + let mut res = client.get(uri).dispatch(); assert_eq!(res.body_string(), Some(expected.into())); } fn test_404(uri: &str) { - let rocket = rocket::ignite().mount("/", routes![super::people]); - - let mut req = MockRequest::new(Get, uri); - let res = req.dispatch_with(&rocket); - + let client = Client::new(rocket()).unwrap(); + let res = client.get(uri).dispatch(); assert_eq!(res.status(), Status::NotFound); } @@ -26,8 +19,7 @@ fn test_people() { test("/people/7f205202-7ba1-4c39-b2fc-3e630722bf9f", "We found: Lacy"); test("/people/4da34121-bc7d-4fc1-aee6-bf8de0795333", "We found: Bob"); test("/people/ad962969-4e3d-4de7-ac4a-2d86d6d10839", "We found: George"); - - test("/people/e18b3a5c-488f-4159-a240-2101e0da19fd", "Person not found for UUID: e18b3a5c-488f-4159-a240-2101e0da19fd"); - + test("/people/e18b3a5c-488f-4159-a240-2101e0da19fd", + "Person not found for UUID: e18b3a5c-488f-4159-a240-2101e0da19fd"); test_404("/people/invalid_uuid"); } diff --git a/lib/Cargo.toml b/lib/Cargo.toml index dcffce68..a756ad19 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -18,21 +18,21 @@ categories = ["web-programming::http-server"] tls = ["rustls", "hyper-rustls"] [dependencies] -yansi = "0.2" +yansi = "0.3" log = "0.3" url = "1" -toml = { version = "0.2", default-features = false } +toml = "0.4" num_cpus = "1" -state = "0.2.1" +state = "0.2.2" time = "0.1" memchr = "1" -base64 = "0.5.2" +base64 = "0.6" smallvec = "0.4" pear = "0.0.8" pear_codegen = "0.0.8" rustls = { version = "0.8.0", optional = true } cookie = { version = "0.8.1", features = ["percent-encode", "secure"] } -hyper = { version = "0.10.9", default-features = false } +hyper = { version = "0.10.11", default-features = false } ordermap = "0.2" [dependencies.hyper-rustls] @@ -47,5 +47,5 @@ lazy_static = "0.2" rocket_codegen = { version = "0.2.8", path = "../codegen" } [build-dependencies] -yansi = "0.2" +yansi = "0.3" version_check = "0.1.2" diff --git a/lib/benches/format-routing.rs b/lib/benches/format-routing.rs index 1bb137bf..41b068d1 100644 --- a/lib/benches/format-routing.rs +++ b/lib/benches/format-routing.rs @@ -13,8 +13,7 @@ fn post() -> &'static str { "post" } fn rocket() -> rocket::Rocket { let config = Config::new(Environment::Production).unwrap(); - rocket::custom(config, false) - .mount("/", routes![get, post]) + rocket::custom(config, false).mount("/", routes![get, post]) } mod benches { @@ -22,35 +21,34 @@ mod benches { use super::rocket; use self::test::Bencher; - use rocket::testing::MockRequest; - use rocket::http::Method::*; + use rocket::local::Client; use rocket::http::{Accept, ContentType}; #[bench] fn accept_format(b: &mut Bencher) { - let rocket = rocket(); - let mut request = MockRequest::new(Get, "/").header(Accept::JSON); - b.iter(|| { request.dispatch_with(&rocket); }); + let client = Client::new(rocket()).unwrap(); + let mut request = client.get("/").header(Accept::JSON); + b.iter(|| { request.mut_dispatch(); }); } #[bench] fn wrong_accept_format(b: &mut Bencher) { - let rocket = rocket(); - let mut request = MockRequest::new(Get, "/").header(Accept::HTML); - b.iter(|| { request.dispatch_with(&rocket); }); + let client = Client::new(rocket()).unwrap(); + let mut request = client.get("/").header(Accept::HTML); + b.iter(|| { request.mut_dispatch(); }); } #[bench] fn content_type_format(b: &mut Bencher) { - let rocket = rocket(); - let mut request = MockRequest::new(Post, "/").header(ContentType::JSON); - b.iter(|| { request.dispatch_with(&rocket); }); + let client = Client::new(rocket()).unwrap(); + let mut request = client.post("/").header(ContentType::JSON); + b.iter(|| { request.mut_dispatch(); }); } #[bench] fn wrong_content_type_format(b: &mut Bencher) { - let rocket = rocket(); - let mut request = MockRequest::new(Post, "/").header(ContentType::Plain); - b.iter(|| { request.dispatch_with(&rocket); }); + let client = Client::new(rocket()).unwrap(); + let mut request = client.post("/").header(ContentType::Plain); + b.iter(|| { request.mut_dispatch(); }); } } diff --git a/lib/benches/ranked-routing.rs b/lib/benches/ranked-routing.rs index 4c7231b9..d5725861 100644 --- a/lib/benches/ranked-routing.rs +++ b/lib/benches/ranked-routing.rs @@ -35,36 +35,35 @@ mod benches { use super::rocket; use self::test::Bencher; - use rocket::testing::MockRequest; - use rocket::http::Method::*; + use rocket::local::Client; use rocket::http::{Accept, ContentType}; #[bench] fn accept_format(b: &mut Bencher) { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); let mut requests = vec![]; - requests.push(MockRequest::new(Get, "/").header(Accept::JSON)); - requests.push(MockRequest::new(Get, "/").header(Accept::HTML)); - requests.push(MockRequest::new(Get, "/").header(Accept::Plain)); + requests.push(client.get("/").header(Accept::JSON)); + requests.push(client.get("/").header(Accept::HTML)); + requests.push(client.get("/").header(Accept::Plain)); b.iter(|| { for request in requests.iter_mut() { - request.dispatch_with(&rocket); + request.mut_dispatch(); } }); } #[bench] fn content_type_format(b: &mut Bencher) { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); let mut requests = vec![]; - requests.push(MockRequest::new(Post, "/").header(ContentType::JSON)); - requests.push(MockRequest::new(Post, "/").header(ContentType::HTML)); - requests.push(MockRequest::new(Post, "/").header(ContentType::Plain)); + requests.push(client.post("/").header(ContentType::JSON)); + requests.push(client.post("/").header(ContentType::HTML)); + requests.push(client.post("/").header(ContentType::Plain)); b.iter(|| { for request in requests.iter_mut() { - request.dispatch_with(&rocket); + request.mut_dispatch(); } }); } diff --git a/lib/benches/simple-routing.rs b/lib/benches/simple-routing.rs index 92b38144..d53a595c 100644 --- a/lib/benches/simple-routing.rs +++ b/lib/benches/simple-routing.rs @@ -49,82 +49,82 @@ mod benches { use super::{hello_world_rocket, rocket}; use self::test::Bencher; - use rocket::testing::MockRequest; + use rocket::local::Client; use rocket::http::Method::*; #[bench] fn bench_hello_world(b: &mut Bencher) { - let rocket = hello_world_rocket(); - let mut request = MockRequest::new(Get, "/"); + let client = Client::new(hello_world_rocket()).unwrap(); + let mut request = client.get("/"); b.iter(|| { - request.dispatch_with(&rocket); + request.mut_dispatch(); }); } #[bench] fn bench_single_get_index(b: &mut Bencher) { - let rocket = rocket(); - let mut request = MockRequest::new(Get, "/"); + let client = Client::new(rocket()).unwrap(); + let mut request = client.get("/"); b.iter(|| { - request.dispatch_with(&rocket); + request.mut_dispatch(); }); } #[bench] fn bench_get_put_post_index(b: &mut Bencher) { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); // Hold all of the requests we're going to make during the benchmark. let mut requests = vec![]; - requests.push(MockRequest::new(Get, "/")); - requests.push(MockRequest::new(Put, "/")); - requests.push(MockRequest::new(Post, "/")); + requests.push(client.get("/")); + requests.push(client.put("/")); + requests.push(client.post("/")); b.iter(|| { for request in requests.iter_mut() { - request.dispatch_with(&rocket); + request.mut_dispatch(); } }); } #[bench] fn bench_dynamic(b: &mut Bencher) { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); // Hold all of the requests we're going to make during the benchmark. let mut requests = vec![]; - requests.push(MockRequest::new(Get, "/abc")); - requests.push(MockRequest::new(Get, "/abcdefg")); - requests.push(MockRequest::new(Get, "/123")); + requests.push(client.get("/abc")); + requests.push(client.get("/abcdefg")); + requests.push(client.get("/123")); b.iter(|| { for request in requests.iter_mut() { - request.dispatch_with(&rocket); + request.mut_dispatch(); } }); } #[bench] fn bench_simple_routing(b: &mut Bencher) { - let rocket = rocket(); + let client = Client::new(rocket()).unwrap(); // Hold all of the requests we're going to make during the benchmark. let mut requests = vec![]; - for route in rocket.routes() { - let request = MockRequest::new(route.method, route.path.path()); + for route in client.rocket().routes() { + let request = client.req(route.method, route.path.path()); requests.push(request); } // A few more for the dynamic route. - requests.push(MockRequest::new(Get, "/abc")); - requests.push(MockRequest::new(Get, "/abcdefg")); - requests.push(MockRequest::new(Get, "/123")); + requests.push(client.get("/abc")); + requests.push(client.get("/abcdefg")); + requests.push(client.get("/123")); b.iter(|| { for request in requests.iter_mut() { - request.dispatch_with(&rocket); + request.mut_dispatch(); } }); } diff --git a/lib/src/config/builder.rs b/lib/src/config/builder.rs index 7ecc68a7..3e43bae5 100644 --- a/lib/src/config/builder.rs +++ b/lib/src/config/builder.rs @@ -176,7 +176,7 @@ impl ConfigBuilder { /// use rocket::config::{Config, Environment, Limits}; /// /// let mut config = Config::build(Environment::Staging) - /// .limits(Limits::default().add("json", 5 * (1 << 20))) + /// .limits(Limits::new().limit("json", 5 * (1 << 20))) /// .unwrap(); /// ``` pub fn limits(mut self, limits: Limits) -> Self { @@ -319,7 +319,7 @@ impl ConfigBuilder { /// # Panics /// /// Panics if the current working directory cannot be retrieved or if the - /// supplied address or secret key fail to parse. + /// supplied address, secret key, or TLS configuration fail to parse. /// /// # Example /// @@ -336,4 +336,29 @@ impl ConfigBuilder { pub fn unwrap(self) -> Config { self.finalize().expect("ConfigBuilder::unwrap() failed") } + + /// + /// Returns the `Config` structure that was being built by this builder. + /// + /// # Panics + /// + /// Panics if the current working directory cannot be retrieved or if the + /// supplied address, secret key, or TLS configuration fail to parse. If a + /// panic occurs, the error message `msg` is printed. + /// + /// # Example + /// + /// ```rust + /// use rocket::config::{Config, Environment}; + /// + /// let config = Config::build(Environment::Staging) + /// .address("127.0.0.1") + /// .expect("the configuration is bad!"); + /// + /// assert_eq!(config.address.as_str(), "127.0.0.1"); + /// ``` + #[inline(always)] + pub fn expect(self, msg: &str) -> Config { + self.finalize().expect(msg) + } } diff --git a/lib/src/config/config.rs b/lib/src/config/config.rs index 6820b223..9a5c6601 100644 --- a/lib/src/config/config.rs +++ b/lib/src/config/config.rs @@ -8,7 +8,8 @@ use std::env; use super::custom_values::*; use {num_cpus, base64}; use config::Environment::*; -use config::{Result, Table, Value, ConfigBuilder, Environment, ConfigError}; +use config::{Result, ConfigBuilder, Environment, ConfigError}; +use config::{Table, Value, Array, Datetime}; use logger::LoggingLevel; use http::Key; @@ -112,6 +113,69 @@ impl Config { Config::default(env, cwd.as_path().join("Rocket.custom.toml")) } + /// Returns a builder for `Config` structure where the default parameters + /// are set to those of the development environment. The root configuration + /// directory is set to the current working directory. + /// + /// # Errors + /// + /// If the current directory cannot be retrieved, a `BadCWD` error is + /// returned. + /// + /// # Example + /// + /// ```rust + /// use rocket::config::{Config, Environment}; + /// + /// let mut my_config = Config::development().unwrap(); + /// my_config.set_port(1001); + /// ``` + pub fn development() -> Result { + Config::new(Environment::Development) + } + + /// Creates a new configuration using the default parameters from the + /// staging environment. The root configuration directory is set to the + /// current working directory. + /// + /// # Errors + /// + /// If the current directory cannot be retrieved, a `BadCWD` error is + /// returned. + /// + /// # Example + /// + /// ```rust + /// use rocket::config::{Config, Environment}; + /// + /// let mut my_config = Config::staging().expect("cwd"); + /// my_config.set_port(1001); + /// ``` + pub fn staging() -> Result { + Config::new(Environment::Staging) + } + + /// Creates a new configuration using the default parameters from the + /// production environment. The root configuration directory is set to the + /// current working directory. + /// + /// # Errors + /// + /// If the current directory cannot be retrieved, a `BadCWD` error is + /// returned. + /// + /// # Example + /// + /// ```rust + /// use rocket::config::{Config, Environment}; + /// + /// let mut my_config = Config::production().expect("cwd"); + /// my_config.set_port(1001); + /// ``` + pub fn production() -> Result { + Config::new(Environment::Production) + } + /// Returns the default configuration for the environment `env` given that /// the configuration was stored at `config_path`. If `config_path` is not /// an absolute path, an `Err` of `ConfigError::BadFilePath` is returned. @@ -128,8 +192,8 @@ impl Config { "Configuration files must be rooted in a directory.")); } - // Note: This may truncate if num_cpus::get() > u16::max. That's okay. - let default_workers = ::std::cmp::max(num_cpus::get(), 2) as u16; + // Note: This may truncate if num_cpus::get() / 2 > u16::max. That's okay. + let default_workers = (num_cpus::get() * 2) as u16; // Use a generated secret key by default. let key = SecretKey::Generated(Key::generate()); @@ -599,9 +663,9 @@ impl Config { /// /// assert!(config.get_slice("numbers").is_ok()); /// ``` - pub fn get_slice(&self, name: &str) -> Result<&[Value]> { + pub fn get_slice(&self, name: &str) -> Result<&Array> { let val = self.extras.get(name).ok_or_else(|| ConfigError::NotFound)?; - val.as_slice().ok_or_else(|| self.bad_type(name, val.type_str(), "a slice")) + val.as_array().ok_or_else(|| self.bad_type(name, val.type_str(), "an array")) } /// Attempts to retrieve the extra named `name` as a table. @@ -632,6 +696,32 @@ impl Config { val.as_table().ok_or_else(|| self.bad_type(name, val.type_str(), "a table")) } + /// Attempts to retrieve the extra named `name` as a datetime value. + /// + /// # Errors + /// + /// If an extra with `name` doesn't exist, returns an `Err` of `NotFound`. + /// If an extra with `name` _does_ exist but is not a datetime, returns a + /// `BadType` error. + /// + /// # Example + /// + /// ```rust + /// use rocket::config::{Config, Environment, Value, Datetime}; + /// + /// let date = "1979-05-27T00:32:00-07:00".parse::().unwrap(); + /// + /// let config = Config::build(Environment::Staging) + /// .extra("my_date", Value::Datetime(date.clone())) + /// .unwrap(); + /// + /// assert_eq!(config.get_datetime("my_date"), Ok(&date)); + /// ``` + pub fn get_datetime(&self, name: &str) -> Result<&Datetime> { + let v = self.extras.get(name).ok_or_else(|| ConfigError::NotFound)?; + v.as_datetime().ok_or_else(|| self.bad_type(name, v.type_str(), "a datetime")) + } + /// Returns the path at which the configuration file for `self` is stored. /// For instance, if the configuration file is at `/tmp/Rocket.toml`, the /// path `/tmp` is returned. @@ -658,7 +748,8 @@ impl Config { impl fmt::Debug for Config { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Config[{}] {{ address: {}, port: {}, workers: {}, log: {:?}", - self.environment, self.address, self.port, self.workers, self.log_level)?; + self.environment, self.address, self.port, self.workers, + self.log_level)?; for (key, value) in self.extras() { write!(f, ", {}: {}", key, value)?; diff --git a/lib/src/config/custom_values.rs b/lib/src/config/custom_values.rs index cfe3f3a1..5c590590 100644 --- a/lib/src/config/custom_values.rs +++ b/lib/src/config/custom_values.rs @@ -14,17 +14,26 @@ pub enum SecretKey { impl SecretKey { #[inline] - pub fn kind(&self) -> &'static str { + pub(crate) fn inner(&self) -> &Key { match *self { - SecretKey::Generated(_) => "generated", - SecretKey::Provided(_) => "provided", + SecretKey::Generated(ref key) | SecretKey::Provided(ref key) => key } } #[inline] - pub(crate) fn inner(&self) -> &Key { + pub(crate) fn is_generated(&self) -> bool { match *self { - SecretKey::Generated(ref key) | SecretKey::Provided(ref key) => key + SecretKey::Generated(_) => true, + _ => false + } + } +} + +impl fmt::Display for SecretKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SecretKey::Generated(_) => write!(f, "generated"), + SecretKey::Provided(_) => write!(f, "provided"), } } } @@ -40,10 +49,36 @@ pub struct TlsConfig { #[derive(Clone)] pub struct TlsConfig; -// Size limit configuration. We cache those used by Rocket internally but don't -// share that fact in the API. +/// Mapping from data type to size limits. +/// +/// A `Limits` structure contains a mapping from a given data type ("forms", +/// "json", and so on) to the maximum size in bytes that should be accepted by a +/// Rocket application for that data type. For instance, if the limit for +/// "forms" is set to `256`, only 256 bytes from an incoming form request will +/// be read. +/// +/// # Defaults +/// +/// As documented in the [config module](/rocket/config/), the default limits +/// are as follows: +/// +/// * **forms**: 32KiB +/// +/// # Usage +/// +/// A `Limits` structure is created following the builder pattern: +/// +/// ```rust +/// use rocket::config::Limits; +/// +/// // Set a limit of 64KiB for forms and 3MiB for JSON. +/// let limits = Limits::new() +/// .limit("forms", 64 * 1024) +/// .limit("json", 3 * 1024 * 1024); +/// ``` #[derive(Debug, Clone)] pub struct Limits { + // We cache this internally but don't share that fact in the API. pub(crate) forms: u64, extra: Vec<(String, u64)> } @@ -56,16 +91,75 @@ impl Default for Limits { } impl Limits { - pub fn add>(mut self, name: S, limit: u64) -> Self { + /// Construct a new `Limits` structure with the default limits set. + /// + /// # Example + /// + /// ```rust + /// use rocket::config::Limits; + /// + /// let limits = Limits::new(); + /// assert_eq!(limits.get("forms"), Some(32 * 1024)); + /// ``` + #[inline] + pub fn new() -> Self { + Limits::default() + } + + /// Adds or replaces a limit in `self`, consuming `self` anf returning a new + /// `Limits` structure with the added or replaced limit. + /// + /// # Example + /// + /// ```rust + /// use rocket::config::Limits; + /// + /// let limits = Limits::new() + /// .limit("json", 1 * 1024 * 1024); + /// + /// assert_eq!(limits.get("forms"), Some(32 * 1024)); + /// assert_eq!(limits.get("json"), Some(1 * 1024 * 1024)); + /// + /// let new_limits = limits.limit("json", 64 * 1024 * 1024); + /// assert_eq!(new_limits.get("json"), Some(64 * 1024 * 1024)); + /// ``` + pub fn limit>(mut self, name: S, limit: u64) -> Self { let name = name.into(); match name.as_str() { "forms" => self.forms = limit, - _ => self.extra.push((name, limit)) + _ => { + let mut found = false; + for tuple in self.extra.iter_mut() { + if tuple.0 == name { + tuple.1 = limit; + found = true; + break; + } + } + + if !found { + self.extra.push((name, limit)) + } + } } self } + /// Retrieve the set limit, if any, for the data type with name `name`. + /// + /// # Example + /// + /// ```rust + /// use rocket::config::Limits; + /// + /// let limits = Limits::new() + /// .limit("json", 64 * 1024 * 1024); + /// + /// assert_eq!(limits.get("forms"), Some(32 * 1024)); + /// assert_eq!(limits.get("json"), Some(64 * 1024 * 1024)); + /// assert!(limits.get("msgpack").is_none()); + /// ``` pub fn get(&self, name: &str) -> Option { if name == "forms" { return Some(self.forms); @@ -162,7 +256,7 @@ pub fn limits(conf: &Config, name: &str, value: &Value) -> Result { let mut limits = Limits::default(); for (key, val) in table { let val = u64(conf, &format!("limits.{}", key), val)?; - limits = limits.add(key.as_str(), val); + limits = limits.limit(key.as_str(), val); } Ok(limits) diff --git a/lib/src/config/environment.rs b/lib/src/config/environment.rs index 895506f3..3a154921 100644 --- a/lib/src/config/environment.rs +++ b/lib/src/config/environment.rs @@ -19,6 +19,62 @@ pub enum Environment { Production, } +impl Environment { + /// Returns `true` if `self` is `Environment::Development`. + /// + /// # Example + /// + /// ```rust + /// use rocket::config::Environment; + /// + /// assert!(Environment::Development.is_dev()); + /// assert!(!Environment::Production.is_dev()); + /// ``` + #[inline] + pub fn is_dev(self) -> bool { + match self { + Development => true, + _ => false + } + } + + /// Returns `true` if `self` is `Environment::Staging`. + /// + /// # Example + /// + /// ```rust + /// use rocket::config::Environment; + /// + /// assert!(Environment::Staging.is_stage()); + /// assert!(!Environment::Production.is_stage()); + /// ``` + #[inline] + pub fn is_stage(self) -> bool { + match self { + Staging => true, + _ => false + } + } + + /// Returns `true` if `self` is `Environment::Production`. + /// + /// # Example + /// + /// ```rust + /// use rocket::config::Environment; + /// + /// assert!(Environment::Production.is_prod()); + /// assert!(!Environment::Staging.is_prod()); + /// ``` + #[inline] + pub fn is_prod(self) -> bool { + match self { + Production => true, + _ => false + } + } +} + impl Environment { /// Retrieves the "active" environment as determined by the `ROCKET_ENV` /// environment variable. If `ROCKET_ENV` is not set, returns `Development`. diff --git a/lib/src/config/error.rs b/lib/src/config/error.rs index 6ca2c6b8..51bc31c1 100644 --- a/lib/src/config/error.rs +++ b/lib/src/config/error.rs @@ -47,8 +47,8 @@ pub enum ConfigError { BadType(String, &'static str, &'static str, PathBuf), /// There was a TOML parsing error. /// - /// Parameters: (toml_source_string, filename, error_list) - ParseError(String, PathBuf, Vec), + /// Parameters: (toml_source_string, filename, error_description, line/col) + ParseError(String, PathBuf, String, Option<(usize, usize)>), /// There was a TOML parsing error in a config environment variable. /// /// Parameters: (env_key, env_value, error) @@ -87,15 +87,14 @@ impl ConfigError { info_!("expected value to be {}, but found {}", White.paint(expected), White.paint(actual)); } - ParseError(ref source, ref filename, ref errors) => { - for error in errors { - let (lo, hi) = error.byte_range; - let (line, col) = error.start; - let error_source = &source[lo..hi]; - - error!("config file could not be parsed as TOML"); - info_!("at {:?}:{}:{}", White.paint(filename), line + 1, col + 1); - trace_!("'{}' - {}", error_source, White.paint(&error.desc)); + ParseError(_, ref filename, ref desc, line_col) => { + error!("config file failed to parse due to invalid TOML"); + info_!("{}", desc); + if let Some((line, col)) = line_col { + info_!("at {:?}:{}:{}", White.paint(filename), + White.paint(line + 1), White.paint(col + 1)); + } else { + info_!("in {:?}", White.paint(filename)); } } BadEnvVal(ref key, ref value, ref error) => { diff --git a/lib/src/config/mod.rs b/lib/src/config/mod.rs index 11981954..68b5714d 100644 --- a/lib/src/config/mod.rs +++ b/lib/src/config/mod.rs @@ -3,7 +3,7 @@ //! This module implements configuration handling for Rocket. It implements the //! parsing and interpretation of the `Rocket.toml` config file and //! `ROCKET_{PARAM}` environment variables. It also allows libraries to access -//! values that have been configured by the user. +//! user-configured values. //! //! ## Application Configuration //! @@ -43,13 +43,15 @@ //! * **secret_key**: _[string]_ a 256-bit base64 encoded string (44 //! characters) to use as the secret key //! * example: `"8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="` -//! * **tls**: _[table]_ a table with two keys: 1) `certs`: _[string]_ a path -//! to a certificate chain in PEM format, and 2) `key`: _[string]_ a path to a -//! private key file in PEM format for the certificate in `certs` +//! * **tls**: _[table]_ a table with two keys: +//! 1. `certs`: _[string]_ a path to a certificate chain in PEM format +//! 2. `key`: _[string]_ a path to a private key file in PEM format for the +//! certificate in `certs` +//! //! * example: `{ certs = "/path/to/certs.pem", key = "/path/to/key.pem" }` -//! * **limits**: _[table]_ a table where the key _[string]_ corresponds to a -//! data type and the value _[u64]_ corresponds to the maximum size in bytes -//! Rocket should accept for that type. +//! * **limits**: _[table]_ a table where each key (_[string]_) corresponds to +//! a data type and the value (_[u64]_) corresponds to the maximum size in +//! bytes Rocket should accept for that type. //! * example: `{ forms = 65536 }` (maximum form size to 64KiB) //! //! ### Rocket.toml @@ -58,7 +60,7 @@ //! each environment. The file is optional. If it is not present, the default //! configuration parameters are used. //! -//! The file must be a series of TOML tables, at most one for each environment +//! The file must be a series of TOML tables, at most one for each environment, //! and an optional "global" table, where each table contains key-value pairs //! corresponding to configuration parameters for that environment. If a //! configuration parameter is missing, the default value is used. The following @@ -69,7 +71,7 @@ //! [development] //! address = "localhost" //! port = 8000 -//! workers = max(number_of_cpus, 2) +//! workers = [number_of_cpus * 2] //! log = "normal" //! secret_key = [randomly generated at launch] //! limits = { forms = 32768 } @@ -77,7 +79,7 @@ //! [staging] //! address = "0.0.0.0" //! port = 80 -//! workers = max(number_of_cpus, 2) +//! workers = [number_of_cpus * 2] //! log = "normal" //! secret_key = [randomly generated at launch] //! limits = { forms = 32768 } @@ -85,7 +87,7 @@ //! [production] //! address = "0.0.0.0" //! port = 80 -//! workers = max(number_of_cpus, 2) +//! workers = [number_of_cpus * 2] //! log = "critical" //! secret_key = [randomly generated at launch] //! limits = { forms = 32768 } @@ -115,6 +117,19 @@ //! address = "0.0.0.0" //! ``` //! +//! ### TLS Configuration +//! +//! TLS can be enabled by specifying the `tls.key` and `tls.certs` parameters. +//! Rocket must be compiled with the `tls` feature enabled for the parameters to +//! take effect. The recommended way to specify the parameters is via the +//! `global` environment: +//! +//! ```toml +//! [global.tls] +//! certs = "/path/to/certs.pem" +//! key = "/path/to/key.pem" +//! ``` +//! //! ### Environment Variables //! //! All configuration parameters, including extras, can be overridden through @@ -142,19 +157,6 @@ //! ROCKET_DICT={key="abc",val=123} //! ``` //! -//! ### TLS Configuration -//! -//! TLS can be enabled by specifying the `tls.key` and `tls.certs` parameters. -//! Rocket must be compiled with the `tls` feature enabled for the parameters to -//! take effect. The recommended way to specify the parameters is via the -//! `global` environment: -//! -//! ```toml -//! [global.tls] -//! certs = "/path/to/certs.pem" -//! key = "/path/to/key.pem" -//! ``` -//! //! ## Retrieving Configuration Parameters //! //! Configuration parameters for the currently active configuration environment @@ -203,7 +205,7 @@ use std::env; use toml; pub use self::custom_values::Limits; -pub use toml::{Array, Table, Value}; +pub use toml::value::{Array, Table, Value, Datetime}; pub use self::error::{ConfigError, ParsingError}; pub use self::environment::Environment; pub use self::config::Config; @@ -391,23 +393,18 @@ impl RocketConfig { /// Parses the configuration from the Rocket.toml file. Also overrides any /// values there with values from the environment. fn parse>(src: String, filename: P) -> Result { - // Get a PathBuf version of the filename. - let path = filename.as_ref().to_path_buf(); + use self::ConfigError::ParseError; // Parse the source as TOML, if possible. - let mut parser = toml::Parser::new(&src); - let toml = parser.parse().ok_or_else(|| { - let source = src.clone(); - let errors = parser.errors.iter() - .map(|error| ParsingError { - byte_range: (error.lo, error.hi), - start: parser.to_linecol(error.lo), - end: parser.to_linecol(error.hi), - desc: error.desc.clone(), - }); - - ConfigError::ParseError(source, path.clone(), errors.collect()) - })?; + let path = filename.as_ref().to_path_buf(); + let table = match src.parse::() { + Ok(toml::Value::Table(table)) => table, + Ok(value) => { + let err = format!("expected a table, found {}", value.type_str()); + return Err(ConfigError::ParseError(src, path, err, Some((1, 1)))); + } + Err(e) => return Err(ParseError(src, path, e.to_string(), e.line_col())) + }; // Create a config with the defaults; set the env to the active one. let mut config = RocketConfig::active_default(filename)?; @@ -416,7 +413,7 @@ impl RocketConfig { let mut global = None; // Parse the values from the TOML file. - for (entry, value) in toml { + for (entry, value) in table { // Each environment must be a table. let kv_pairs = match value.as_table() { Some(table) => table, diff --git a/lib/src/data/data.rs b/lib/src/data/data.rs index 31b71f8b..f212f856 100644 --- a/lib/src/data/data.rs +++ b/lib/src/data/data.rs @@ -66,8 +66,7 @@ impl Data { /// the data in a request. pub fn open(mut self) -> DataStream { let buffer = ::std::mem::replace(&mut self.buffer, vec![]); - let empty_stream = Cursor::new(vec![]) - .chain(NetStream::Local(Cursor::new(vec![]))); + let empty_stream = Cursor::new(vec![]).chain(NetStream::Empty); // FIXME: Insert a `BufReader` in front of the `NetStream` with capacity // 4096. We need the new `Chain` methods to get the inner reader to @@ -205,9 +204,9 @@ impl Data { } /// This creates a `data` object from a local data source `data`. + #[inline] pub(crate) fn local(data: Vec) -> Data { - let empty_stream = Cursor::new(vec![]) - .chain(NetStream::Local(Cursor::new(vec![]))); + let empty_stream = Cursor::new(vec![]).chain(NetStream::Empty); Data { buffer: data, diff --git a/lib/src/data/net_stream.rs b/lib/src/data/net_stream.rs index a53b037d..b4becdfd 100644 --- a/lib/src/data/net_stream.rs +++ b/lib/src/data/net_stream.rs @@ -1,4 +1,4 @@ -use std::io::{self, Cursor}; +use std::io; use std::net::{SocketAddr, Shutdown}; use std::time::Duration; @@ -16,7 +16,7 @@ pub enum NetStream { Http(HttpStream), #[cfg(feature = "tls")] Https(HttpsStream), - Local(Cursor>) + Empty, } impl io::Read for NetStream { @@ -25,9 +25,10 @@ impl io::Read for NetStream { trace_!("NetStream::read()"); let res = match *self { Http(ref mut stream) => stream.read(buf), - Local(ref mut stream) => stream.read(buf), - #[cfg(feature = "tls")] Https(ref mut stream) => stream.read(buf) + #[cfg(feature = "tls")] Https(ref mut stream) => stream.read(buf), + Empty => Ok(0), }; + trace_!("NetStream::read() -- complete"); res } @@ -39,8 +40,8 @@ impl io::Write for NetStream { trace_!("NetStream::write()"); match *self { Http(ref mut stream) => stream.write(buf), - Local(ref mut stream) => stream.write(buf), - #[cfg(feature = "tls")] Https(ref mut stream) => stream.write(buf) + #[cfg(feature = "tls")] Https(ref mut stream) => stream.write(buf), + Empty => Ok(0), } } @@ -48,8 +49,8 @@ impl io::Write for NetStream { fn flush(&mut self) -> io::Result<()> { match *self { Http(ref mut stream) => stream.flush(), - Local(ref mut stream) => stream.flush(), - #[cfg(feature = "tls")] Https(ref mut stream) => stream.flush() + #[cfg(feature = "tls")] Https(ref mut stream) => stream.flush(), + Empty => Ok(()), } } } @@ -60,7 +61,7 @@ impl NetworkStream for NetStream { match *self { Http(ref mut stream) => stream.peer_addr(), #[cfg(feature = "tls")] Https(ref mut stream) => stream.peer_addr(), - Local(_) => Err(io::Error::from(io::ErrorKind::AddrNotAvailable)), + Empty => Err(io::Error::from(io::ErrorKind::AddrNotAvailable)), } } @@ -69,7 +70,7 @@ impl NetworkStream for NetStream { match *self { Http(ref stream) => stream.set_read_timeout(dur), #[cfg(feature = "tls")] Https(ref stream) => stream.set_read_timeout(dur), - Local(_) => Ok(()), + Empty => Ok(()), } } @@ -78,7 +79,7 @@ impl NetworkStream for NetStream { match *self { Http(ref stream) => stream.set_write_timeout(dur), #[cfg(feature = "tls")] Https(ref stream) => stream.set_write_timeout(dur), - Local(_) => Ok(()), + Empty => Ok(()), } } @@ -87,7 +88,7 @@ impl NetworkStream for NetStream { match *self { Http(ref mut stream) => stream.close(how), #[cfg(feature = "tls")] Https(ref mut stream) => stream.close(how), - Local(_) => Ok(()), + Empty => Ok(()), } } } diff --git a/lib/src/http/cookies.rs b/lib/src/http/cookies.rs index cc655629..253bbd1f 100644 --- a/lib/src/http/cookies.rs +++ b/lib/src/http/cookies.rs @@ -1,28 +1,23 @@ -use http::Header; - +use std::fmt; use std::cell::RefMut; -pub use cookie::{Cookie, CookieJar, Iter, CookieBuilder, Delta}; +pub use cookie::{Cookie, Key, CookieJar}; +use cookie::{SameSite, Delta}; -use cookie::{PrivateJar, Key}; +use http::Header; -impl<'a, 'c> From<&'a Cookie<'c>> for Header<'static> { - fn from(cookie: &Cookie) -> Header<'static> { - Header::new("Set-Cookie", cookie.encoded().to_string()) - } -} - -#[derive(Debug)] pub enum Cookies<'a> { - Jarred(RefMut<'a, CookieJar>), + Jarred(RefMut<'a, CookieJar>, &'a Key), Empty(CookieJar) } impl<'a> Cookies<'a> { - pub(crate) fn new(jar: RefMut<'a, CookieJar>) -> Cookies<'a> { - Cookies::Jarred(jar) + #[inline] + pub(crate) fn new(jar: RefMut<'a, CookieJar>, key: &'a Key) -> Cookies<'a> { + Cookies::Jarred(jar, key) } + #[inline] pub(crate) fn empty() -> Cookies<'static> { Cookies::Empty(CookieJar::new()) } @@ -34,41 +29,82 @@ impl<'a> Cookies<'a> { pub fn get(&self, name: &str) -> Option<&Cookie<'static>> { match *self { - Cookies::Jarred(ref jar) => jar.get(name), + Cookies::Jarred(ref jar, _) => jar.get(name), + Cookies::Empty(_) => None + } + } + + pub fn get_private(&mut self, name: &str) -> Option> { + match *self { + Cookies::Jarred(ref mut jar, key) => jar.private(key).get(name), Cookies::Empty(_) => None } } pub fn add(&mut self, cookie: Cookie<'static>) { - if let Cookies::Jarred(ref mut jar) = *self { + if let Cookies::Jarred(ref mut jar, _) = *self { jar.add(cookie) } } + pub fn add_private(&mut self, mut cookie: Cookie<'static>) { + cookie.set_http_only(true); + + if cookie.path().is_none() { + cookie.set_path("/"); + } + + if cookie.same_site().is_none() { + cookie.set_same_site(SameSite::Strict); + } + + if let Cookies::Jarred(ref mut jar, key) = *self { + jar.private(key).add(cookie) + } + } + pub fn remove(&mut self, cookie: Cookie<'static>) { - if let Cookies::Jarred(ref mut jar) = *self { + if let Cookies::Jarred(ref mut jar, _) = *self { jar.remove(cookie) } } - pub(crate) fn private(&mut self, key: &Key) -> PrivateJar { - match *self { - Cookies::Jarred(ref mut jar) => jar.private(key), - Cookies::Empty(ref mut jar) => jar.private(key) + pub fn remove_private(&mut self, mut cookie: Cookie<'static>) { + if cookie.path().is_none() { + cookie.set_path("/"); + } + + if let Cookies::Jarred(ref mut jar, key) = *self { + jar.private(key).remove(cookie) } } - pub fn iter(&self) -> Iter { + pub fn iter<'s>(&'s self) -> impl Iterator> { match *self { - Cookies::Jarred(ref jar) => jar.iter(), + Cookies::Jarred(ref jar, _) => jar.iter(), Cookies::Empty(ref jar) => jar.iter() } } pub(crate) fn delta(&self) -> Delta { match *self { - Cookies::Jarred(ref jar) => jar.delta(), + Cookies::Jarred(ref jar, _) => jar.delta(), Cookies::Empty(ref jar) => jar.delta() } } } + +impl<'a> fmt::Debug for Cookies<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Cookies::Jarred(ref jar, _) => write!(f, "{:?}", jar), + Cookies::Empty(ref jar) => write!(f, "{:?}", jar) + } + } +} + +impl<'a, 'c> From<&'a Cookie<'c>> for Header<'static> { + fn from(cookie: &Cookie) -> Header<'static> { + Header::new("Set-Cookie", cookie.encoded().to_string()) + } +} diff --git a/lib/src/http/mod.rs b/lib/src/http/mod.rs index 27639452..c70e6c1f 100644 --- a/lib/src/http/mod.rs +++ b/lib/src/http/mod.rs @@ -11,7 +11,6 @@ pub mod uri; #[macro_use] mod known_media_types; mod cookies; -mod session; mod method; mod media_type; mod content_type; @@ -36,5 +35,5 @@ pub use self::header::{Header, HeaderMap}; pub use self::raw_str::RawStr; pub use self::media_type::MediaType; -pub use self::cookies::*; -pub use self::session::*; +pub use self::cookies::{Cookie, Cookies}; +pub(crate) use self::cookies::{Key, CookieJar}; diff --git a/lib/src/http/session.rs b/lib/src/http/session.rs deleted file mode 100644 index e2c23ff6..00000000 --- a/lib/src/http/session.rs +++ /dev/null @@ -1,75 +0,0 @@ -use std::cell::{RefCell, RefMut}; - -use time::{self, Duration}; -use cookie::{Cookie, CookieJar, Delta}; -pub use cookie::Key; - -use http::{Header, Cookies}; - -const SESSION_PREFIX: &'static str = "__sess_"; - -pub struct Session<'a> { - cookies: RefCell>, - key: &'a Key -} - -impl<'a> Session<'a> { - #[inline(always)] - pub(crate) fn new(jar: RefMut<'a, CookieJar>, key: &'a Key) -> Session<'a> { - Session { cookies: RefCell::new(Cookies::new(jar)), key: key } - } - - #[inline(always)] - pub(crate) fn empty(key: &'a Key) -> Session<'a> { - Session { cookies: RefCell::new(Cookies::empty()), key: key } - } - - #[inline(always)] - pub(crate) fn header_for(cookie: &Cookie) -> Header<'static> { - Header::new("Set-Cookie", format!("{}{}", SESSION_PREFIX, cookie)) - } - - #[inline(always)] - pub(crate) fn parse_cookie(cookie_str: &str) -> Option> { - if !cookie_str.starts_with(SESSION_PREFIX) { - return None; - } - - Cookie::parse(&cookie_str[SESSION_PREFIX.len()..]).ok() - .map(|c| c.into_owned()) - } - - pub fn get(&self, name: &str) -> Option> { - self.cookies.borrow_mut().private(&self.key).get(name) - } - - pub fn set(&mut self, mut cookie: Cookie<'static>) { - cookie.set_http_only(true); - - if cookie.path().is_none() { - cookie.set_path("/"); - } - - // TODO: Should this be configurable? - if cookie.max_age().is_none() && cookie.expires().is_none() { - let session_lifetime = Duration::hours(3); - cookie.set_max_age(session_lifetime); - cookie.set_expires(time::now() + session_lifetime); - } - - self.cookies.get_mut().private(&self.key).add(cookie) - } - - pub fn remove(&mut self, mut cookie: Cookie<'static>) { - if cookie.path().is_none() { - cookie.set_path("/"); - } - - self.cookies.get_mut().private(&self.key).remove(cookie) - } - - #[inline(always)] - pub(crate) fn delta(&mut self) -> Delta { - self.cookies.get_mut().delta() - } -} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index c14b5707..5408c14e 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -10,6 +10,9 @@ #![plugin(pear_codegen)] +// TODO: Version URLs. +#![doc(html_root_url = "https://api.rocket.rs/rocket/")] + //! # Rocket - Core API Documentation //! //! Hello, and welcome to the core Rocket API documentation! @@ -92,10 +95,10 @@ //! //! ## Testing //! -//! Rocket includes a small testing library that can be used to test your Rocket -//! application. For information on how to test your Rocket applications, see -//! the [testing module](/rocket/testing) documentation. -//! +//! The [local](/rocket/local) module contains structures that facilitate unit +//! and itegration testing of a Rocket application. The [top-level `local` +//! module documentation](/rocket/local) and the [testing chapter of the +//! guide](https://rocket.rs/guide/testing/#testing) include detailed examples. #[macro_use] extern crate log; #[macro_use] extern crate pear; @@ -117,7 +120,7 @@ extern crate ordermap; #[cfg(test)] #[macro_use] extern crate lazy_static; #[doc(hidden)] #[macro_use] pub mod logger; -pub mod testing; +pub mod local; pub mod http; pub mod request; pub mod response; @@ -140,6 +143,7 @@ mod ext; #[doc(hidden)] pub use codegen::{StaticRouteInfo, StaticCatchInfo}; #[doc(inline)] pub use outcome::Outcome; #[doc(inline)] pub use data::Data; +#[doc(inline)] pub use config::Config; pub use router::Route; pub use request::{Request, State}; pub use error::{Error, LaunchError}; diff --git a/lib/src/local/client.rs b/lib/src/local/client.rs new file mode 100644 index 00000000..7994274d --- /dev/null +++ b/lib/src/local/client.rs @@ -0,0 +1,78 @@ +use {Rocket, Request}; +use local::LocalRequest; +use http::Method; +use http::uri::URI; +use error::LaunchError; + +pub struct Client { + rocket: Rocket, +} + +impl Client { + #[inline] + pub fn new(rocket: Rocket) -> Result { + if let Some(err) = rocket.prelaunch_check() { + return Err(err); + } + + Ok(Client { + rocket: rocket, + }) + } + + #[inline(always)] + pub fn rocket(&self) -> &Rocket { + &self.rocket + } + + #[inline(always)] + pub fn req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c> + where U: Into> + { + let request = Request::new(&self.rocket, method, uri); + LocalRequest::new(&self.rocket, request) + } + + #[inline(always)] + pub fn get<'c, 'u: 'c, U: Into>>(&'c self, uri: U) -> LocalRequest<'c> { + self.req(Method::Get, uri) + } + + #[inline(always)] + pub fn put<'c, 'u: 'c, U: Into>>(&'c self, uri: U) -> LocalRequest<'c> { + self.req(Method::Put, uri) + } + + #[inline(always)] + pub fn post<'c, 'u: 'c, U: Into>>(&'c self, uri: U) -> LocalRequest<'c> { + self.req(Method::Post, uri) + } + + #[inline(always)] + pub fn delete<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c> + where U: Into> + { + self.req(Method::Delete, uri) + } + + #[inline(always)] + pub fn options<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c> + where U: Into> + { + self.req(Method::Options, uri) + } + + #[inline(always)] + pub fn head<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c> + where U: Into> + { + self.req(Method::Head, uri) + } + + #[inline(always)] + pub fn patch<'c, 'u: 'c, U>(&'c self, uri: U) -> LocalRequest<'c> + where U: Into> + { + self.req(Method::Patch, uri) + } +} diff --git a/lib/src/local/mod.rs b/lib/src/local/mod.rs new file mode 100644 index 00000000..da765b81 --- /dev/null +++ b/lib/src/local/mod.rs @@ -0,0 +1,108 @@ +//! Structures for local dispatching of requests, primarily for testing. +//! +//! This module allows for simple request dispatching against a local, +//! non-networked instance of Rocket. The primary use of this module is to unit +//! and integration test Rocket applications by crafting requests, dispatching +//! them, and verifying the response. +//! +//! # Usage +//! +//! This module contains a [`Client`] structure that is used to create +//! [`LocalRequest`] structures that can be dispatched against a given +//! [`Rocket`] instance. Usage is straightforward: +//! +//! 1. Construct a `Rocket` instance that represents the application. +//! +//! ```rust +//! let rocket = rocket::ignite(); +//! # let _ = rocket; +//! ``` +//! +//! 2. Construct a `Client` using the `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 +//! # use rocket::local::Client; +//! # let rocket = rocket::ignite(); +//! # let client = Client::new(rocket).unwrap(); +//! let req = client.get("/"); +//! # let _ = req; +//! ``` +//! +//! 3. Dispatch the request to retrieve the response. +//! +//! ```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; +//! ``` +//! +//! All together and in idiomatic fashion, this might look like: +//! +//! ```rust +//! use rocket::local::Client; +//! +//! let client = Client::new(rocket::ignite()).expect("valid rocket"); +//! let response = client.post("/") +//! .body("Hello, world!") +//! .dispatch(); +//! # let _ = response; +//! ``` +//! +//! # Unit/Integration Testing +//! +//! This module can be used to test a Rocket application by constructing +//! requests via `Client` and validating the resulting response. As an example, +//! consider the following complete "Hello, world!" application, with testing. +//! +//! ```rust +//! #![feature(plugin)] +//! #![plugin(rocket_codegen)] +//! +//! extern crate rocket; +//! +//! #[get("/")] +//! fn hello() -> &'static str { +//! "Hello, world!" +//! } +//! +//! # fn main() { } +//! #[cfg(test)] +//! mod test { +//! use super::{rocket, hello}; +//! use rocket::local::Client; +//! +//! #[test] +//! fn test_hello_world() { +//! // Construct a client to use for dispatching requests. +//! let rocket = rocket::ignite().mount("/", routes![hello]); +//! let client = Client::new(rocket).expect("valid rocket instance"); +//! +//! // Dispatch a request to 'GET /' and validate the response. +//! let mut response = client.get("/").dispatch(); +//! assert_eq!(response.body_string(), Some("Hello, world!".into())); +//! } +//! } +//! ``` +//! +//! [`Client`]: /rocket/local/struct.Client.html +//! [`LocalRequest`]: /rocket/local/struct.LocalRequest.html +//! [`Rocket`]: /rocket/struct.Rocket.html +//! + +mod request; +mod client; + +pub use self::request::{LocalResponse, LocalRequest}; +pub use self::client::Client; diff --git a/lib/src/local/request.rs b/lib/src/local/request.rs new file mode 100644 index 00000000..54980497 --- /dev/null +++ b/lib/src/local/request.rs @@ -0,0 +1,265 @@ +use std::fmt; +use std::rc::Rc; +use std::mem::transmute; +use std::net::SocketAddr; +use std::ops::{Deref, DerefMut}; + +use {Rocket, Request, Response, Data}; +use http::{Header, Cookie}; + +pub struct LocalRequest<'c> { + rocket: &'c Rocket, + ptr: *mut Request<'c>, + request: Rc>, + data: Vec +} + +pub struct LocalResponse<'c> { + _request: Rc>, + response: Response<'c>, +} + +impl<'c> Deref for LocalResponse<'c> { + type Target = Response<'c>; + + #[inline(always)] + fn deref(&self) -> &Response<'c> { + &self.response + } +} + +impl<'c> DerefMut for LocalResponse<'c> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Response<'c> { + &mut self.response + } +} + +impl<'c> LocalRequest<'c> { + #[inline(always)] + pub fn new(rocket: &'c Rocket, request: Request<'c>) -> LocalRequest<'c> { + let mut req = Rc::new(request); + let ptr = Rc::get_mut(&mut req).unwrap() as *mut Request; + LocalRequest { rocket: rocket, ptr: ptr, request: req, data: vec![] } + } + + #[inline] + pub fn inner(&self) -> &Request<'c> { + &*self.request + } + + #[inline(always)] + fn request(&mut self) -> &mut Request<'c> { + unsafe { &mut *self.ptr } + } + + #[inline(always)] + pub fn dispatch(mut self) -> LocalResponse<'c> { + let req = unsafe { transmute(self.request()) }; + let response = self.rocket.dispatch(req, Data::local(self.data)); + + LocalResponse { + _request: self.request, + response: response + } + } + + #[inline(always)] + pub fn mut_dispatch(&mut self) -> LocalResponse<'c> { + let data = ::std::mem::replace(&mut self.data, vec![]); + let req = unsafe { transmute(self.request()) }; + let response = self.rocket.dispatch(req, Data::local(data)); + + LocalResponse { + _request: self.request.clone(), + response: response + } + } + + #[inline(always)] + pub fn cloned_dispatch(&self) -> LocalResponse<'c> { + let cloned = (*self.request).clone(); + let mut req = LocalRequest::new(self.rocket, cloned); + req.data = self.data.clone(); + req.dispatch() + } + + /// Add a header to this request. + /// + /// # Examples + /// + /// Add the Content-Type header: + /// + /// ```rust + /// use rocket::local::Client; + /// use rocket::http::ContentType; + /// + /// # #[allow(unused_variables)] + /// let client = Client::new(rocket::ignite()).unwrap(); + /// let req = client.get("/").header(ContentType::JSON); + /// ``` + #[inline] + pub fn header>>(mut self, header: H) -> Self { + self.request().add_header(header.into()); + self + } + + /// Adds a header to this request without consuming `self`. + /// + /// # Examples + /// + /// Add the Content-Type header: + /// + /// ```rust + /// use rocket::local::Client; + /// use rocket::http::ContentType; + /// + /// let client = Client::new(rocket::ignite()).unwrap(); + /// let mut req = client.get("/"); + /// req.add_header(ContentType::JSON); + /// ``` + #[inline] + pub fn add_header>>(&mut self, header: H) { + self.request().add_header(header.into()); + } + + /// Set the remote address of this request. + /// + /// # Examples + /// + /// Set the remote address to "8.8.8.8:80": + /// + /// ```rust + /// use rocket::local::Client; + /// + /// let client = Client::new(rocket::ignite()).unwrap(); + /// let address = "8.8.8.8:80".parse().unwrap(); + /// let req = client.get("/").remote(address); + /// ``` + #[inline] + pub fn remote(mut self, address: SocketAddr) -> Self { + self.request().set_remote(address); + self + } + + /// Add a cookie to this request. + /// + /// # Examples + /// + /// Add `user_id` cookie: + /// + /// ```rust + /// use rocket::local::Client; + /// use rocket::http::Cookie; + /// + /// let client = Client::new(rocket::ignite()).unwrap(); + /// # #[allow(unused_variables)] + /// let req = client.get("/") + /// .cookie(Cookie::new("username", "sb")) + /// .cookie(Cookie::new("user_id", format!("{}", 12))); + /// ``` + #[inline] + pub fn cookie(self, cookie: Cookie<'static>) -> Self { + self.request.cookies().add(cookie); + self + } + + // TODO: For CGI, we want to be able to set the body to be stdin without + // actually reading everything into a vector. Can we allow that here while + // keeping the simplicity? Looks like it would require us to reintroduce a + // NetStream::Local(Box) or something like that. + + /// Set the body (data) of the request. + /// + /// # Examples + /// + /// Set the body to be a JSON structure; also sets the Content-Type. + /// + /// ```rust + /// use rocket::local::Client; + /// use rocket::http::ContentType; + /// + /// let client = Client::new(rocket::ignite()).unwrap(); + /// # #[allow(unused_variables)] + /// let req = client.post("/") + /// .header(ContentType::JSON) + /// .body(r#"{ "key": "value", "array": [1, 2, 3], }"#); + /// ``` + #[inline] + pub fn body>(mut self, body: S) -> Self { + self.data = body.as_ref().into(); + self + } +} + +impl<'c> fmt::Debug for LocalRequest<'c> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.request, f) + } +} + +impl<'c> fmt::Debug for LocalResponse<'c> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.response, f) + } +} + +// fn test() { +// use local::Client; + +// let rocket = Rocket::ignite(); +// let res = { +// let mut client = Client::new(rocket).unwrap(); +// client.get("/").dispatch() +// }; + +// // let client = Client::new(rocket).unwrap(); +// // let res1 = client.get("/").dispatch(); +// // let res2 = client.get("/").dispatch(); +// } + +// fn test() { +// use local::Client; + +// let rocket = Rocket::ignite(); +// let res = { +// Client::new(rocket).unwrap() +// .get("/").dispatch(); +// }; + +// // let client = Client::new(rocket).unwrap(); +// // let res1 = client.get("/").dispatch(); +// // let res2 = client.get("/").dispatch(); +// } + +// fn test() { +// use local::Client; + +// let rocket = Rocket::ignite(); +// let client = Client::new(rocket).unwrap(); + +// let res = { +// let x = client.get("/").dispatch(); +// let y = client.get("/").dispatch(); +// }; + +// let x = client; +// } + +// fn test() { +// use local::Client; + +// let rocket1 = Rocket::ignite(); +// let rocket2 = Rocket::ignite(); + +// let client1 = Client::new(rocket1).unwrap(); +// let client2 = Client::new(rocket2).unwrap(); + +// let res = { +// let mut res1 = client1.get("/"); +// res1.set_client(&client2); +// res1 +// }; + +// drop(client1); +// } diff --git a/lib/src/request/from_request.rs b/lib/src/request/from_request.rs index 52b1b297..00f344bf 100644 --- a/lib/src/request/from_request.rs +++ b/lib/src/request/from_request.rs @@ -6,7 +6,7 @@ use request::Request; use outcome::{self, IntoOutcome}; use outcome::Outcome::*; -use http::{Status, ContentType, Accept, Method, Cookies, Session}; +use http::{Status, ContentType, Accept, Method, Cookies}; use http::uri::URI; /// Type alias for the `Outcome` of a `FromRequest` conversion. @@ -100,10 +100,10 @@ impl IntoOutcome for Result { /// /// * **Cookies** /// -/// Returns a borrow to the [Cookies](/rocket/http/type.Cookies.html) in the -/// incoming request. Note that `Cookies` implements internal mutability, so -/// a handle to `Cookies` allows you to get _and_ set cookies in the -/// request. +/// Returns a borrow to the [Cookies](/rocket/http/enum.Cookies.html) in +/// the incoming request. Note that `Cookies` implements internal +/// mutability, so a handle to `Cookies` allows you to get _and_ set cookies +/// in the request. /// /// _This implementation always returns successfully._ /// @@ -236,14 +236,6 @@ impl<'a, 'r> FromRequest<'a, 'r> for Cookies<'a> { } } -impl<'a, 'r> FromRequest<'a, 'r> for Session<'a> { - type Error = (); - - fn from_request(request: &'a Request<'r>) -> Outcome { - Success(request.session()) - } -} - impl<'a, 'r> FromRequest<'a, 'r> for &'a Accept { type Error = (); diff --git a/lib/src/request/request.rs b/lib/src/request/request.rs index 355cddbd..5939c2e8 100644 --- a/lib/src/request/request.rs +++ b/lib/src/request/request.rs @@ -4,32 +4,26 @@ use std::fmt; use std::str; use yansi::Paint; - use state::{Container, Storage}; -use error::Error; use super::{FromParam, FromSegments, FromRequest, Outcome}; +use rocket::Rocket; use router::Route; use config::{Config, Limits}; use http::uri::{URI, Segments}; -use http::{Method, Header, HeaderMap, Cookies, Session, CookieJar}; +use error::Error; +use http::{Method, Header, HeaderMap, Cookies, CookieJar}; use http::{RawStr, ContentType, Accept, MediaType}; use http::hyper; -struct PresetState<'r> { - // The running Rocket instances configuration. - config: &'r Config, - // The managed state of the running Rocket instance. - state: &'r Container, -} - +#[derive(Clone)] struct RequestState<'r> { - preset: Option>, + config: &'r Config, + state: &'r Container, params: RefCell>, route: Cell>, cookies: RefCell, - session: RefCell, accept: Storage>, content_type: Storage>, } @@ -41,6 +35,7 @@ struct RequestState<'r> { /// [FromRequest](/rocket/request/trait.FromRequest.html) implementations. It /// contains all of the information for a given web request except for the body /// data. This includes the HTTP method, URI, cookies, headers, and more. +#[derive(Clone)] pub struct Request<'r> { method: Method, uri: URI<'r>, @@ -53,45 +48,45 @@ impl<'r> Request<'r> { /// Create a new `Request` with the given `method` and `uri`. The `uri` /// parameter can be of any type that implements `Into` including /// `&str` and `String`; it must be a valid absolute URI. - /// - /// # Example - /// - /// ```rust - /// use rocket::Request; - /// use rocket::http::Method; - /// - /// # #[allow(unused_variables)] - /// let request = Request::new(Method::Get, "/uri"); - /// ``` #[inline(always)] - pub fn new<'s: 'r, U: Into>>(method: Method, uri: U) -> Request<'r> { + pub(crate) fn new<'s: 'r, U: Into>>(rocket: &'r Rocket, + method: Method, + uri: U) -> Request<'r> { Request { method: method, uri: uri.into(), headers: HeaderMap::new(), remote: None, state: RequestState { - preset: None, + config: &rocket.config, + state: &rocket.state, route: Cell::new(None), params: RefCell::new(Vec::new()), cookies: RefCell::new(CookieJar::new()), - session: RefCell::new(CookieJar::new()), accept: Storage::new(), content_type: Storage::new(), } } } + #[doc(hidden)] + pub fn example(method: Method, uri: &str, f: F) { + let rocket = Rocket::custom(Config::development().unwrap(), true); + let mut request = Request::new(&rocket, method, uri); + f(&mut request); + } + /// Retrieve the method from `self`. /// /// # Example /// /// ```rust - /// use rocket::Request; + /// # use rocket::Request; /// use rocket::http::Method; /// - /// let request = Request::new(Method::Get, "/uri"); + /// # Request::example(Method::Get, "/uri", |request| { /// assert_eq!(request.method(), Method::Get); + /// # }); /// ``` #[inline(always)] pub fn method(&self) -> Method { @@ -103,14 +98,15 @@ impl<'r> Request<'r> { /// # Example /// /// ```rust - /// use rocket::Request; + /// # use rocket::Request; /// use rocket::http::Method; /// - /// let mut request = Request::new(Method::Get, "/uri"); + /// # Request::example(Method::Get, "/uri", |request| { /// assert_eq!(request.method(), Method::Get); /// /// request.set_method(Method::Post); /// assert_eq!(request.method(), Method::Post); + /// # }); /// ``` #[inline(always)] pub fn set_method(&mut self, method: Method) { @@ -122,11 +118,11 @@ impl<'r> Request<'r> { /// # Example /// /// ```rust - /// use rocket::Request; - /// use rocket::http::Method; - /// - /// let request = Request::new(Method::Get, "/uri"); + /// # use rocket::Request; + /// # use rocket::http::Method; + /// # Request::example(Method::Get, "/uri", |request| { /// assert_eq!(request.uri().as_str(), "/uri"); + /// # }); /// ``` #[inline(always)] pub fn uri(&self) -> &URI { @@ -140,13 +136,12 @@ impl<'r> Request<'r> { /// # Example /// /// ```rust - /// use rocket::Request; - /// use rocket::http::Method; - /// - /// let mut request = Request::new(Method::Get, "/uri"); - /// + /// # use rocket::Request; + /// # use rocket::http::Method; + /// # Request::example(Method::Get, "/uri", |mut request| { /// request.set_uri("/hello/Sergio?type=greeting"); /// assert_eq!(request.uri().as_str(), "/hello/Sergio?type=greeting"); + /// # }); /// ``` #[inline(always)] pub fn set_uri<'u: 'r, U: Into>>(&mut self, uri: U) { @@ -161,11 +156,11 @@ impl<'r> Request<'r> { /// # Example /// /// ```rust - /// use rocket::Request; - /// use rocket::http::Method; - /// - /// let request = Request::new(Method::Get, "/uri"); + /// # use rocket::Request; + /// # use rocket::http::Method; + /// # Request::example(Method::Get, "/uri", |request| { /// assert!(request.remote().is_none()); + /// # }); /// ``` #[inline(always)] pub fn remote(&self) -> Option { @@ -179,17 +174,17 @@ impl<'r> Request<'r> { /// Set the remote address to be 127.0.0.1:8000: /// /// ```rust - /// use rocket::Request; - /// use rocket::http::Method; + /// # use rocket::Request; + /// # use rocket::http::Method; /// use std::net::{SocketAddr, IpAddr, Ipv4Addr}; /// - /// let mut request = Request::new(Method::Get, "/uri"); - /// + /// # Request::example(Method::Get, "/uri", |mut request| { /// let (ip, port) = (IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8000); /// let localhost = SocketAddr::new(ip, port); /// request.set_remote(localhost); /// /// assert_eq!(request.remote(), Some(localhost)); + /// # }); /// ``` #[inline(always)] pub fn set_remote(&mut self, address: SocketAddr) { @@ -202,12 +197,12 @@ impl<'r> Request<'r> { /// # Example /// /// ```rust - /// use rocket::Request; - /// use rocket::http::Method; - /// - /// let request = Request::new(Method::Get, "/uri"); + /// # use rocket::Request; + /// # use rocket::http::Method; + /// # Request::example(Method::Get, "/uri", |request| { /// let header_map = request.headers(); /// assert!(header_map.is_empty()); + /// # }); /// ``` #[inline(always)] pub fn headers(&self) -> &HeaderMap<'r> { @@ -219,18 +214,20 @@ impl<'r> Request<'r> { /// # Example /// /// ```rust - /// use rocket::Request; - /// use rocket::http::{Method, ContentType}; + /// # use rocket::Request; + /// # use rocket::http::Method; + /// use rocket::http::ContentType; /// - /// let mut request = Request::new(Method::Get, "/uri"); + /// # Request::example(Method::Get, "/uri", |mut request| { /// assert!(request.headers().is_empty()); /// /// request.add_header(ContentType::HTML); /// assert!(request.headers().contains("Content-Type")); /// assert_eq!(request.headers().len(), 1); + /// # }); /// ``` #[inline(always)] - pub fn add_header>>(&mut self, header: H) { + pub fn add_header<'h: 'r, H: Into>>(&mut self, header: H) { self.headers.add(header.into()); } @@ -240,10 +237,11 @@ impl<'r> Request<'r> { /// # Example /// /// ```rust - /// use rocket::Request; - /// use rocket::http::{Method, ContentType}; + /// # use rocket::Request; + /// # use rocket::http::Method; + /// use rocket::http::ContentType; /// - /// let mut request = Request::new(Method::Get, "/uri"); + /// # Request::example(Method::Get, "/uri", |mut request| { /// assert!(request.headers().is_empty()); /// /// request.add_header(ContentType::Any); @@ -251,13 +249,14 @@ impl<'r> Request<'r> { /// /// request.replace_header(ContentType::PNG); /// assert_eq!(request.headers().get_one("Content-Type"), Some("image/png")); + /// # }); /// ``` #[inline(always)] - pub fn replace_header>>(&mut self, header: H) { + pub fn replace_header<'h: 'r, H: Into>>(&mut self, header: H) { self.headers.replace(header.into()); } - /// Returns a borrow to the cookies in `self`. + /// Returns a wrapped borrow to the cookies in `self`. /// /// Note that `Cookies` implements internal mutability, so this method /// allows you to get _and_ set cookies in `self`. @@ -267,17 +266,18 @@ impl<'r> Request<'r> { /// Add a new cookie to a request's cookies: /// /// ```rust - /// use rocket::Request; - /// use rocket::http::{Cookie, Method}; + /// # use rocket::Request; + /// # use rocket::http::Method; + /// use rocket::http::Cookie; /// - /// let request = Request::new(Method::Get, "/uri"); + /// # Request::example(Method::Get, "/uri", |mut request| { /// request.cookies().add(Cookie::new("key", "val")); /// request.cookies().add(Cookie::new("ans", format!("life: {}", 38 + 4))); + /// # }); /// ``` - #[inline] pub fn cookies(&self) -> Cookies { match self.state.cookies.try_borrow_mut() { - Ok(jar) => Cookies::new(jar), + Ok(jar) => Cookies::new(jar, self.state.config.secret_key()), Err(_) => { error_!("Multiple `Cookies` instances are active at once."); info_!("An instance of `Cookies` must be dropped before another \ @@ -288,21 +288,6 @@ impl<'r> Request<'r> { } } - #[inline] - pub fn session(&self) -> Session { - let key = self.preset().config.secret_key(); - match self.state.session.try_borrow_mut() { - Ok(jar) => Session::new(jar, key), - Err(_) => { - error_!("Multiple `Session` instances are active at once."); - info_!("An instance of `Session` must be dropped before another \ - can be retrieved."); - warn_!("The retrieved `Session` instance will be empty."); - Session::empty(key) - } - } - } - /// Returns the Content-Type header of `self`. If the header is not present, /// returns `None`. The Content-Type header is cached after the first call /// to this function. As a result, subsequent calls will always return the @@ -311,20 +296,22 @@ impl<'r> Request<'r> { /// # Example /// /// ```rust - /// use rocket::Request; - /// use rocket::http::{Method, ContentType}; - /// - /// let mut request = Request::new(Method::Get, "/uri"); + /// # use rocket::Request; + /// # use rocket::http::Method; + /// # Request::example(Method::Get, "/uri", |mut request| { /// assert_eq!(request.content_type(), None); + /// # }); /// ``` /// /// ```rust - /// use rocket::Request; - /// use rocket::http::{Method, ContentType}; + /// # use rocket::Request; + /// # use rocket::http::Method; + /// use rocket::http::ContentType; /// - /// let mut request = Request::new(Method::Get, "/uri"); + /// # Request::example(Method::Get, "/uri", |mut request| { /// request.add_header(ContentType::JSON); /// assert_eq!(request.content_type(), Some(&ContentType::JSON)); + /// # }); /// ``` #[inline(always)] pub fn content_type(&self) -> Option<&ContentType> { @@ -361,7 +348,7 @@ impl<'r> Request<'r> { /// Get the limits. pub fn limits(&self) -> &'r Limits { - &self.preset().config.limits + &self.state.config.limits } /// Get the current route, if any. @@ -463,17 +450,6 @@ impl<'r> Request<'r> { Some(Segments(&path[i..j])) } - #[inline(always)] - fn preset(&self) -> &PresetState<'r> { - match self.state.preset { - Some(ref state) => state, - None => { - error_!("Internal Rocket error: preset state is unset!"); - panic!("Please report this error to the GitHub issue tracker."); - } - } - } - /// Set `self`'s parameters given that the route used to reach this request /// was `route`. This should only be used internally by `Rocket` as improper /// use may result in out of bounds indexing. @@ -490,12 +466,6 @@ impl<'r> Request<'r> { self.state.cookies = RefCell::new(jar); } - /// Replace all of the session cookie in `self` with those in `jar`. - #[inline] - pub(crate) fn set_session(&mut self, jar: CookieJar) { - self.state.session = RefCell::new(jar); - } - /// Try to derive some guarded value from `self`. #[inline(always)] pub fn guard<'a, T: FromRequest<'a, 'r>>(&'a self) -> Outcome { @@ -505,17 +475,12 @@ impl<'r> Request<'r> { /// Get the managed state T, if it exists. For internal use only! #[inline(always)] pub(crate) fn get_state(&self) -> Option<&'r T> { - self.preset().state.try_get() - } - - /// Set the precomputed state. For internal use only! - #[inline(always)] - pub(crate) fn set_preset(&mut self, config: &'r Config, state: &'r Container) { - self.state.preset = Some(PresetState { config, state }); + self.state.state.try_get() } /// Convert from Hyper types into a Rocket Request. - pub(crate) fn from_hyp(h_method: hyper::Method, + pub(crate) fn from_hyp(rocket: &'r Rocket, + h_method: hyper::Method, h_headers: hyper::header::Headers, h_uri: hyper::RequestUri, h_addr: SocketAddr, @@ -533,13 +498,12 @@ impl<'r> Request<'r> { }; // Construct the request object. - let mut request = Request::new(method, uri); + let mut request = Request::new(rocket, method, uri); request.set_remote(h_addr); // Set the request cookies, if they exist. if let Some(cookie_headers) = h_headers.get_raw("Cookie") { let mut cookie_jar = CookieJar::new(); - let mut session_jar = CookieJar::new(); for header in cookie_headers { let raw_str = match ::std::str::from_utf8(header) { Ok(string) => string, @@ -547,16 +511,13 @@ impl<'r> Request<'r> { }; for cookie_str in raw_str.split(";").map(|s| s.trim()) { - if let Some(cookie) = Session::parse_cookie(cookie_str) { - session_jar.add_original(cookie); - } else if let Some(cookie) = Cookies::parse_cookie(cookie_str) { + if let Some(cookie) = Cookies::parse_cookie(cookie_str) { cookie_jar.add_original(cookie); } } } request.set_cookies(cookie_jar); - request.set_session(session_jar); } // Set the rest of the headers. @@ -575,6 +536,17 @@ impl<'r> Request<'r> { } } +impl<'r> fmt::Debug for Request<'r> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("Request") + .field("method", &self.method) + .field("uri", &self.uri) + .field("headers", &self.headers()) + .field("remote", &self.remote()) + .finish() + } +} + impl<'r> fmt::Display for Request<'r> { /// Pretty prints a Request. This is primarily used by Rocket's logging /// infrastructure. diff --git a/lib/src/request/tests.rs b/lib/src/request/tests.rs index 9b7ab7b9..7d820961 100644 --- a/lib/src/request/tests.rs +++ b/lib/src/request/tests.rs @@ -1,7 +1,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::collections::HashMap; -use Request; +use {Rocket, Request, Config}; use http::hyper; macro_rules! assert_headers { @@ -20,7 +20,9 @@ macro_rules! assert_headers { $(expected.entry($key).or_insert(vec![]).append(&mut vec![$($value),+]);)+ // Dispatch the request and check that the headers are what we expect. - let req = Request::from_hyp(h_method, h_headers, h_uri, h_addr).unwrap(); + let config = Config::development().unwrap(); + let r = Rocket::custom(config, true); + let req = Request::from_hyp(&r, h_method, h_headers, h_uri, h_addr).unwrap(); let actual_headers = req.headers(); for (key, values) in expected.iter() { let actual: Vec<_> = actual_headers.get(key).collect(); diff --git a/lib/src/response/response.rs b/lib/src/response/response.rs index e1aea303..6689d685 100644 --- a/lib/src/response/response.rs +++ b/lib/src/response/response.rs @@ -903,7 +903,29 @@ impl<'r> Response<'r> { /// ``` #[inline(always)] pub fn body_string(&mut self) -> Option { - self.take_body().and_then(|b| b.into_string()) + self.take_body().and_then(Body::into_string) + } + + /// Consumes `self's` body and reads it into a `Vec` of `u8` bytes. If + /// `self` doesn't have a body or reading fails returns `None`. Note that + /// `self`'s `body` is consumed after a call to this method. + /// + /// # Example + /// + /// ```rust + /// use std::io::Cursor; + /// use rocket::Response; + /// + /// let mut response = Response::new(); + /// assert!(response.body().is_none()); + /// + /// response.set_sized_body(Cursor::new("hi!")); + /// assert_eq!(response.body_bytes(), Some(vec![0x68, 0x69, 0x21])); + /// assert!(response.body().is_none()); + /// ``` + #[inline(always)] + pub fn body_bytes(&mut self) -> Option> { + self.take_body().and_then(Body::into_bytes) } /// Moves the body of `self` out and returns it, if there is one, leaving no diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index 9de347a8..f2e823c0 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -21,19 +21,19 @@ use outcome::Outcome; use error::{Error, LaunchError, LaunchErrorKind}; use fairing::{Fairing, Fairings}; -use http::{Method, Status, Header, Session}; +use http::{Method, Status, Header}; use http::hyper::{self, header}; use http::uri::URI; /// The main `Rocket` type: used to mount routes and catchers and launch the /// application. pub struct Rocket { - config: Config, + pub(crate) config: Config, router: Router, default_catchers: HashMap, catchers: HashMap, - state: Container, - fairings: Fairings + pub(crate) state: Container, + fairings: Fairings, } #[doc(hidden)] @@ -50,11 +50,12 @@ impl hyper::Handler for Rocket { let (h_addr, h_method, h_headers, h_uri, _, h_body) = hyp_req.deconstruct(); // Convert the Hyper request into a Rocket request. - let mut req = match Request::from_hyp(h_method, h_headers, h_uri, h_addr) { + let req_res = Request::from_hyp(self, h_method, h_headers, h_uri, h_addr); + let mut req = match req_res { Ok(req) => req, Err(e) => { error!("Bad incoming request: {}", e); - let dummy = Request::new(Method::Get, URI::new("")); + let dummy = Request::new(self, Method::Get, URI::new("")); let r = self.handle_error(Status::InternalServerError, &dummy); return self.issue_response(r, res); } @@ -209,16 +210,12 @@ impl Rocket { } } - // TODO: Explain this `UnsafeCell` business at a macro level. #[inline] pub(crate) fn dispatch<'s, 'r>(&'s self, request: &'r mut Request<'s>, data: Data) -> Response<'r> { info!("{}:", request); - // Inform the request about all of the precomputed state. - request.set_preset(&self.config, &self.state); - // Do a bit of preprocessing before routing; run the attached fairings. self.preprocess_request(request, &data); self.fairings.handle_request(request, &data); @@ -226,16 +223,11 @@ impl Rocket { // Route the request to get a response. let mut response = match self.route(request, data) { Outcome::Success(mut response) => { - // A user's route responded! Set the regular cookies. + // A user's route responded! Set the cookies. for cookie in request.cookies().delta() { response.adjoin_header(cookie); } - // And now the session cookies. - for cookie in request.session().delta() { - response.adjoin_header(Session::header_for(cookie)); - } - response } Outcome::Forward(data) => { @@ -399,19 +391,21 @@ impl Rocket { info_!("port: {}", Paint::white(&config.port)); info_!("log: {}", Paint::white(config.log_level)); info_!("workers: {}", Paint::white(config.workers)); - info_!("secret key: {}", Paint::white(config.secret_key.kind())); + info_!("secret key: {}", Paint::white(&config.secret_key)); info_!("limits: {}", Paint::white(&config.limits)); let tls_configured = config.tls.is_some(); if tls_configured && cfg!(feature = "tls") { info_!("tls: {}", Paint::white("enabled")); + } else if tls_configured { + error_!("tls: {}", Paint::white("disabled")); + error_!("tls is configured, but the tls feature is disabled"); } else { - if tls_configured { - error_!("tls: {}", Paint::white("disabled")); - error_!("tls is configured, but the tls feature is disabled"); - } else { - info_!("tls: {}", Paint::white("disabled")); - } + info_!("tls: {}", Paint::white("disabled")); + } + + if config.secret_key.is_generated() && config.environment.is_prod() { + warn!("environment is 'production', but no `secret_key` is configured"); } for (name, value) in config.extras() { diff --git a/lib/src/router/collider.rs b/lib/src/router/collider.rs index 91325c36..dbde99ae 100644 --- a/lib/src/router/collider.rs +++ b/lib/src/router/collider.rs @@ -131,6 +131,8 @@ mod tests { use std::str::FromStr; use super::Collider; + use rocket::Rocket; + use config::Config; use request::Request; use data::Data; use handler::Outcome; @@ -368,7 +370,8 @@ mod tests { fn req_route_mt_collide(m: Method, mt1: S1, mt2: S2) -> bool where S1: Into>, S2: Into> { - let mut req = Request::new(m, "/"); + let rocket = Rocket::custom(Config::development().unwrap(), true); + let mut req = Request::new(&rocket, m, "/"); if let Some(mt_str) = mt1.into() { if m.supports_payload() { req.replace_header(mt_str.parse::().unwrap()); @@ -425,7 +428,8 @@ mod tests { } fn req_route_path_collide(a: &'static str, b: &'static str) -> bool { - let req = Request::new(Get, a.to_string()); + let rocket = Rocket::custom(Config::development().unwrap(), true); + let req = Request::new(&rocket, Get, a.to_string()); let route = Route::ranked(0, Get, b.to_string(), dummy_handler); route.collides_with(&req) } diff --git a/lib/src/router/mod.rs b/lib/src/router/mod.rs index bab2da2e..5f672da6 100644 --- a/lib/src/router/mod.rs +++ b/lib/src/router/mod.rs @@ -70,6 +70,8 @@ impl Router { mod test { use super::{Router, Route}; + use rocket::Rocket; + use config::Config; use http::Method; use http::Method::*; use http::uri::URI; @@ -159,7 +161,8 @@ mod test { } fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> { - let request = Request::new(method, URI::new(uri)); + let rocket = Rocket::custom(Config::development().unwrap(), true); + let request = Request::new(&rocket, method, URI::new(uri)); let matches = router.route(&request); if matches.len() > 0 { Some(matches[0]) @@ -169,7 +172,8 @@ mod test { } fn matches<'a>(router: &'a Router, method: Method, uri: &str) -> Vec<&'a Route> { - let request = Request::new(method, URI::new(uri)); + let rocket = Rocket::custom(Config::development().unwrap(), true); + let request = Request::new(&rocket, method, URI::new(uri)); router.route(&request) } diff --git a/lib/src/testing.rs b/lib/src/testing.rs deleted file mode 100644 index d551adc2..00000000 --- a/lib/src/testing.rs +++ /dev/null @@ -1,274 +0,0 @@ -//! A tiny module for testing Rocket applications. -//! -//! # Usage -//! -//! The testing methadology is simple: -//! -//! 1. Construct a `Rocket` instance. -//! 2. Construct a request. -//! 3. Dispatch the request using the Rocket instance. -//! 4. Inspect, validate, and verify the response. -//! -//! ## Construct a `Rocket` Instance -//! -//! Constructing a `Rocket` instance for testing is identical to constructing -//! one for launching, except you should not call the `launch` method. That is, -//! use `rocket::ignite`, then mount routes and catchers. That's it! -//! -//! ## Construct a (Mock)Request -//! -//! The [MockRequest](struct.MockRequest.html) object enables the creation of an -//! HTTP request without using any networking. A `MockRequest` object is -//! constructed using the builder pattern. For example, the following code -//! builds a request for submitting a login form with three fields: -//! -//! ```rust -//! use rocket::http::Method::*; -//! use rocket::http::ContentType; -//! use rocket::testing::MockRequest; -//! -//! let (username, password, age) = ("user", "password", 32); -//! MockRequest::new(Post, "/login") -//! .header(ContentType::Form) -//! .body(&format!("username={}&password={}&age={}", username, password, age)); -//! ``` -//! -//! ## Dispatch and Validate -//! -//! Finally, requests can be dispatched using the -//! [dispatch_with](struct.MockRequest.html#method.dispatch_with) method on the -//! contructed `MockRequest` instance. The method returns the body of the -//! response. At present, the API does not allow for headers in the response to -//! be examined. -//! -//! # Example -//! -//! The following is an example of a complete application with testing. -//! -//! ```rust -//! #![feature(plugin)] -//! #![plugin(rocket_codegen)] -//! -//! extern crate rocket; -//! -//! #[get("/")] -//! fn hello() -> &'static str { -//! "Hello, world!" -//! } -//! -//! # fn main() { } -//! #[cfg(test)] -//! mod test { -//! use super::rocket; -//! use rocket::testing::MockRequest; -//! use rocket::http::Method::*; -//! -//! #[test] -//! fn test_hello_world() { -//! let rocket = rocket::ignite().mount("/", routes![super::hello]); -//! let mut req = MockRequest::new(Get, "/"); -//! let mut response = req.dispatch_with(&rocket); -//! -//! // Check that the body contains the string we expect. -//! assert_eq!(response.body_string(), Some("Hello, world!".into())); -//! } -//! } -//! ``` - -use ::{Rocket, Request, Response, Data}; -use error::LaunchError; -use http::{Method, Status, Header, Cookie}; - -use std::net::SocketAddr; - -/// A type for mocking requests for testing Rocket applications. -pub struct MockRequest<'r> { - prechecked: Option<&'r Rocket>, - request: Request<'r>, - data: Data -} - -impl<'r> MockRequest<'r> { - /// Constructs a new mocked request with the given `method` and `uri`. - #[inline] - pub fn new>(method: Method, uri: S) -> Self { - MockRequest { - prechecked: None, - request: Request::new(method, uri.as_ref().to_string()), - data: Data::local(vec![]) - } - } - - /// Add a header to this request. - /// - /// # Examples - /// - /// Add the Content-Type header: - /// - /// ```rust - /// use rocket::http::Method::*; - /// use rocket::testing::MockRequest; - /// use rocket::http::ContentType; - /// - /// # #[allow(unused_variables)] - /// let req = MockRequest::new(Get, "/").header(ContentType::JSON); - /// ``` - #[inline] - pub fn header>>(mut self, header: H) -> Self { - self.request.add_header(header.into()); - self - } - - /// Adds a header to this request without consuming `self`. - /// - /// # Examples - /// - /// Add the Content-Type header: - /// - /// ```rust - /// use rocket::http::Method::*; - /// use rocket::testing::MockRequest; - /// use rocket::http::ContentType; - /// - /// let mut req = MockRequest::new(Get, "/"); - /// req.add_header(ContentType::JSON); - /// ``` - #[inline] - pub fn add_header>>(&mut self, header: H) { - self.request.add_header(header.into()); - } - - /// Set the remote address of this request. - /// - /// # Examples - /// - /// Set the remote address to "8.8.8.8:80": - /// - /// ```rust - /// use rocket::http::Method::*; - /// use rocket::testing::MockRequest; - /// - /// let address = "8.8.8.8:80".parse().unwrap(); - /// # #[allow(unused_variables)] - /// let req = MockRequest::new(Get, "/").remote(address); - /// ``` - #[inline] - pub fn remote(mut self, address: SocketAddr) -> Self { - self.request.set_remote(address); - self - } - - /// Add a cookie to this request. - /// - /// # Examples - /// - /// Add `user_id` cookie: - /// - /// ```rust - /// use rocket::http::Method::*; - /// use rocket::testing::MockRequest; - /// use rocket::http::Cookie; - /// - /// # #[allow(unused_variables)] - /// let req = MockRequest::new(Get, "/") - /// .cookie(Cookie::new("username", "sb")) - /// .cookie(Cookie::new("user_id", format!("{}", 12))); - /// ``` - #[inline] - pub fn cookie(self, cookie: Cookie<'static>) -> Self { - self.request.cookies().add(cookie); - self - } - - /// Set the body (data) of the request. - /// - /// # Examples - /// - /// Set the body to be a JSON structure; also sets the Content-Type. - /// - /// ```rust - /// use rocket::http::Method::*; - /// use rocket::testing::MockRequest; - /// use rocket::http::ContentType; - /// - /// # #[allow(unused_variables)] - /// let req = MockRequest::new(Post, "/") - /// .header(ContentType::JSON) - /// .body(r#"{ "key": "value", "array": [1, 2, 3], }"#); - /// ``` - #[inline] - pub fn body>(mut self, body: S) -> Self { - self.data = Data::local(body.as_ref().into()); - self - } - - /// Returns `Some` if there an error checking `rocket`. Returns `None` if - /// there's no error and dispatching can continue. - fn precheck(&mut self, rocket: &'r Rocket) -> Option { - // Check if we've already prechecked some `Rocket` instance. - if let Some(r) = self.prechecked { - // Check if the one we've prechecked is indeed `rocket`. This does a - // straight pointer comparison. If they're the same, then we know - // that the instance must not have changed since we kept an - // immutable borrow to it from the precheck. - if (r as *const Rocket) == (rocket as *const Rocket) { - return None - } - } - - if let Some(err) = rocket.prelaunch_check() { - return Some(err); - } - - self.prechecked = Some(rocket); - None - } - - /// Dispatch this request using a given instance of Rocket. - /// - /// It is possible that the supplied `rocket` instance contains malformed - /// input such as colliding or invalid routes or failed fairings. When this - /// is the case, the returned `Response` will contain a status of - /// `InternalServerError`, and the body will contain the error that - /// occurred. In all other cases, the returned `Response` will be that of - /// the application. - /// - /// # Examples - /// - /// Dispatch to a Rocket instance with the `"Hello, world!"` example - /// mounted: - /// - /// ```rust - /// # #![feature(plugin)] - /// # #![plugin(rocket_codegen)] - /// # extern crate rocket; - /// # - /// #[get("/")] - /// fn hello() -> &'static str { - /// "Hello, world!" - /// } - /// - /// use rocket::testing::MockRequest; - /// use rocket::http::Method::*; - /// - /// # fn main() { - /// let rocket = rocket::ignite().mount("/", routes![hello]); - /// let mut req = MockRequest::new(Get, "/"); - /// let mut response = req.dispatch_with(&rocket); - /// - /// assert_eq!(response.body_string(), Some("Hello, world!".into())); - /// # } - /// ``` - #[inline] - pub fn dispatch_with<'s>(&'s mut self, rocket: &'r Rocket) -> Response<'s> { - if let Some(err) = self.precheck(rocket) { - return Response::build() - .status(Status::InternalServerError) - .sized_body(::std::io::Cursor::new(err.to_string())) - .finalize() - } - - let data = ::std::mem::replace(&mut self.data, Data::local(vec![])); - rocket.dispatch(&mut self.request, data) - } -} diff --git a/lib/tests/form_method-issue-45.rs b/lib/tests/form_method-issue-45.rs index a271842a..4cc0dad4 100644 --- a/lib/tests/form_method-issue-45.rs +++ b/lib/tests/form_method-issue-45.rs @@ -18,31 +18,28 @@ fn bug(form_data: Form) -> &'static str { mod tests { use super::*; - use rocket::testing::MockRequest; - use rocket::http::Method::*; + use rocket::local::Client; use rocket::http::{Status, ContentType}; #[test] fn method_eval() { - let rocket = rocket::ignite().mount("/", routes![bug]); - - let mut req = MockRequest::new(Post, "/") + let client = Client::new(rocket::ignite().mount("/", routes![bug])).unwrap(); + let mut response = client.post("/") .header(ContentType::Form) - .body("_method=patch&form_data=Form+data"); + .body("_method=patch&form_data=Form+data") + .dispatch(); - let mut response = req.dispatch_with(&rocket); assert_eq!(response.body_string(), Some("OK".into())); } #[test] fn get_passes_through() { - let rocket = rocket::ignite().mount("/", routes![bug]); - - let mut req = MockRequest::new(Get, "/") + let client = Client::new(rocket::ignite().mount("/", routes![bug])).unwrap(); + let response = client.get("/") .header(ContentType::Form) - .body("_method=patch&form_data=Form+data"); + .body("_method=patch&form_data=Form+data") + .dispatch(); - let response = req.dispatch_with(&rocket); assert_eq!(response.status(), Status::NotFound); } } diff --git a/lib/tests/form_value_decoding-issue-82.rs b/lib/tests/form_value_decoding-issue-82.rs index 904d4cca..5ad1c849 100644 --- a/lib/tests/form_value_decoding-issue-82.rs +++ b/lib/tests/form_value_decoding-issue-82.rs @@ -17,18 +17,17 @@ fn bug(form_data: Form) -> String { mod tests { use super::*; - use rocket::testing::MockRequest; - use rocket::http::Method::*; + use rocket::local::Client; use rocket::http::ContentType; use rocket::http::Status; fn check_decoding(raw: &str, decoded: &str) { - let rocket = rocket::ignite().mount("/", routes![bug]); - let mut req = MockRequest::new(Post, "/") + let client = Client::new(rocket::ignite().mount("/", routes![bug])).unwrap(); + let mut response = client.post("/") .header(ContentType::Form) - .body(format!("form_data={}", raw)); + .body(format!("form_data={}", raw)) + .dispatch(); - let mut response = req.dispatch_with(&rocket); assert_eq!(response.status(), Status::Ok); assert_eq!(Some(decoded.to_string()), response.body_string()); } diff --git a/lib/tests/head_handling.rs b/lib/tests/head_handling.rs index c4cc52aa..b97ebb90 100644 --- a/lib/tests/head_handling.rs +++ b/lib/tests/head_handling.rs @@ -24,8 +24,7 @@ mod tests { use super::*; use rocket::Route; - use rocket::testing::MockRequest; - use rocket::http::Method::*; + use rocket::local::Client; use rocket::http::{Status, ContentType}; use rocket::response::Body; @@ -35,12 +34,10 @@ mod tests { #[test] fn auto_head() { - let rocket = rocket::ignite().mount("/", routes()); - - let mut req = MockRequest::new(Head, "/"); - let mut response = req.dispatch_with(&rocket); - + let client = Client::new(rocket::ignite().mount("/", routes())).unwrap(); + let mut response = client.head("/").dispatch(); assert_eq!(response.status(), Status::Ok); + if let Some(body) = response.body() { match body { Body::Sized(_, n) => assert_eq!(n, "Hello, world!".len() as u64), @@ -49,27 +46,23 @@ mod tests { assert_eq!(body.into_string(), Some("".to_string())); } else { - panic!("Expected an empty body!") + panic!("Expected a non-empty body!") } - let content_type: Vec<_> = response.headers().get("Content-Type").collect(); assert_eq!(content_type, vec![ContentType::Plain.to_string()]); - let mut req = MockRequest::new(Head, "/empty"); - let response = req.dispatch_with(&rocket); + let response = client.head("empty").dispatch(); assert_eq!(response.status(), Status::NoContent); } #[test] fn user_head() { - let rocket = rocket::ignite().mount("/", routes()); - let mut req = MockRequest::new(Head, "/other"); - let response = req.dispatch_with(&rocket); - - assert_eq!(response.status(), Status::Ok); + let client = Client::new(rocket::ignite().mount("/", routes())).unwrap(); + let response = client.head("/other").dispatch(); let content_type: Vec<_> = response.headers().get("Content-Type").collect(); + assert_eq!(response.status(), Status::Ok); assert_eq!(content_type, vec![ContentType::JSON.to_string()]); } } diff --git a/lib/tests/limits.rs b/lib/tests/limits.rs index 9f6bd6d3..1aa97f10 100644 --- a/lib/tests/limits.rs +++ b/lib/tests/limits.rs @@ -18,13 +18,12 @@ fn index(form: Form) -> String { mod limits_tests { use rocket; use rocket::config::{Environment, Config, Limits}; - use rocket::testing::MockRequest; - use rocket::http::Method::*; + use rocket::local::Client; use rocket::http::{Status, ContentType}; fn rocket_with_forms_limit(limit: u64) -> rocket::Rocket { let config = Config::build(Environment::Development) - .limits(Limits::default().add("forms", limit)) + .limits(Limits::default().limit("forms", limit)) .unwrap(); rocket::custom(config, true).mount("/", routes![super::index]) @@ -32,45 +31,45 @@ mod limits_tests { #[test] fn large_enough() { - let rocket = rocket_with_forms_limit(128); - let mut req = MockRequest::new(Post, "/") + let client = Client::new(rocket_with_forms_limit(128)).unwrap(); + let mut response = client.post("/") .body("value=Hello+world") - .header(ContentType::Form); + .header(ContentType::Form) + .dispatch(); - let mut response = req.dispatch_with(&rocket); assert_eq!(response.body_string(), Some("Hello world".into())); } #[test] fn just_large_enough() { - let rocket = rocket_with_forms_limit(17); - let mut req = MockRequest::new(Post, "/") + let client = Client::new(rocket_with_forms_limit(17)).unwrap(); + let mut response = client.post("/") .body("value=Hello+world") - .header(ContentType::Form); + .header(ContentType::Form) + .dispatch(); - let mut response = req.dispatch_with(&rocket); assert_eq!(response.body_string(), Some("Hello world".into())); } #[test] fn much_too_small() { - let rocket = rocket_with_forms_limit(4); - let mut req = MockRequest::new(Post, "/") + let client = Client::new(rocket_with_forms_limit(4)).unwrap(); + let response = client.post("/") .body("value=Hello+world") - .header(ContentType::Form); + .header(ContentType::Form) + .dispatch(); - let response = req.dispatch_with(&rocket); assert_eq!(response.status(), Status::BadRequest); } #[test] fn contracted() { - let rocket = rocket_with_forms_limit(10); - let mut req = MockRequest::new(Post, "/") + let client = Client::new(rocket_with_forms_limit(10)).unwrap(); + let mut response = client.post("/") .body("value=Hello+world") - .header(ContentType::Form); + .header(ContentType::Form) + .dispatch(); - let mut response = req.dispatch_with(&rocket); assert_eq!(response.body_string(), Some("Hell".into())); } } diff --git a/lib/tests/precise-content-type-matching.rs b/lib/tests/precise-content-type-matching.rs index 18e4538c..93e67e65 100644 --- a/lib/tests/precise-content-type-matching.rs +++ b/lib/tests/precise-content-type-matching.rs @@ -27,8 +27,7 @@ mod tests { use super::*; use rocket::Rocket; - use rocket::testing::MockRequest; - use rocket::http::Method::*; + use rocket::local::Client; use rocket::http::{Status, ContentType}; fn rocket() -> Rocket { @@ -39,14 +38,14 @@ mod tests { macro_rules! check_dispatch { ($mount:expr, $ct:expr, $body:expr) => ( - let rocket = rocket(); - let mut req = MockRequest::new(Post, $mount); + let client = Client::new(rocket()).unwrap(); + let mut req = client.post($mount); let ct: Option = $ct; if let Some(ct) = ct { req.add_header(ct); } - let mut response = req.dispatch_with(&rocket); + let mut response = req.dispatch(); let body_str = response.body_string(); let body: Option<&'static str> = $body; match body { diff --git a/lib/tests/query-and-non-query-dont-collide.rs b/lib/tests/query-and-non-query-dont-collide.rs index 3e1c14a2..18ad64a8 100644 --- a/lib/tests/query-and-non-query-dont-collide.rs +++ b/lib/tests/query-and-non-query-dont-collide.rs @@ -20,27 +20,24 @@ fn second() -> &'static str { mod tests { use super::*; - use rocket::Rocket; - use rocket::testing::MockRequest; - use rocket::http::Method::*; + use rocket::local::Client; - fn assert_no_collision(rocket: &Rocket) { - let mut req = MockRequest::new(Get, "/?field=query"); - let mut response = req.dispatch_with(&rocket); + fn assert_no_collision(rocket: Rocket) { + let client = Client::new(rocket).unwrap(); + let mut response = client.get("/?field=query").dispatch(); assert_eq!(response.body_string(), Some("query".into())); - let mut req = MockRequest::new(Get, "/"); - let mut response = req.dispatch_with(&rocket); + let mut response = client.get("/").dispatch(); assert_eq!(response.body_string(), Some("no query".into())); } #[test] fn check_query_collisions() { let rocket = rocket::ignite().mount("/", routes![first, second]); - assert_no_collision(&rocket); + assert_no_collision(rocket); let rocket = rocket::ignite().mount("/", routes![second, first]); - assert_no_collision(&rocket); + assert_no_collision(rocket); } } diff --git a/lib/tests/redirect_from_catcher-issue-113.rs b/lib/tests/redirect_from_catcher-issue-113.rs index 03dc5d81..a497e51c 100644 --- a/lib/tests/redirect_from_catcher-issue-113.rs +++ b/lib/tests/redirect_from_catcher-issue-113.rs @@ -12,15 +12,13 @@ fn not_found() -> Redirect { mod tests { use super::*; - use rocket::testing::MockRequest; - use rocket::http::Method::*; + use rocket::local::Client; use rocket::http::Status; #[test] fn error_catcher_redirect() { - let rocket = rocket::ignite().catch(errors![not_found]); - let mut req = MockRequest::new(Get, "/unknown"); - let response = req.dispatch_with(&rocket); + let client = Client::new(rocket::ignite().catch(errors![not_found])).unwrap(); + let response = client.get("/unknown").dispatch(); println!("Response:\n{:?}", response); let location: Vec<_> = response.headers().get("location").collect(); diff --git a/lib/tests/remote-rewrite.rs b/lib/tests/remote-rewrite.rs index c7befbc5..2af7ef61 100644 --- a/lib/tests/remote-rewrite.rs +++ b/lib/tests/remote-rewrite.rs @@ -12,8 +12,7 @@ fn get_ip(remote: SocketAddr) -> String { mod remote_rewrite_tests { use super::*; - use rocket::testing::MockRequest; - use rocket::http::Method::*; + use rocket::local::Client; use rocket::http::{Header, Status}; use std::net::SocketAddr; @@ -21,21 +20,19 @@ mod remote_rewrite_tests { const KNOWN_IP: &'static str = "127.0.0.1:8000"; fn check_ip(header: Option>, ip: Option) { - let address: SocketAddr = KNOWN_IP.parse().unwrap(); - let port = address.port(); + let addr: SocketAddr = KNOWN_IP.parse().unwrap(); - let rocket = rocket::ignite().mount("/", routes![get_ip]); - let mut req = MockRequest::new(Get, "/").remote(address); - if let Some(header) = header { - req.add_header(header); - } + let c = Client::new(rocket::ignite().mount("/", routes![get_ip])).unwrap(); + let mut response = match header { + Some(header) => c.get("/").header(header).remote(addr).dispatch(), + None => c.get("/").remote(addr).dispatch() + }; - let mut response = req.dispatch_with(&rocket); assert_eq!(response.status(), Status::Ok); - let body_str = response.body_string(); + let body = response.body_string(); match ip { - Some(ip) => assert_eq!(body_str, Some(format!("{}:{}", ip, port))), - None => assert_eq!(body_str, Some(KNOWN_IP.into())) + Some(ip) => assert_eq!(body, Some(format!("{}:{}", ip, addr.port()))), + None => assert_eq!(body, Some(KNOWN_IP.into())) } } diff --git a/lib/tests/route_guard.rs b/lib/tests/route_guard.rs index 54d4cdb0..ee6204c3 100644 --- a/lib/tests/route_guard.rs +++ b/lib/tests/route_guard.rs @@ -13,14 +13,10 @@ fn files(route: &Route, path: PathBuf) -> String { mod route_guard_tests { use super::*; + use rocket::local::Client; - use rocket::Rocket; - use rocket::testing::MockRequest; - use rocket::http::Method::*; - - fn assert_path(rocket: &Rocket, path: &str) { - let mut req = MockRequest::new(Get, path); - let mut res = req.dispatch_with(&rocket); + fn assert_path(client: &Client, path: &str) { + let mut res = client.get(path).dispatch(); assert_eq!(res.body_string(), Some(path.into())); } @@ -30,9 +26,10 @@ mod route_guard_tests { .mount("/first", routes![files]) .mount("/second", routes![files]); - assert_path(&rocket, "/first/some/path"); - assert_path(&rocket, "/second/some/path"); - assert_path(&rocket, "/first/second/b/c"); - assert_path(&rocket, "/second/a/b/c"); + let client = Client::new(rocket).unwrap(); + assert_path(&client, "/first/some/path"); + assert_path(&client, "/second/some/path"); + assert_path(&client, "/first/second/b/c"); + assert_path(&client, "/second/a/b/c"); } } diff --git a/lib/tests/segments-issues-41-86.rs b/lib/tests/segments-issues-41-86.rs index 34d55ad4..804a3ec4 100644 --- a/lib/tests/segments-issues-41-86.rs +++ b/lib/tests/segments-issues-41-86.rs @@ -32,14 +32,14 @@ fn dual(user: String, path: Segments) -> String { mod tests { use super::*; - use rocket::testing::MockRequest; - use rocket::http::Method::*; + use rocket::local::Client; #[test] fn segments_works() { let rocket = rocket::ignite() .mount("/", routes![test, two, one_two, none, dual]) .mount("/point", routes![test, two, one_two, dual]); + let client = Client::new(rocket).unwrap(); // We construct a path that matches each of the routes above. We ensure the // prefix is stripped, confirming that dynamic segments are working. @@ -48,9 +48,7 @@ mod tests { "/static", "/point/static"] { let path = "this/is/the/path/we/want"; - let mut req = MockRequest::new(Get, format!("{}/{}", prefix, path)); - - let mut response = req.dispatch_with(&rocket); + let mut response = client.get(format!("{}/{}", prefix, path)).dispatch(); assert_eq!(response.body_string(), Some(path.into())); } }