Test 'secret_key' validation, now on pre-launch.

Prior to this commit, it was not possible to test Rocket crates in
production mode without setting a global secret key or bypassing secret
key checking - the testing script did the latter. The consequence is
that it became impossible to test secret key related failures because
the tests passed regardless.

This commit undoes this. As a consequence, all tests are now aware of
the difference between debug and release configurations, the latter of
which validates 'secret_key' by default. New 'Client::debug()' and
'Client::debug_with()' simplify creating an instance of 'Client' with
configuration in debug mode to avoid undesired test failures.

The summary of changes in this commit are:

  * Config 'secret_key' success and failure are now tested.
  * 'secret_key' validation was moved to pre-launch from 'Config:from()'.
  * 'Config::from()' only extracts the config.
  * Added 'Config::try_from()' for non-panicking extraction.
  * 'Config' now knows the profile it was extracted from.
  * The 'Config' provider sets a profile of 'Config.profile'.
  * 'Rocket', 'Client', 'Fairings', implement 'Debug'.
  * 'fairing::Info' implements 'Copy', 'Clone'.
  * 'Fairings' keeps track of, logs attach fairings.
  * 'Rocket::reconfigure()' was added to allow modifying a config.

Internally, the testing script was refactored to properly test the
codebase with the new changes. In particular, it no longer sets a rustc
'cfg' to avoid secret-key checking.

Resolves #1543.
Fixes #1564.
This commit is contained in:
Sergio Benitez 2021-03-09 00:07:43 -08:00
parent bbbe362740
commit 4e06ee64aa
72 changed files with 594 additions and 363 deletions

View File

@ -66,7 +66,7 @@ mod compress_responder_tests {
#[test] #[test]
fn test_prioritizes_brotli() { fn test_prioritizes_brotli() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/") .get("/")
.header(Header::new("Accept-Encoding", "deflate, gzip, br")) .header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -90,7 +90,7 @@ mod compress_responder_tests {
#[test] #[test]
fn test_br_font() { fn test_br_font() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/font") .get("/font")
.header(Header::new("Accept-Encoding", "deflate, gzip, br")) .header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -114,7 +114,7 @@ mod compress_responder_tests {
#[test] #[test]
fn test_fallback_gzip() { fn test_fallback_gzip() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/") .get("/")
.header(Header::new("Accept-Encoding", "deflate, gzip")) .header(Header::new("Accept-Encoding", "deflate, gzip"))
@ -137,7 +137,7 @@ mod compress_responder_tests {
#[test] #[test]
fn test_does_not_recompress() { fn test_does_not_recompress() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/already_encoded") .get("/already_encoded")
.header(Header::new("Accept-Encoding", "deflate, gzip, br")) .header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -160,7 +160,7 @@ mod compress_responder_tests {
#[test] #[test]
fn test_does_not_compress_explicit_identity() { fn test_does_not_compress_explicit_identity() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/identity") .get("/identity")
.header(Header::new("Accept-Encoding", "deflate, gzip, br")) .header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -178,7 +178,7 @@ mod compress_responder_tests {
#[test] #[test]
fn test_ignore_exceptions() { fn test_ignore_exceptions() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/image") .get("/image")
.header(Header::new("Accept-Encoding", "deflate, gzip, br")) .header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -202,7 +202,7 @@ mod compress_responder_tests {
#[test] #[test]
fn test_ignores_unimplemented_encodings() { fn test_ignores_unimplemented_encodings() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/") .get("/")
.header(Header::new("Accept-Encoding", "deflate")) .header(Header::new("Accept-Encoding", "deflate"))
@ -220,7 +220,7 @@ mod compress_responder_tests {
#[test] #[test]
fn test_respects_identity_only() { fn test_respects_identity_only() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/") .get("/")
.header(Header::new("Accept-Encoding", "identity")) .header(Header::new("Accept-Encoding", "identity"))

View File

@ -85,7 +85,7 @@ mod compression_fairing_tests {
#[test] #[test]
fn test_prioritizes_brotli() { fn test_prioritizes_brotli() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/") .get("/")
.header(Header::new("Accept-Encoding", "deflate, gzip, br")) .header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -109,7 +109,7 @@ mod compression_fairing_tests {
#[test] #[test]
fn test_br_font() { fn test_br_font() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/font") .get("/font")
.header(Header::new("Accept-Encoding", "deflate, gzip, br")) .header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -133,7 +133,7 @@ mod compression_fairing_tests {
#[test] #[test]
fn test_fallback_gzip() { fn test_fallback_gzip() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/") .get("/")
.header(Header::new("Accept-Encoding", "deflate, gzip")) .header(Header::new("Accept-Encoding", "deflate, gzip"))
@ -156,7 +156,7 @@ mod compression_fairing_tests {
#[test] #[test]
fn test_does_not_recompress() { fn test_does_not_recompress() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/already_encoded") .get("/already_encoded")
.header(Header::new("Accept-Encoding", "deflate, gzip, br")) .header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -179,7 +179,7 @@ mod compression_fairing_tests {
#[test] #[test]
fn test_does_not_compress_explicit_identity() { fn test_does_not_compress_explicit_identity() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/identity") .get("/identity")
.header(Header::new("Accept-Encoding", "deflate, gzip, br")) .header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -197,7 +197,7 @@ mod compression_fairing_tests {
#[test] #[test]
fn test_does_not_compress_image() { fn test_does_not_compress_image() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/image") .get("/image")
.header(Header::new("Accept-Encoding", "deflate, gzip, br")) .header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -215,7 +215,7 @@ mod compression_fairing_tests {
#[test] #[test]
fn test_ignores_unimplemented_encodings() { fn test_ignores_unimplemented_encodings() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/") .get("/")
.header(Header::new("Accept-Encoding", "deflate")) .header(Header::new("Accept-Encoding", "deflate"))
@ -233,7 +233,7 @@ mod compression_fairing_tests {
#[test] #[test]
fn test_respects_identity_only() { fn test_respects_identity_only() {
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/") .get("/")
.header(Header::new("Accept-Encoding", "identity")) .header(Header::new("Accept-Encoding", "identity"))
@ -251,7 +251,7 @@ mod compression_fairing_tests {
#[test] #[test]
fn test_does_not_compress_custom_exception() { fn test_does_not_compress_custom_exception() {
let client = Client::tracked(rocket_tar_exception()).expect("valid rocket instance"); let client = Client::debug(rocket_tar_exception()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/tar") .get("/tar")
.header(Header::new("Accept-Encoding", "deflate, gzip, br")) .header(Header::new("Accept-Encoding", "deflate, gzip, br"))
@ -269,7 +269,7 @@ mod compression_fairing_tests {
#[test] #[test]
fn test_compress_custom_removed_exception() { fn test_compress_custom_removed_exception() {
let client = Client::tracked(rocket_tar_exception()).expect("valid rocket instance"); let client = Client::debug(rocket_tar_exception()).expect("valid rocket instance");
let mut response = client let mut response = client
.get("/image") .get("/image")
.header(Header::new("Accept-Encoding", "deflate, gzip, br")) .header(Header::new("Accept-Encoding", "deflate, gzip, br"))

View File

@ -32,7 +32,7 @@ mod helmet_tests {
macro_rules! dispatch { macro_rules! dispatch {
($helmet:expr, $closure:expr) => {{ ($helmet:expr, $closure:expr) => {{
let rocket = rocket::ignite().mount("/", routes![hello]).attach($helmet); let rocket = rocket::ignite().mount("/", routes![hello]).attach($helmet);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.get("/").dispatch(); let response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
$closure(response) $closure(response)

View File

@ -69,7 +69,7 @@ mod static_tests {
#[test] #[test]
fn test_static_no_index() { fn test_static_no_index() {
let client = Client::tracked(rocket()).expect("valid rocket"); let client = Client::debug(rocket()).expect("valid rocket");
assert_all(&client, "no_index", REGULAR_FILES, true); assert_all(&client, "no_index", REGULAR_FILES, true);
assert_all(&client, "no_index", HIDDEN_FILES, false); assert_all(&client, "no_index", HIDDEN_FILES, false);
assert_all(&client, "no_index", INDEXED_DIRECTORIES, false); assert_all(&client, "no_index", INDEXED_DIRECTORIES, false);
@ -77,7 +77,7 @@ mod static_tests {
#[test] #[test]
fn test_static_hidden() { fn test_static_hidden() {
let client = Client::tracked(rocket()).expect("valid rocket"); let client = Client::debug(rocket()).expect("valid rocket");
assert_all(&client, "dots", REGULAR_FILES, true); assert_all(&client, "dots", REGULAR_FILES, true);
assert_all(&client, "dots", HIDDEN_FILES, true); assert_all(&client, "dots", HIDDEN_FILES, true);
assert_all(&client, "dots", INDEXED_DIRECTORIES, false); assert_all(&client, "dots", INDEXED_DIRECTORIES, false);
@ -85,7 +85,7 @@ mod static_tests {
#[test] #[test]
fn test_static_index() { fn test_static_index() {
let client = Client::tracked(rocket()).expect("valid rocket"); let client = Client::debug(rocket()).expect("valid rocket");
assert_all(&client, "index", REGULAR_FILES, true); assert_all(&client, "index", REGULAR_FILES, true);
assert_all(&client, "index", HIDDEN_FILES, false); assert_all(&client, "index", HIDDEN_FILES, false);
assert_all(&client, "index", INDEXED_DIRECTORIES, true); assert_all(&client, "index", INDEXED_DIRECTORIES, true);
@ -97,7 +97,7 @@ mod static_tests {
#[test] #[test]
fn test_static_all() { fn test_static_all() {
let client = Client::tracked(rocket()).expect("valid rocket"); let client = Client::debug(rocket()).expect("valid rocket");
assert_all(&client, "both", REGULAR_FILES, true); assert_all(&client, "both", REGULAR_FILES, true);
assert_all(&client, "both", HIDDEN_FILES, true); assert_all(&client, "both", HIDDEN_FILES, true);
assert_all(&client, "both", INDEXED_DIRECTORIES, true); assert_all(&client, "both", INDEXED_DIRECTORIES, true);
@ -128,7 +128,7 @@ mod static_tests {
fn catch_two(a: &str, b: &str) -> String { format!("{}/{}", a, b) } fn catch_two(a: &str, b: &str) -> String { format!("{}/{}", a, b) }
let rocket = rocket().mount("/default", routes![catch_one, catch_two]); let rocket = rocket().mount("/default", routes![catch_one, catch_two]);
let client = Client::tracked(rocket).expect("valid rocket"); let client = Client::debug(rocket).expect("valid rocket");
let response = client.get("/default/ireallydontexist").dispatch(); let response = client.get("/default/ireallydontexist").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
@ -145,7 +145,7 @@ mod static_tests {
#[test] #[test]
fn test_redirection() { fn test_redirection() {
let client = Client::tracked(rocket()).expect("valid rocket"); let client = Client::debug(rocket()).expect("valid rocket");
// Redirection only happens if enabled, and doesn't affect index behaviour. // Redirection only happens if enabled, and doesn't affect index behaviour.
let response = client.get("/no_index/inner").dispatch(); let response = client.get("/no_index/inner").dispatch();

View File

@ -40,12 +40,10 @@ mod templates_tests {
Err("error reloading templates!".into()) Err("error reloading templates!".into())
})); }));
match Client::untracked(rocket) { let error = Client::debug(rocket).expect_err("client failure");
Err(e) => match e.kind() { match error.kind() {
FailedFairings(failures) => assert_eq!(failures[0], "Templates"), FailedFairings(failures) => assert_eq!(failures[0].name, "Templates"),
_ => panic!("Wrong kind of launch error"), _ => panic!("Wrong kind of launch error"),
}
_ => panic!("Wrong kind of error"),
} }
} }
@ -79,7 +77,7 @@ mod templates_tests {
#[test] #[test]
fn test_template_metadata_with_tera() { fn test_template_metadata_with_tera() {
let client = Client::tracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let response = client.get("/tera/txt_test").dispatch(); let response = client.get("/tera/txt_test").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
@ -119,7 +117,7 @@ mod templates_tests {
#[test] #[test]
fn test_template_metadata_with_handlebars() { fn test_template_metadata_with_handlebars() {
let client = Client::tracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let response = client.get("/hbs/test").dispatch(); let response = client.get("/hbs/test").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
@ -156,7 +154,7 @@ mod templates_tests {
write_file(&reload_path, INITIAL_TEXT); write_file(&reload_path, INITIAL_TEXT);
// set up the client. if we can't reload templates, then just quit // set up the client. if we can't reload templates, then just quit
let client = Client::tracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let res = client.get("/is_reloading").dispatch(); let res = client.get("/is_reloading").dispatch();
if res.status() != Status::Ok { if res.status() != Status::Ok {
return; return;

View File

@ -34,7 +34,7 @@ foo!("/hello/<name>", name);
#[test] #[test]
fn test_reexpansion() { fn test_reexpansion() {
let rocket = rocket::ignite().mount("/", routes![easy, hard, hi]); let rocket = rocket::ignite().mount("/", routes![easy, hard, hi]);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.get("/easy/327").dispatch(); let response = client.get("/easy/327").dispatch();
assert_eq!(response.into_string().unwrap(), "easy id: 327"); assert_eq!(response.into_string().unwrap(), "easy id: 327");
@ -60,7 +60,7 @@ index!(i32);
#[test] #[test]
fn test_index() { fn test_index() {
let rocket = rocket::ignite().mount("/", routes![index]).manage(100i32); let rocket = rocket::ignite().mount("/", routes![index]).manage(100i32);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.get("/").dispatch(); let response = client.get("/").dispatch();
assert_eq!(response.into_string().unwrap(), "Thing: 100"); assert_eq!(response.into_string().unwrap(), "Thing: 100");

View File

@ -587,7 +587,7 @@ fn test_nested_multi() {
// Ok("hi") // Ok("hi")
// } // }
// //
// let client = Client::untracked(crate::ignite().mount("/", routes![form])).unwrap(); // let client = Client::debug_with(routes![form]).unwrap();
// let ct = "multipart/form-data; boundary=X-BOUNDARY" // let ct = "multipart/form-data; boundary=X-BOUNDARY"
// .parse::<ContentType>() // .parse::<ContentType>()
// .unwrap(); // .unwrap();

View File

@ -21,7 +21,7 @@ pub enum Foo<'r> {
#[rocket::async_test] #[rocket::async_test]
async fn responder_foo() { async fn responder_foo() {
let client = Client::tracked(rocket::ignite()).await.expect("valid rocket"); let client = Client::debug_with(vec![]).await.expect("valid rocket");
let local_req = client.get("/"); let local_req = client.get("/");
let req = local_req.inner(); let req = local_req.inner();
@ -70,7 +70,7 @@ pub struct Bar<'r> {
#[rocket::async_test] #[rocket::async_test]
async fn responder_bar() { async fn responder_bar() {
let client = Client::tracked(rocket::ignite()).await.expect("valid rocket"); let client = Client::debug_with(vec![]).await.expect("valid rocket");
let local_req = client.get("/"); let local_req = client.get("/");
let req = local_req.inner(); let req = local_req.inner();
@ -95,7 +95,7 @@ pub struct Baz {
#[rocket::async_test] #[rocket::async_test]
async fn responder_baz() { async fn responder_baz() {
let client = Client::tracked(rocket::ignite()).await.expect("valid rocket"); let client = Client::debug_with(vec![]).await.expect("valid rocket");
let local_req = client.get("/"); let local_req = client.get("/");
let req = local_req.inner(); let req = local_req.inner();

View File

@ -1,4 +1,4 @@
#[macro_use]extern crate rocket; #[macro_use] extern crate rocket;
use rocket::{Request, Data}; use rocket::{Request, Data};
use rocket::local::blocking::Client; use rocket::local::blocking::Client;
@ -33,7 +33,7 @@ fn simple<'r>(simple: Simple<'r>) -> &'r str { simple.0 }
#[test] #[test]
fn test_data() { fn test_data() {
let rocket = rocket::ignite().mount("/", routes![form, simple]); let rocket = rocket::ignite().mount("/", routes![form, simple]);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.post("/f") let response = client.post("/f")
.header(ContentType::Form) .header(ContentType::Form)

View File

@ -37,7 +37,7 @@ fn test_formats() {
.mount("/", routes![json, xml, json_long, msgpack_long, msgpack, .mount("/", routes![json, xml, json_long, msgpack_long, msgpack,
plain, binary, other]); plain, binary, other]);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.post("/").header(ContentType::JSON).dispatch(); let response = client.post("/").header(ContentType::JSON).dispatch();
assert_eq!(response.into_string().unwrap(), "json"); assert_eq!(response.into_string().unwrap(), "json");
@ -87,7 +87,7 @@ fn test_custom_formats() {
let rocket = rocket::ignite() let rocket = rocket::ignite()
.mount("/", routes![get_foo, post_foo, get_bar_baz, put_bar_baz]); .mount("/", routes![get_foo, post_foo, get_bar_baz, put_bar_baz]);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let foo_a = Accept::new(&[MediaType::new("application", "foo").into()]); let foo_a = Accept::new(&[MediaType::new("application", "foo").into()]);
let foo_ct = ContentType::new("application", "foo"); let foo_ct = ContentType::new("application", "foo");

View File

@ -19,7 +19,7 @@ fn get3(_number: u64) -> &'static str { "3" }
#[test] #[test]
fn test_ranking() { fn test_ranking() {
let rocket = rocket::ignite().mount("/", routes![get0, get1, get2, get3]); let rocket = rocket::ignite().mount("/", routes![get0, get1, get2, get3]);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.get("/0").dispatch(); let response = client.get("/0").dispatch();
assert_eq!(response.into_string().unwrap(), "0"); assert_eq!(response.into_string().unwrap(), "0");
@ -44,7 +44,7 @@ fn test_rank_collision() {
use rocket::error::ErrorKind; use rocket::error::ErrorKind;
let rocket = rocket::ignite().mount("/", routes![get0, get0b]); let rocket = rocket::ignite().mount("/", routes![get0, get0b]);
let client_result = Client::tracked(rocket); let client_result = Client::debug(rocket);
match client_result.as_ref().map_err(|e| e.kind()) { match client_result.as_ref().map_err(|e| e.kind()) {
Err(ErrorKind::Collision(..)) => { /* o.k. */ }, Err(ErrorKind::Collision(..)) => { /* o.k. */ },
Ok(_) => panic!("client succeeded unexpectedly"), Ok(_) => panic!("client succeeded unexpectedly"),

View File

@ -25,7 +25,7 @@ fn test_raw_ident() {
.mount("/", routes![get, swap]) .mount("/", routes![get, swap])
.register(catchers![catch]); .register(catchers![catch]);
let client = Client::untracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.get("/example?type=1").dispatch(); let response = client.get("/example?type=1").dispatch();
assert_eq!(response.into_string().unwrap(), "example is 1"); assert_eq!(response.into_string().unwrap(), "example is 1");

View File

@ -87,7 +87,7 @@ fn test_full_route() {
.mount("/1", routes![post1]) .mount("/1", routes![post1])
.mount("/2", routes![post2]); .mount("/2", routes![post2]);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let a = RawStr::new("A%20A"); let a = RawStr::new("A%20A");
let name = RawStr::new("Bob%20McDonald"); let name = RawStr::new("Bob%20McDonald");
@ -171,7 +171,7 @@ fn filtered_raw_query(bird: usize, color: &str, rest: Contextual<'_, Filtered<'_
#[test] #[test]
fn test_filtered_raw_query() { fn test_filtered_raw_query() {
let rocket = rocket::ignite().mount("/", routes![filtered_raw_query]); let rocket = rocket::ignite().mount("/", routes![filtered_raw_query]);
let client = Client::untracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
#[track_caller] #[track_caller]
fn run(client: &Client, birds: &[&str], colors: &[&str], cats: &[&str]) -> (Status, String) { fn run(client: &Client, birds: &[&str], colors: &[&str], cats: &[&str]) -> (Status, String) {
@ -259,7 +259,7 @@ fn test_query_collection() {
} }
fn run_tests(rocket: rocket::Rocket) { fn run_tests(rocket: rocket::Rocket) {
let client = Client::untracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let colors = &["blue", "green"]; let colors = &["blue", "green"];
let dog = &["name=Fido", "age=10"]; let dog = &["name=Fido", "age=10"];
@ -327,7 +327,7 @@ fn test_inclusive_segments() {
.mount("/", routes![segments]) .mount("/", routes![segments])
.mount("/", routes![segments_empty]); .mount("/", routes![segments_empty]);
let client = Client::untracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let get = |uri| client.get(uri).dispatch().into_string().unwrap(); let get = |uri| client.get(uri).dispatch().into_string().unwrap();
assert_eq!(get("/"), "empty+"); assert_eq!(get("/"), "empty+");

View File

@ -27,11 +27,7 @@ fn test_ignored_segments() {
ig_1, just_static, ig_2, ig_3, ig_1_static, ig_1_static_static, wrapped ig_1, just_static, ig_2, ig_3, ig_1_static, ig_1_static_static, wrapped
]); ]);
let client = match Client::untracked(rocket) { let client = Client::debug(rocket).unwrap();
Ok(client) => client,
Err(e) => { drop(e); panic!("whoops") },
};
assert_eq!(get_string(&client, "/foo"), "1"); assert_eq!(get_string(&client, "/foo"), "1");
assert_eq!(get_string(&client, "/bar"), "1"); assert_eq!(get_string(&client, "/bar"), "1");
assert_eq!(get_string(&client, "/static"), "static"); assert_eq!(get_string(&client, "/static"), "static");

View File

@ -47,7 +47,7 @@ async-trait = "0.1.43"
[dependencies.state] [dependencies.state]
git = "https://github.com/SergioBenitez/state.git" git = "https://github.com/SergioBenitez/state.git"
rev = "7576652" rev = "0a7457d2"
[dependencies.rocket_codegen] [dependencies.rocket_codegen]
version = "0.5.0-dev" version = "0.5.0-dev"
@ -74,6 +74,7 @@ version_check = "0.9.1"
[dev-dependencies] [dev-dependencies]
bencher = "0.1" bencher = "0.1"
figment = { version = "0.10", features = ["test"] } figment = { version = "0.10", features = ["test"] }
pretty_assertions = "0.7"
[[bench]] [[bench]]
name = "format-routing" name = "format-routing"

View File

@ -10,8 +10,12 @@ fn get() -> &'static str { "get" }
fn post() -> &'static str { "post" } fn post() -> &'static str { "post" }
fn rocket() -> rocket::Rocket { fn rocket() -> rocket::Rocket {
rocket::custom(rocket::Config::figment().merge(("log_level", "off"))) let config = rocket::Config {
.mount("/", routes![get, post]) log_level: rocket::config::LogLevel::Off,
..rocket::Config::debug_default()
};
rocket::custom(config).mount("/", routes![get, post])
} }
use bencher::Bencher; use bencher::Bencher;

View File

@ -20,7 +20,12 @@ fn post2() -> &'static str { "html" }
fn post3() -> &'static str { "plain" } fn post3() -> &'static str { "plain" }
fn rocket() -> rocket::Rocket { fn rocket() -> rocket::Rocket {
rocket::custom(rocket::Config::figment().merge(("log_level", "off"))) let config = rocket::Config {
log_level: rocket::config::LogLevel::Off,
..rocket::Config::debug_default()
};
rocket::custom(config)
.mount("/", routes![get, get2, get3]) .mount("/", routes![get, get2, get3])
.mount("/", routes![post, post2, post3]) .mount("/", routes![post, post2, post3])
} }

View File

@ -23,7 +23,11 @@ fn index_c() -> &'static str { "index" }
fn index_dyn_a(_a: &str) -> &'static str { "index" } fn index_dyn_a(_a: &str) -> &'static str { "index" }
fn hello_world_rocket() -> rocket::Rocket { fn hello_world_rocket() -> rocket::Rocket {
let config = rocket::Config::figment().merge(("log_level", "off")); let config = rocket::Config {
log_level: rocket::config::LogLevel::Off,
..rocket::Config::debug_default()
};
rocket::custom(config).mount("/", routes![hello_world]) rocket::custom(config).mount("/", routes![hello_world])
} }

View File

@ -38,7 +38,7 @@ use crate::config::SecretKey;
/// ///
/// * **Profile** /// * **Profile**
/// ///
/// This provider does not set a profile. /// The profile is set to the value of the `profile` field.
/// ///
/// * **Metadata** /// * **Metadata**
/// ///
@ -54,6 +54,12 @@ use crate::config::SecretKey;
/// Note that these behaviors differ from those of [`Config::figment()`]. /// Note that these behaviors differ from those of [`Config::figment()`].
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Config { pub struct Config {
/// The selected profile. **(default: _debug_ `debug` / _release_ `release`)**
///
/// **Note:** This field is never serialized nor deserialized. It is set to
/// the value of the selected `Profile` during extraction.
#[serde(skip)]
pub profile: Profile,
/// IP address to serve on. **(default: `127.0.0.1`)** /// IP address to serve on. **(default: `127.0.0.1`)**
pub address: IpAddr, pub address: IpAddr,
/// Port to serve on. **(default: `8000`)** /// Port to serve on. **(default: `8000`)**
@ -106,10 +112,10 @@ impl Default for Config {
} }
impl Config { impl Config {
/// The default "debug" profile. /// The default debug profile: `debug`.
pub const DEBUG_PROFILE: Profile = Profile::const_new("debug"); pub const DEBUG_PROFILE: Profile = Profile::const_new("debug");
/// The default "release" profile. /// The default release profile: `release`.
pub const RELEASE_PROFILE: Profile = Profile::const_new("release"); pub const RELEASE_PROFILE: Profile = Profile::const_new("release");
/// The default profile: "debug" on `debug`, "release" on `release`. /// The default profile: "debug" on `debug`, "release" on `release`.
@ -145,6 +151,7 @@ impl Config {
/// ``` /// ```
pub fn debug_default() -> Config { pub fn debug_default() -> Config {
Config { Config {
profile: Self::DEBUG_PROFILE,
address: Ipv4Addr::new(127, 0, 0, 1).into(), address: Ipv4Addr::new(127, 0, 0, 1).into(),
port: 8000, port: 8000,
workers: num_cpus::get(), workers: num_cpus::get(),
@ -177,6 +184,7 @@ impl Config {
/// ``` /// ```
pub fn release_default() -> Config { pub fn release_default() -> Config {
Config { Config {
profile: Self::RELEASE_PROFILE,
log_level: LogLevel::Critical, log_level: LogLevel::Critical,
..Config::debug_default() ..Config::debug_default()
} }
@ -217,52 +225,55 @@ impl Config {
.merge(Env::prefixed("ROCKET_").ignore(&["PROFILE"]).global()) .merge(Env::prefixed("ROCKET_").ignore(&["PROFILE"]).global())
} }
/// Attempts to extract a `Config` from `provider`. /// Attempts to extract a `Config` from `provider`, returning the result.
///
/// # Panics
///
/// If extraction fails, prints an error message indicating the failure and
/// panics.
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use figment::{Figment, providers::{Toml, Format, Env}}; /// use rocket::Config;
/// use rocket::figment::providers::{Toml, Format, Env};
/// ///
/// // Use Rocket's default `Figment`, but allow values from `MyApp.toml` /// // Use Rocket's default `Figment`, but allow values from `MyApp.toml`
/// // and `MY_APP_` prefixed environment variables to supersede its values. /// // and `MY_APP_` prefixed environment variables to supersede its values.
/// let figment = rocket::Config::figment() /// let figment = Config::figment()
/// .merge(("some-thing", 123))
/// .merge(Env::prefixed("CONFIG_"));
///
/// let config = Config::try_from(figment);
/// ```
pub fn try_from<T: Provider>(provider: T) -> Result<Self> {
let figment = Figment::from(provider);
let mut config = figment.extract::<Self>()?;
config.profile = figment.profile().clone();
Ok(config)
}
/// Extract a `Config` from `provider`, panicking if extraction fails.
///
/// # Panics
///
/// If extraction fails, prints an error message indicating the failure and
/// panics. For a version that doesn't panic, use [`Config::try_from()`].
///
/// # Example
///
/// ```rust
/// use rocket::Config;
/// use rocket::figment::providers::{Toml, Format, Env};
///
/// // Use Rocket's default `Figment`, but allow values from `MyApp.toml`
/// // and `MY_APP_` prefixed environment variables to supersede its values.
/// let figment = Config::figment()
/// .merge(Toml::file("MyApp.toml").nested()) /// .merge(Toml::file("MyApp.toml").nested())
/// .merge(Env::prefixed("MY_APP_")); /// .merge(Env::prefixed("MY_APP_"));
/// ///
/// let config = rocket::Config::from(figment); /// let config = Config::from(figment);
/// ``` /// ```
#[track_caller]
pub fn from<T: Provider>(provider: T) -> Self { pub fn from<T: Provider>(provider: T) -> Self {
let figment = Figment::from(&provider); Self::try_from(provider).unwrap_or_else(|e| {
#[allow(unused_mut)]
let mut config = figment.extract::<Self>().unwrap_or_else(|e| {
pretty_print_error(e); pretty_print_error(e);
panic!("aborting due to configuration error(s)") panic!("aborting due to configuration error(s)")
}); })
#[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))]
if !config.secret_key.is_provided() {
if figment.profile() == Self::DEBUG_PROFILE {
// in debug, try to generate a key for a bit more security
let key = SecretKey::generate().unwrap_or(SecretKey::zero());
config.secret_key = key;
} else {
crate::logger::try_init(LogLevel::Debug, true, false);
error!("secrets enabled in non-debug without `secret_key`");
info_!("selected profile: {}", Paint::white(figment.profile()));
info_!("disable `secrets` feature or configure a `secret_key`");
panic!("aborting due to configuration error(s)")
}
}
config
} }
/// Returns `true` if TLS is enabled. /// Returns `true` if TLS is enabled.
@ -306,17 +317,8 @@ impl Config {
false => launch_info_!("tls: {}", Paint::default("disabled").bold()), false => launch_info_!("tls: {}", Paint::default("disabled").bold()),
} }
#[cfg(all(feature = "secrets", not(test), not(rocket_unsafe_secret_key)))] { #[cfg(feature = "secrets")]
launch_info_!("secret key: {:?}", launch_info_!("secret key: {:?}", Paint::default(&self.secret_key).bold());
Paint::default(&self.secret_key).bold());
if !self.secret_key.is_provided() {
warn!("secrets enabled without a configured `secret_key`");
info_!("disable `secrets` feature or configure a `secret_key`");
info_!("this becomes a {} in non-debug profiles",
Paint::red("hard error").bold());
}
}
launch_info_!("temp dir: {}", Paint::default(&self.temp_dir.display()).bold()); launch_info_!("temp dir: {}", Paint::default(&self.temp_dir.display()).bold());
launch_info_!("log level: {}", Paint::default(self.log_level).bold()); launch_info_!("log level: {}", Paint::default(self.log_level).bold());
@ -372,6 +374,9 @@ impl Provider for Config {
Ok(map) Ok(map)
} }
fn profile(&self) -> Option<Profile> {
Some(self.profile.clone())
}
} }
#[crate::async_trait] #[crate::async_trait]
@ -387,7 +392,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for &'r Config {
pub fn pretty_print_error(error: figment::Error) { pub fn pretty_print_error(error: figment::Error) {
use figment::error::{Kind, OneOf}; use figment::error::{Kind, OneOf};
crate::logger::try_init(LogLevel::Debug, true, false); let mut config = Config::debug_default();
config.log_level = LogLevel::Debug;
crate::logger::init(&config);
error!("Rocket configuration extraction from provider failed."); error!("Rocket configuration extraction from provider failed.");
for e in error { for e in error {

View File

@ -130,7 +130,8 @@ pub use secret_key::SecretKey;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use figment::Figment; use figment::{Figment, Profile};
use pretty_assertions::assert_eq;
use crate::config::{Config, TlsConfig}; use crate::config::{Config, TlsConfig};
use crate::logger::LogLevel; use crate::logger::LogLevel;
@ -140,19 +141,10 @@ mod tests {
fn test_default_round_trip() { fn test_default_round_trip() {
figment::Jail::expect_with(|_| { figment::Jail::expect_with(|_| {
let original = Config::figment(); let original = Config::figment();
let profile = original.profile().clone(); let roundtrip = Figment::from(Config::from(&original));
let roundtrip = Figment::from(Config::from(&original)).select(profile);
for figment in &[original, roundtrip] { for figment in &[original, roundtrip] {
assert_eq!(figment.profile(), Config::DEFAULT_PROFILE); let config = Config::from(figment);
#[cfg(debug_assertions)] assert_eq!(figment.profile(), Config::DEBUG_PROFILE);
#[cfg(not(debug_assertions))] assert_eq!(figment.profile(), Config::RELEASE_PROFILE);
let config: Config = figment.extract().unwrap();
assert_eq!(config, Config::default()); assert_eq!(config, Config::default());
#[cfg(debug_assertions)] assert_eq!(config, Config::debug_default());
#[cfg(not(debug_assertions))] assert_eq!(config, Config::release_default());
} }
Ok(()) Ok(())
@ -305,6 +297,7 @@ mod tests {
jail.set_env("ROCKET_PROFILE", "unknown"); jail.set_env("ROCKET_PROFILE", "unknown");
let config = Config::from(Config::figment()); let config = Config::from(Config::figment());
assert_eq!(config, Config { assert_eq!(config, Config {
profile: Profile::const_new("unknown"),
limits: Limits::default() limits: Limits::default()
.limit("stream", 50.kilobytes()) .limit("stream", 50.kilobytes())
.limit("forms", 2.kilobytes()), .limit("forms", 2.kilobytes()),
@ -314,6 +307,7 @@ mod tests {
jail.set_env("ROCKET_PROFILE", "debug"); jail.set_env("ROCKET_PROFILE", "debug");
let config = Config::from(Config::figment()); let config = Config::from(Config::figment());
assert_eq!(config, Config { assert_eq!(config, Config {
profile: Profile::const_new("debug"),
limits: Limits::default() limits: Limits::default()
.limit("stream", 50.kilobytes()) .limit("stream", 50.kilobytes())
.limit("forms", 2.kilobytes()) .limit("forms", 2.kilobytes())
@ -404,4 +398,60 @@ mod tests {
Ok(()) Ok(())
}); });
} }
#[test]
#[cfg(feature = "secrets")]
#[should_panic]
fn test_err_on_non_debug_and_no_secret_key() {
figment::Jail::expect_with(|jail| {
jail.set_env("ROCKET_PROFILE", "release");
let rocket = crate::custom(Config::figment());
let _result = crate::local::blocking::Client::untracked(rocket);
Ok(())
});
}
#[test]
#[cfg(feature = "secrets")]
#[should_panic]
fn test_err_on_non_debug2_and_no_secret_key() {
figment::Jail::expect_with(|jail| {
jail.set_env("ROCKET_PROFILE", "boop");
let rocket = crate::custom(Config::figment());
let _result = crate::local::blocking::Client::tracked(rocket);
Ok(())
});
}
#[test]
fn test_no_err_on_debug_and_no_secret_key() {
figment::Jail::expect_with(|jail| {
jail.set_env("ROCKET_PROFILE", "debug");
let figment = Config::figment();
assert!(crate::local::blocking::Client::untracked(crate::custom(&figment)).is_ok());
crate::async_main(async {
let rocket = crate::custom(&figment);
assert!(crate::local::asynchronous::Client::tracked(rocket).await.is_ok());
});
Ok(())
});
}
#[test]
fn test_no_err_on_release_and_custom_secret_key() {
figment::Jail::expect_with(|jail| {
jail.set_env("ROCKET_PROFILE", "release");
let key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk=";
let figment = Config::figment().merge(("secret_key", key));
assert!(crate::local::blocking::Client::tracked(crate::custom(&figment)).is_ok());
crate::async_main(async {
let rocket = crate::custom(&figment);
assert!(crate::local::asynchronous::Client::untracked(rocket).await.is_ok());
});
Ok(())
});
}
} }

View File

@ -30,34 +30,40 @@ enum Kind {
/// ``` /// ```
/// ///
/// When configured in the debug profile with the `secrets` feature enabled, a /// When configured in the debug profile with the `secrets` feature enabled, a
/// key set a `0` is automatically regenerated from the OS's random source if /// key set as `0` is automatically regenerated at launch time from the OS's
/// available. /// random source if available.
/// ///
/// ```rust,ignore /// ```rust
/// # // FIXME: Don't special case `SecretKey`.
/// use rocket::config::Config; /// use rocket::config::Config;
/// use rocket::local::blocking::Client;
/// ///
/// let figment = Config::figment() /// let figment = Config::figment()
/// .merge(("secret_key", vec![0u8; 64])) /// .merge(("secret_key", vec![0u8; 64]))
/// .select("debug"); /// .select("debug");
/// ///
/// let config = Config::from(figment); /// let rocket = rocket::custom(figment);
/// assert!(!config.secret_key.is_zero()); /// let client = Client::tracked(rocket).expect("okay in debug");
/// assert!(!client.rocket().config().secret_key.is_zero());
/// ``` /// ```
/// ///
/// When running in any other profile with the `secrets` feature enabled, /// When running in any other profile with the `secrets` feature enabled,
/// providing a key of `0` or not provided a key at all results in a failure at /// providing a key of `0` or not provided a key at all results in a failure at
/// launch-time: /// launch-time:
/// ///
/// ```rust,should_panic,ignore /// ```rust
/// # // FIXME: Don't special case `SecretKey` checking on test/unsafe_key.
/// use rocket::config::Config; /// use rocket::config::Config;
/// use rocket::figment::Profile;
/// use rocket::local::blocking::Client;
/// use rocket::error::ErrorKind;
/// ///
/// let profile = Profile::const_new("staging");
/// let figment = Config::figment() /// let figment = Config::figment()
/// .merge(("secret_key", vec![0u8; 64])) /// .merge(("secret_key", vec![0u8; 64]))
/// .select("staging"); /// .select(profile.clone());
/// ///
/// let config = Config::from(figment); /// let rocket = rocket::custom(figment);
/// let error = Client::tracked(rocket).expect_err("failure in non-debug");
/// assert!(matches!(error.kind(), ErrorKind::InsecureSecretKey(profile)));
/// ``` /// ```
/// ///
/// [private cookies]: https://rocket.rs/master/guide/requests/#private-cookies /// [private cookies]: https://rocket.rs/master/guide/requests/#private-cookies

View File

@ -47,8 +47,7 @@ pub use self::cookie::{Cookie, SameSite, Iter};
/// } /// }
/// # fn main() { /// # fn main() {
/// # use rocket::local::blocking::Client; /// # use rocket::local::blocking::Client;
/// # let rocket = rocket::ignite().mount("/", routes![message]); /// # let client = Client::debug_with(routes![message]).unwrap();
/// # let client = Client::tracked(rocket).unwrap();
/// # let response = client.get("/message") /// # let response = client.get("/message")
/// # .cookie(Cookie::new("message", "hi")) /// # .cookie(Cookie::new("message", "hi"))
/// # .dispatch(); /// # .dispatch();

View File

@ -4,6 +4,7 @@ use std::{io, fmt};
use std::sync::atomic::{Ordering, AtomicBool}; use std::sync::atomic::{Ordering, AtomicBool};
use yansi::Paint; use yansi::Paint;
use figment::Profile;
use crate::router::Route; use crate::router::Route;
@ -80,8 +81,10 @@ pub enum ErrorKind {
Runtime(Box<dyn std::error::Error + Send + Sync>), Runtime(Box<dyn std::error::Error + Send + Sync>),
/// Route collisions were detected. /// Route collisions were detected.
Collision(Vec<(Route, Route)>), Collision(Vec<(Route, Route)>),
/// A launch fairing reported an error. /// Launch fairing(s) failed.
FailedFairings(Vec<&'static str>), FailedFairings(Vec<crate::fairing::Info>),
/// The configuration profile is not debug but not secret key is configured.
InsecureSecretKey(Profile),
} }
impl From<ErrorKind> for Error { impl From<ErrorKind> for Error {
@ -137,9 +140,10 @@ impl fmt::Display for ErrorKind {
match self { match self {
ErrorKind::Bind(e) => write!(f, "binding failed: {}", e), ErrorKind::Bind(e) => write!(f, "binding failed: {}", e),
ErrorKind::Io(e) => write!(f, "I/O error: {}", e), ErrorKind::Io(e) => write!(f, "I/O error: {}", e),
ErrorKind::Collision(_) => write!(f, "route collisions detected"), ErrorKind::Collision(_) => "route collisions detected".fmt(f),
ErrorKind::FailedFairings(_) => write!(f, "a launch fairing failed"), ErrorKind::FailedFairings(_) => "a launch fairing failed".fmt(f),
ErrorKind::Runtime(e) => write!(f, "runtime error: {}", e) ErrorKind::Runtime(e) => write!(f, "runtime error: {}", e),
ErrorKind::InsecureSecretKey(_) => "insecure secret key config".fmt(f),
} }
} }
} }
@ -166,7 +170,7 @@ impl Drop for Error {
return return
} }
match *self.kind() { match self.kind() {
ErrorKind::Bind(ref e) => { ErrorKind::Bind(ref e) => {
error!("Rocket failed to bind network socket to given address/port."); error!("Rocket failed to bind network socket to given address/port.");
info_!("{}", e); info_!("{}", e);
@ -189,7 +193,7 @@ impl Drop for Error {
ErrorKind::FailedFairings(ref failures) => { ErrorKind::FailedFairings(ref failures) => {
error!("Rocket failed to launch due to failing fairings:"); error!("Rocket failed to launch due to failing fairings:");
for fairing in failures { for fairing in failures {
info_!("{}", fairing); info_!("{}", fairing.name);
} }
panic!("aborting due to launch fairing failure"); panic!("aborting due to launch fairing failure");
@ -199,6 +203,12 @@ impl Drop for Error {
info_!("{}", err); info_!("{}", err);
panic!("aborting due to runtime failure"); panic!("aborting due to runtime failure");
} }
ErrorKind::InsecureSecretKey(profile) => {
error!("secrets enabled in non-debug without `secret_key`");
info_!("selected profile: {}", Paint::white(profile));
info_!("disable `secrets` feature or configure a `secret_key`");
panic!("aborting due to insecure configuration")
}
} }
} }
} }

View File

@ -1,5 +1,5 @@
use crate::{Rocket, Request, Response, Data}; use crate::{Rocket, Request, Response, Data};
use crate::fairing::{Fairing, Kind}; use crate::fairing::{Fairing, Info, Kind};
use crate::logger::PaintExt; use crate::logger::PaintExt;
use yansi::Paint; use yansi::Paint;
@ -7,8 +7,9 @@ use yansi::Paint;
#[derive(Default)] #[derive(Default)]
pub struct Fairings { pub struct Fairings {
all_fairings: Vec<Box<dyn Fairing>>, all_fairings: Vec<Box<dyn Fairing>>,
attach_failures: Vec<&'static str>, attach_failures: Vec<Info>,
// The vectors below hold indices into `all_fairings`. // The vectors below hold indices into `all_fairings`.
attach: Vec<usize>,
launch: Vec<usize>, launch: Vec<usize>,
request: Vec<usize>, request: Vec<usize>,
response: Vec<usize>, response: Vec<usize>,
@ -23,26 +24,39 @@ impl Fairings {
pub async fn attach(&mut self, fairing: Box<dyn Fairing>, mut rocket: Rocket) -> Rocket { pub async fn attach(&mut self, fairing: Box<dyn Fairing>, mut rocket: Rocket) -> Rocket {
// Run the `on_attach` callback if this is an 'attach' fairing. // Run the `on_attach` callback if this is an 'attach' fairing.
let kind = fairing.info().kind; let kind = fairing.info().kind;
let name = fairing.info().name; let fairing = self.add(fairing);
if kind.is(Kind::Attach) { if kind.is(Kind::Attach) {
let info = fairing.info();
rocket = fairing.on_attach(rocket).await rocket = fairing.on_attach(rocket).await
.unwrap_or_else(|r| { self.attach_failures.push(name); r }) .unwrap_or_else(|r| { self.attach_failures.push(info); r })
} }
self.add(fairing);
rocket rocket
} }
fn add(&mut self, fairing: Box<dyn Fairing>) { fn add(&mut self, fairing: Box<dyn Fairing>) -> &dyn Fairing {
let kind = fairing.info().kind; let kind = fairing.info().kind;
if !kind.is_exactly(Kind::Attach) { let index = self.all_fairings.len();
let index = self.all_fairings.len(); self.all_fairings.push(fairing);
self.all_fairings.push(fairing);
if kind.is(Kind::Launch) { self.launch.push(index); } if kind.is(Kind::Attach) { self.attach.push(index); }
if kind.is(Kind::Request) { self.request.push(index); } if kind.is(Kind::Launch) { self.launch.push(index); }
if kind.is(Kind::Response) { self.response.push(index); } if kind.is(Kind::Request) { self.request.push(index); }
} if kind.is(Kind::Response) { self.response.push(index); }
&*self.all_fairings[index]
}
#[inline(always)]
fn fairings(&self, kind: Kind) -> impl Iterator<Item = &dyn Fairing> {
let indices = match kind {
k if k.is(Kind::Attach) => &self.attach,
k if k.is(Kind::Launch) => &self.launch,
k if k.is(Kind::Request) => &self.request,
_ => &self.response,
};
indices.iter().map(move |i| &*self.all_fairings[*i])
} }
pub fn append(&mut self, others: Fairings) { pub fn append(&mut self, others: Fairings) {
@ -53,26 +67,26 @@ impl Fairings {
#[inline(always)] #[inline(always)]
pub fn handle_launch(&self, rocket: &Rocket) { pub fn handle_launch(&self, rocket: &Rocket) {
for &i in &self.launch { for fairing in self.fairings(Kind::Launch) {
self.all_fairings[i].on_launch(rocket); fairing.on_launch(rocket);
} }
} }
#[inline(always)] #[inline(always)]
pub async fn handle_request(&self, req: &mut Request<'_>, data: &mut Data) { pub async fn handle_request(&self, req: &mut Request<'_>, data: &mut Data) {
for &i in &self.request { for fairing in self.fairings(Kind::Request) {
self.all_fairings[i].on_request(req, data).await; fairing.on_request(req, data).await
} }
} }
#[inline(always)] #[inline(always)]
pub async fn handle_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) { pub async fn handle_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) {
for &i in &self.response { for fairing in self.fairings(Kind::Response) {
self.all_fairings[i].on_response(request, response).await; fairing.on_response(request, response).await;
} }
} }
pub fn failures(&self) -> Option<&[&'static str]> { pub fn failures(&self) -> Option<&[Info]> {
if self.attach_failures.is_empty() { if self.attach_failures.is_empty() {
None None
} else { } else {
@ -80,24 +94,35 @@ impl Fairings {
} }
} }
fn info_for(&self, kind: &str, fairings: &[usize]) {
if !fairings.is_empty() {
let num = fairings.len();
let names = fairings.iter().cloned()
.map(|i| self.all_fairings[i].info().name)
.collect::<Vec<_>>()
.join(", ");
info_!("{} {}: {}", Paint::default(num).bold(), kind, Paint::default(names).bold());
}
}
pub fn pretty_print_counts(&self) { pub fn pretty_print_counts(&self) {
fn pretty_print(f: &Fairings, prefix: &str, kind: Kind) {
let names: Vec<_> = f.fairings(kind).map(|f| f.info().name).collect();
let num = names.len();
let joined = names.join(", ");
info_!("{} {}: {}", Paint::default(num).bold(), prefix, Paint::default(joined).bold());
}
if !self.all_fairings.is_empty() { if !self.all_fairings.is_empty() {
info!("{}{}:", Paint::emoji("📡 "), Paint::magenta("Fairings")); info!("{}{}:", Paint::emoji("📡 "), Paint::magenta("Fairings"));
self.info_for("launch", &self.launch); pretty_print(self, "attach", Kind::Attach);
self.info_for("request", &self.request); pretty_print(self, "launch", Kind::Launch);
self.info_for("response", &self.response); pretty_print(self, "request", Kind::Request);
pretty_print(self, "response", Kind::Response);
} }
} }
} }
impl std::fmt::Debug for Fairings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn debug_info(fs: &Fairings, kind: Kind) -> Vec<Info> {
fs.fairings(kind).map(|f| f.info()).collect()
}
f.debug_struct("Fairings")
.field("attach", &debug_info(self, Kind::Attach))
.field("launch", &debug_info(self, Kind::Launch))
.field("request", &debug_info(self, Kind::Request))
.field("response", &debug_info(self, Kind::Response))
.finish()
}
}

View File

@ -22,7 +22,7 @@ use std::ops::BitOr;
/// } /// }
/// # ; /// # ;
/// ``` /// ```
#[derive(Debug)] #[derive(Debug, Copy, Clone)]
pub struct Info { pub struct Info {
/// The name of the fairing. /// The name of the fairing.
pub name: &'static str, pub name: &'static str,

View File

@ -35,7 +35,7 @@ use crate::error::Error;
/// The following snippet creates a `Client` from a `Rocket` instance and /// The following snippet creates a `Client` from a `Rocket` instance and
/// dispatches a local `POST /` request with a body of `Hello, world!`. /// dispatches a local `POST /` request with a body of `Hello, world!`.
/// ///
/// ```rust /// ```rust,no_run
/// use rocket::local::asynchronous::Client; /// use rocket::local::asynchronous::Client;
/// ///
/// # rocket::async_test(async { /// # rocket::async_test(async {
@ -69,8 +69,7 @@ impl Client {
where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send
{ {
crate::async_test(async { crate::async_test(async {
let rocket = crate::ignite(); let client = Client::debug(crate::ignite()).await.unwrap();
let client = Client::untracked(rocket).await.expect("valid rocket");
let request = client.get("/"); let request = client.get("/");
let response = request.clone().dispatch().await; let response = request.clone().dispatch().await;
f(&client, request, response) f(&client, request, response)
@ -107,6 +106,12 @@ impl Client {
pub_client_impl!("use rocket::local::asynchronous::Client;" @async await); pub_client_impl!("use rocket::local::asynchronous::Client;" @async await);
} }
impl std::fmt::Debug for Client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self._rocket().fmt(f)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
#[test] #[test]

View File

@ -14,7 +14,7 @@ use super::{Client, LocalResponse};
/// The following snippet uses the available builder methods to construct and /// The following snippet uses the available builder methods to construct and
/// dispatch a `POST` request to `/` with a JSON body: /// dispatch a `POST` request to `/` with a JSON body:
/// ///
/// ```rust /// ```rust,no_run
/// use rocket::local::asynchronous::{Client, LocalRequest}; /// use rocket::local::asynchronous::{Client, LocalRequest};
/// use rocket::http::{ContentType, Cookie}; /// use rocket::http::{ContentType, Cookie};
/// ///

View File

@ -27,11 +27,10 @@ use crate::{Request, Response};
/// "Hello, world!" /// "Hello, world!"
/// } /// }
/// ///
/// # /*
/// #[launch] /// #[launch]
/// # */
/// fn rocket() -> rocket::Rocket { /// fn rocket() -> rocket::Rocket {
/// rocket::ignite().mount("/", routes![hello_world]) /// rocket::ignite().mount("/", routes![hello_world])
/// # .reconfigure(rocket::Config::debug_default())
/// } /// }
/// ///
/// # async fn read_body_manually() -> io::Result<()> { /// # async fn read_body_manually() -> io::Result<()> {

View File

@ -16,7 +16,7 @@ use crate::http::Method;
/// The following snippet creates a `Client` from a `Rocket` instance and /// The following snippet creates a `Client` from a `Rocket` instance and
/// dispatches a local `POST /` request with a body of `Hello, world!`. /// dispatches a local `POST /` request with a body of `Hello, world!`.
/// ///
/// ```rust /// ```rust,no_run
/// use rocket::local::blocking::Client; /// use rocket::local::blocking::Client;
/// ///
/// let rocket = rocket::ignite(); /// let rocket = rocket::ignite();
@ -47,8 +47,7 @@ impl Client {
pub fn _test<T, F>(f: F) -> T pub fn _test<T, F>(f: F) -> T
where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send
{ {
let rocket = crate::ignite(); let client = Client::debug(crate::ignite()).unwrap();
let client = Client::untracked(rocket).expect("valid rocket");
let request = client.get("/"); let request = client.get("/");
let response = request.clone().dispatch(); let response = request.clone().dispatch();
f(&client, request, response) f(&client, request, response)
@ -93,6 +92,12 @@ impl Client {
pub_client_impl!("use rocket::local::blocking::Client;"); pub_client_impl!("use rocket::local::blocking::Client;");
} }
impl std::fmt::Debug for Client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self._rocket().fmt(f)
}
}
impl Drop for Client { impl Drop for Client {
fn drop(&mut self) { fn drop(&mut self) {
let client = self.inner.take(); let client = self.inner.take();

View File

@ -13,7 +13,7 @@ use super::{Client, LocalResponse};
/// The following snippet uses the available builder methods to construct and /// The following snippet uses the available builder methods to construct and
/// dispatch a `POST` request to `/` with a JSON body: /// dispatch a `POST` request to `/` with a JSON body:
/// ///
/// ```rust /// ```rust,no_run
/// use rocket::local::blocking::{Client, LocalRequest}; /// use rocket::local::blocking::{Client, LocalRequest};
/// use rocket::http::{ContentType, Cookie}; /// use rocket::http::{ContentType, Cookie};
/// ///

View File

@ -24,11 +24,10 @@ use super::Client;
/// "Hello, world!" /// "Hello, world!"
/// } /// }
/// ///
/// # /*
/// #[launch] /// #[launch]
/// # */
/// fn rocket() -> rocket::Rocket { /// fn rocket() -> rocket::Rocket {
/// rocket::ignite().mount("/", routes![hello_world]) /// rocket::ignite().mount("/", routes![hello_world])
/// # .reconfigure(rocket::Config::debug_default())
/// } /// }
/// ///
/// # fn read_body_manually() -> io::Result<()> { /// # fn read_body_manually() -> io::Result<()> {

View File

@ -96,11 +96,17 @@ macro_rules! pub_client_impl {
} }
#[doc(hidden)] #[doc(hidden)]
pub $($prefix)? fn debug(base: &str, routes: Vec<crate::Route>) -> Result<Self, Error> { pub $($prefix)? fn debug_with(routes: Vec<crate::Route>) -> Result<Self, Error> {
let mut config = crate::Config::debug_default(); let rocket = crate::custom(crate::Config::debug_default());
Self::debug(rocket.mount("/", routes)) $(.$suffix)?
}
#[doc(hidden)]
pub $($prefix)? fn debug(rocket: Rocket) -> Result<Self, Error> {
let mut config = rocket.config().clone();
config.log_level = crate::config::LogLevel::Debug; config.log_level = crate::config::LogLevel::Debug;
let rocket = crate::custom(config).mount(base, routes); config.profile = crate::Config::DEBUG_PROFILE;
Self::tracked(rocket) $(.$suffix)? Self::tracked(rocket.reconfigure(config)) $(.$suffix)?
} }
/// Deprecated alias to [`Client::tracked()`]. /// Deprecated alias to [`Client::tracked()`].
@ -117,7 +123,7 @@ macro_rules! pub_client_impl {
/// ///
/// # Example /// # Example
/// ///
/// ```rust,no_run /// ```rust
#[doc = $import] #[doc = $import]
/// ///
/// # Client::_test(|client, _, _| { /// # Client::_test(|client, _, _| {
@ -196,5 +202,8 @@ macro_rules! pub_client_impl {
fn _ensure_impls_exist() { fn _ensure_impls_exist() {
fn is_send<T: Send>() {} fn is_send<T: Send>() {}
is_send::<Self>(); is_send::<Self>();
fn is_debug<T: std::fmt::Debug>() {}
is_debug::<Self>();
} }
}} }}

View File

@ -163,27 +163,25 @@ impl log::Log for RocketLogger {
} }
} }
pub(crate) fn try_init(level: LogLevel, colors: bool, verbose: bool) -> bool { pub(crate) fn init(config: &crate::Config) -> bool {
if level == LogLevel::Off { if config.log_level == LogLevel::Off {
return false; return false;
} }
if !atty::is(atty::Stream::Stdout) if !atty::is(atty::Stream::Stdout)
|| (cfg!(windows) && !Paint::enable_windows_ascii()) || (cfg!(windows) && !Paint::enable_windows_ascii())
|| !colors || !config.cli_colors
{ {
Paint::disable(); Paint::disable();
} }
if let Err(e) = log::set_boxed_logger(Box::new(RocketLogger(level))) { if let Err(e) = log::set_boxed_logger(Box::new(RocketLogger(config.log_level))) {
if verbose { if config.log_level == LogLevel::Debug {
eprintln!("Logger failed to initialize: {}", e); eprintln!("Logger failed to initialize: {}", e);
} }
return false;
} }
log::set_max_level(level.to_level_filter()); log::set_max_level(config.log_level.to_level_filter());
true true
} }
@ -199,11 +197,6 @@ impl PaintExt for Paint<&str> {
} }
} }
#[doc(hidden)]
pub fn init(level: LogLevel) -> bool {
try_init(level, true, true)
}
// Expose logging macros as (hidden) funcions for use by core/contrib codegen. // Expose logging macros as (hidden) funcions for use by core/contrib codegen.
macro_rules! external_log_function { macro_rules! external_log_function {
($fn_name:ident: $macro_name:ident) => ( ($fn_name:ident: $macro_name:ident) => (

View File

@ -104,7 +104,7 @@ impl<'r> Request<'r> {
/// ```rust /// ```rust
/// use rocket::http::Method; /// use rocket::http::Method;
/// ///
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let get = |uri| c.get(uri); /// # let get = |uri| c.get(uri);
/// # let post = |uri| c.post(uri); /// # let post = |uri| c.post(uri);
/// assert_eq!(get("/").method(), Method::Get); /// assert_eq!(get("/").method(), Method::Get);
@ -139,7 +139,7 @@ impl<'r> Request<'r> {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let get = |uri| c.get(uri); /// # let get = |uri| c.get(uri);
/// assert_eq!(get("/hello/rocketeer").uri().path(), "/hello/rocketeer"); /// assert_eq!(get("/hello/rocketeer").uri().path(), "/hello/rocketeer");
/// assert_eq!(get("/hello").uri().query(), None); /// assert_eq!(get("/hello").uri().query(), None);
@ -231,7 +231,7 @@ impl<'r> Request<'r> {
/// use std::net::Ipv4Addr; /// use std::net::Ipv4Addr;
/// use rocket::http::Header; /// use rocket::http::Header;
/// ///
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let req = c.get("/"); /// # let req = c.get("/");
/// assert_eq!(req.real_ip(), None); /// assert_eq!(req.real_ip(), None);
/// ///
@ -292,7 +292,7 @@ impl<'r> Request<'r> {
/// ```rust /// ```rust
/// use rocket::http::Cookie; /// use rocket::http::Cookie;
/// ///
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let request = c.get("/"); /// # let request = c.get("/");
/// # let req = request.inner(); /// # let req = request.inner();
/// req.cookies().add(Cookie::new("key", "val")); /// req.cookies().add(Cookie::new("key", "val"));
@ -312,7 +312,7 @@ impl<'r> Request<'r> {
/// ```rust /// ```rust
/// use rocket::http::{Accept, ContentType}; /// use rocket::http::{Accept, ContentType};
/// ///
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let get = |uri| c.get(uri); /// # let get = |uri| c.get(uri);
/// assert!(get("/").headers().is_empty()); /// assert!(get("/").headers().is_empty());
/// ///
@ -384,7 +384,7 @@ impl<'r> Request<'r> {
/// ```rust /// ```rust
/// use rocket::http::ContentType; /// use rocket::http::ContentType;
/// ///
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let get = |uri| c.get(uri); /// # let get = |uri| c.get(uri);
/// assert_eq!(get("/").content_type(), None); /// assert_eq!(get("/").content_type(), None);
/// ///
@ -406,7 +406,7 @@ impl<'r> Request<'r> {
/// ```rust /// ```rust
/// use rocket::http::Accept; /// use rocket::http::Accept;
/// ///
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let get = |uri| c.get(uri); /// # let get = |uri| c.get(uri);
/// assert_eq!(get("/").accept(), None); /// assert_eq!(get("/").accept(), None);
/// assert_eq!(get("/").header(Accept::JSON).accept(), Some(&Accept::JSON)); /// assert_eq!(get("/").header(Accept::JSON).accept(), Some(&Accept::JSON));
@ -432,7 +432,7 @@ impl<'r> Request<'r> {
/// ///
/// ```rust /// ```rust
/// use rocket::http::{Accept, ContentType, MediaType}; /// use rocket::http::{Accept, ContentType, MediaType};
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let get = |uri| c.get(uri); /// # let get = |uri| c.get(uri);
/// # let post = |uri| c.post(uri); /// # let post = |uri| c.post(uri);
/// ///
@ -472,7 +472,7 @@ impl<'r> Request<'r> {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let request = c.get("/"); /// # let request = c.get("/");
/// let config = request.config(); /// let config = request.config();
/// ``` /// ```
@ -486,7 +486,7 @@ impl<'r> Request<'r> {
/// ///
/// ```rust /// ```rust
/// use rocket::data::ToByteUnit; /// use rocket::data::ToByteUnit;
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let request = c.get("/"); /// # let request = c.get("/");
/// ///
/// // This is the default `form` limit. /// // This is the default `form` limit.
@ -508,7 +508,7 @@ impl<'r> Request<'r> {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let request = c.get("/"); /// # let request = c.get("/");
/// let route = request.route(); /// let route = request.route();
/// ``` /// ```
@ -525,7 +525,7 @@ impl<'r> Request<'r> {
/// ```rust /// ```rust
/// # type User = rocket::http::Method; /// # type User = rocket::http::Method;
/// # rocket::async_test(async move { /// # rocket::async_test(async move {
/// # let c = rocket::local::asynchronous::Client::debug("/", vec![]).await.unwrap(); /// # let c = rocket::local::asynchronous::Client::debug_with(vec![]).await.unwrap();
/// # let request = c.get("/"); /// # let request = c.get("/");
/// let outcome = request.guard::<User>().await; /// let outcome = request.guard::<User>().await;
/// # }) /// # })
@ -541,7 +541,7 @@ impl<'r> Request<'r> {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let request = c.get("/"); /// # let request = c.get("/");
/// # type Pool = usize; /// # type Pool = usize;
/// let pool = request.managed_state::<Pool>(); /// let pool = request.managed_state::<Pool>();
@ -567,7 +567,7 @@ impl<'r> Request<'r> {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let request = c.get("/"); /// # let request = c.get("/");
/// // The first store into local cache for a given type wins. /// // The first store into local cache for a given type wins.
/// let value = request.local_cache(|| "hello"); /// let value = request.local_cache(|| "hello");
@ -602,7 +602,7 @@ impl<'r> Request<'r> {
/// } /// }
/// ///
/// # rocket::async_test(async move { /// # rocket::async_test(async move {
/// # let c = rocket::local::asynchronous::Client::debug("/", vec![]).await.unwrap(); /// # let c = rocket::local::asynchronous::Client::debug_with(vec![]).await.unwrap();
/// # let request = c.get("/"); /// # let request = c.get("/");
/// let current_user = request.local_cache_async(async { /// let current_user = request.local_cache_async(async {
/// current_user(&request).await /// current_user(&request).await
@ -636,7 +636,7 @@ impl<'r> Request<'r> {
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let get = |uri| c.get(uri); /// # let get = |uri| c.get(uri);
/// assert_eq!(get("/a/b/c").param(0), Some(Ok("a"))); /// assert_eq!(get("/a/b/c").param(0), Some(Ok("a")));
/// assert_eq!(get("/a/b/c").param(1), Some(Ok("b"))); /// assert_eq!(get("/a/b/c").param(1), Some(Ok("b")));
@ -674,7 +674,7 @@ impl<'r> Request<'r> {
/// ```rust /// ```rust
/// use std::path::PathBuf; /// use std::path::PathBuf;
/// ///
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let get = |uri| c.get(uri); /// # let get = |uri| c.get(uri);
/// assert_eq!(get("/").segments(0..), Ok(PathBuf::new())); /// assert_eq!(get("/").segments(0..), Ok(PathBuf::new()));
/// assert_eq!(get("/").segments(2..), Ok(PathBuf::new())); /// assert_eq!(get("/").segments(2..), Ok(PathBuf::new()));
@ -723,7 +723,7 @@ impl<'r> Request<'r> {
/// age: usize /// age: usize
/// } /// }
/// ///
/// # let c = rocket::local::blocking::Client::debug("/", vec![]).unwrap(); /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
/// # let get = |uri| c.get(uri); /// # let get = |uri| c.get(uri);
/// let req = get("/?a=apple&z=zebra&a=aardvark"); /// let req = get("/?a=apple&z=zebra&a=aardvark");
/// assert_eq!(req.query_value::<&str>("a").unwrap(), Ok("apple")); /// assert_eq!(req.query_value::<&str>("a").unwrap(), Ok("apple"));

View File

@ -69,8 +69,7 @@ impl<'r, R> Created<R> {
/// status::Created::new("http://myservice.com/resource.json") /// status::Created::new("http://myservice.com/resource.json")
/// } /// }
/// ///
/// # let rocket = rocket::ignite().mount("/", routes![create]); /// # let client = Client::debug_with(routes![create]).unwrap();
/// # let client = Client::tracked(rocket).unwrap();
/// let response = client.get("/").dispatch(); /// let response = client.get("/").dispatch();
/// ///
/// let loc = response.headers().get_one("Location"); /// let loc = response.headers().get_one("Location");
@ -98,8 +97,7 @@ impl<'r, R> Created<R> {
/// .body("{ 'resource': 'Hello, world!' }") /// .body("{ 'resource': 'Hello, world!' }")
/// } /// }
/// ///
/// # let rocket = rocket::ignite().mount("/", routes![create]); /// # let client = Client::debug_with(routes![create]).unwrap();
/// # let client = Client::tracked(rocket).unwrap();
/// let response = client.get("/").dispatch(); /// let response = client.get("/").dispatch();
/// ///
/// let loc = response.headers().get_one("Location"); /// let loc = response.headers().get_one("Location");
@ -131,8 +129,7 @@ impl<'r, R> Created<R> {
/// .tagged_body("{ 'resource': 'Hello, world!' }") /// .tagged_body("{ 'resource': 'Hello, world!' }")
/// } /// }
/// ///
/// # let rocket = rocket::ignite().mount("/", routes![create]); /// # let client = Client::debug_with(routes![create]).unwrap();
/// # let client = Client::tracked(rocket).unwrap();
/// let response = client.get("/").dispatch(); /// let response = client.get("/").dispatch();
/// ///
/// let loc = response.headers().get_one("Location"); /// let loc = response.headers().get_one("Location");

View File

@ -18,6 +18,7 @@ use crate::error::{Error, ErrorKind};
/// The main `Rocket` type: used to mount routes and catchers and launch the /// The main `Rocket` type: used to mount routes and catchers and launch the
/// application. /// application.
#[derive(Debug)]
pub struct Rocket { pub struct Rocket {
pub(crate) config: Config, pub(crate) config: Config,
pub(crate) figment: Figment, pub(crate) figment: Figment,
@ -53,6 +54,7 @@ impl Rocket {
/// # }; /// # };
/// ``` /// ```
#[track_caller] #[track_caller]
#[inline(always)]
pub fn ignite() -> Rocket { pub fn ignite() -> Rocket {
Rocket::custom(Config::figment()) Rocket::custom(Config::figment())
} }
@ -63,10 +65,10 @@ impl Rocket {
/// ///
/// # Panics /// # Panics
/// ///
/// If there is an error reading configuration sources, this function prints /// If there is an error reading a [`Config`] from `provider`, function
/// a nice error message and then exits the process. /// prints a nice error message and then exits the process.
/// ///
/// # Examples /// # Example
/// ///
/// ```rust /// ```rust
/// use figment::{Figment, providers::{Toml, Env, Format}}; /// use figment::{Figment, providers::{Toml, Env, Format}};
@ -80,18 +82,17 @@ impl Rocket {
/// rocket::custom(figment) /// rocket::custom(figment)
/// } /// }
/// ``` /// ```
#[inline]
#[track_caller] #[track_caller]
pub fn custom<T: figment::Provider>(provider: T) -> Rocket { pub fn custom<T: figment::Provider>(provider: T) -> Rocket {
let (config, figment) = (Config::from(&provider), Figment::from(provider)); let config = Config::from(&provider);
logger::try_init(config.log_level, config.cli_colors, false); let figment = Figment::from(provider);
logger::init(&config);
config.pretty_print(&figment); config.pretty_print(&figment);
let managed_state = <Container![Send + Sync]>::new(); let managed_state = <Container![Send + Sync]>::new();
let (shutdown_sender, shutdown_receiver) = mpsc::channel(1); let (shutdown_sender, shutdown_receiver) = mpsc::channel(1);
Rocket { Rocket {
config, figment, config, figment, managed_state,
managed_state,
shutdown_handle: Shutdown(shutdown_sender), shutdown_handle: Shutdown(shutdown_sender),
router: Router::new(), router: Router::new(),
default_catcher: None, default_catcher: None,
@ -101,6 +102,56 @@ impl Rocket {
} }
} }
/// Resets the configuration in `self` to that provided by `provider`.
///
/// # Panics
///
/// Like [`Rocket::custom()`], panics if `provider` does not provide a valid
/// [`Config`]. The error message is printed.
///
/// # Examples
///
/// To modify only some values, use the existing `config`:
///
/// ```rust
/// use std::net::Ipv4Addr;
///
/// let config = rocket::Config {
/// port: 7777,
/// address: Ipv4Addr::new(18, 127, 0, 1).into(),
/// ..rocket::Config::default()
/// };
///
/// let rocket = rocket::custom(&config);
/// assert_eq!(rocket.config().port, 7777);
/// assert_eq!(rocket.config().address, Ipv4Addr::new(18, 127, 0, 1));
///
/// // Modifying the existing config:
/// let mut new_config = rocket.config().clone();
/// new_config.port = 8888;
///
/// let rocket = rocket.reconfigure(new_config);
/// assert_eq!(rocket.config().port, 8888);
/// assert_eq!(rocket.config().address, Ipv4Addr::new(18, 127, 0, 1));
///
/// // Modifying the existing figment:
/// let mut new_figment = rocket.figment().clone()
/// .merge(("address", "171.64.200.10"));
///
/// let rocket = rocket.reconfigure(new_figment);
/// assert_eq!(rocket.config().port, 8888);
/// assert_eq!(rocket.config().address, Ipv4Addr::new(171, 64, 200, 10));
/// ```
#[inline]
#[track_caller]
pub fn reconfigure<T: figment::Provider>(mut self, provider: T) -> Rocket {
self.config = Config::from(&provider);
self.figment = Figment::from(provider);
logger::init(&self.config);
self.config.pretty_print(&self.figment);
self
}
/// Mounts all of the routes in the supplied vector at the given `base` /// Mounts all of the routes in the supplied vector at the given `base`
/// path. Mounting a route with path `path` at path `base` makes the route /// path. Mounting a route with path `path` at path `base` makes the route
/// available at `base/path`. /// available at `base/path`.
@ -152,7 +203,6 @@ impl Rocket {
/// # .launch().await; /// # .launch().await;
/// # }; /// # };
/// ``` /// ```
#[inline]
pub fn mount<R: Into<Vec<Route>>>(mut self, base: &str, routes: R) -> Self { pub fn mount<R: Into<Vec<Route>>>(mut self, base: &str, routes: R) -> Self {
let base_uri = Origin::parse_owned(base.to_string()) let base_uri = Origin::parse_owned(base.to_string())
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
@ -210,7 +260,6 @@ impl Rocket {
/// rocket::ignite().register(catchers![internal_error, not_found]) /// rocket::ignite().register(catchers![internal_error, not_found])
/// } /// }
/// ``` /// ```
#[inline]
pub fn register(mut self, catchers: Vec<Catcher>) -> Self { pub fn register(mut self, catchers: Vec<Catcher>) -> Self {
info!("{}{}", Paint::emoji("👾 "), Paint::magenta("Catchers:")); info!("{}{}", Paint::emoji("👾 "), Paint::magenta("Catchers:"));
@ -294,7 +343,6 @@ impl Rocket {
/// })) /// }))
/// } /// }
/// ``` /// ```
#[inline]
pub fn attach<F: Fairing>(mut self, fairing: F) -> Self { pub fn attach<F: Fairing>(mut self, fairing: F) -> Self {
let future = async move { let future = async move {
let fairing = Box::new(fairing); let fairing = Box::new(fairing);
@ -472,8 +520,10 @@ impl Rocket {
self.shutdown_handle.clone() self.shutdown_handle.clone()
} }
/// Perform "pre-launch" checks: verify that there are no routing colisions /// Perform "pre-launch" checks: verify:
/// and that there were no fairing failures. /// * there are no routing colisionns
/// * there were no fairing failures
/// * a secret key, if needed, is securely configured
pub(crate) async fn prelaunch_check(&mut self) -> Result<(), Error> { pub(crate) async fn prelaunch_check(&mut self) -> Result<(), Error> {
if let Err(e) = self.router.collisions() { if let Err(e) = self.router.collisions() {
return Err(Error::new(ErrorKind::Collision(e))); return Err(Error::new(ErrorKind::Collision(e)));
@ -483,6 +533,25 @@ impl Rocket {
return Err(Error::new(ErrorKind::FailedFairings(failures.to_vec()))) return Err(Error::new(ErrorKind::FailedFairings(failures.to_vec())))
} }
#[cfg(feature = "secrets")]
if !self.config.secret_key.is_provided() {
let profile = self.figment.profile();
if profile != Config::DEBUG_PROFILE {
return Err(Error::new(ErrorKind::InsecureSecretKey(profile.clone())));
} else {
self.config.secret_key = crate::config::SecretKey::generate()
.unwrap_or(crate::config::SecretKey::zero());
warn!("secrets enabled without a configured `secret_key`");
info_!("disable `secrets` feature or configure a `secret_key`");
info_!("this becomes an {} in non-debug profiles", Paint::red("error"));
if !self.config.secret_key.is_zero() {
warn_!("a random key has been generated for this launch");
}
}
};
Ok(()) Ok(())
} }

View File

@ -14,7 +14,7 @@ pub use self::segment::Segment;
// type Selector = (Method, usize); // type Selector = (Method, usize);
type Selector = Method; type Selector = Method;
#[derive(Default)] #[derive(Debug, Default)]
pub struct Router { pub struct Router {
routes: HashMap<Selector, Vec<Route>>, routes: HashMap<Selector, Vec<Route>>,
} }

View File

@ -18,7 +18,7 @@ mod test_absolute_uris_okay {
#[test] #[test]
fn redirect_works() { fn redirect_works() {
let client = Client::debug("/", routes![google, redirect]).unwrap(); let client = Client::debug_with(routes![google, redirect]).unwrap();
let response = client.get("/google").dispatch(); let response = client.get("/google").dispatch();
let location = response.headers().get_one("Location"); let location = response.headers().get_one("Location");

View File

@ -29,7 +29,7 @@ mod tests {
req.cookies().add(Cookie::new("fairing", "woo")); req.cookies().add(Cookie::new("fairing", "woo"));
}))); })));
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
// Check that the index returns the `index` and `fairing` cookie. // Check that the index returns the `index` and `fairing` cookie.
let response = client.get("/").dispatch(); let response = client.get("/").dispatch();

View File

@ -19,8 +19,7 @@ mod conditionally_set_server_header {
#[test] #[test]
fn do_not_overwrite_server_header() { fn do_not_overwrite_server_header() {
let rocket = rocket::ignite().mount("/", routes![do_not_overwrite, use_default]); let client = Client::debug_with(routes![do_not_overwrite, use_default]).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.get("/do_not_overwrite").dispatch(); let response = client.get("/do_not_overwrite").dispatch();
let server = response.headers().get_one("Server"); let server = response.headers().get_one("Server");

View File

@ -45,8 +45,7 @@ fn number(params: ThingForm) -> DerivedResponder {
fn test_derive_reexports() { fn test_derive_reexports() {
use rocket::local::blocking::Client; use rocket::local::blocking::Client;
let rocket = rocket::ignite().mount("/", routes![index, number]); let client = Client::debug_with(routes![index, number]).unwrap();
let client = Client::tracked(rocket).unwrap();
let response = client.get("/").dispatch(); let response = client.get("/").dispatch();
assert_eq!(response.into_string().unwrap(), "hello"); assert_eq!(response.into_string().unwrap(), "hello");

View File

@ -10,8 +10,7 @@ mod encoded_uris {
#[test] #[test]
fn can_route_to_encoded_uri() { fn can_route_to_encoded_uri() {
let rocket = rocket::ignite().mount("/", routes![super::index]); let client = Client::debug_with(routes![super::index]).unwrap();
let client = Client::untracked(rocket).unwrap();
let response = client.get("/hello%20s%C3%BCper%20%24?a&%3F&value=a+b") let response = client.get("/hello%20s%C3%BCper%20%24?a&%3F&value=a+b")
.dispatch() .dispatch()
.into_string(); .into_string();

View File

@ -40,7 +40,7 @@ mod fairing_before_head_strip {
}) })
})); }));
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.head("/").dispatch(); let response = client.head("/").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert!(response.body().is_none()); assert!(response.body().is_none());
@ -71,7 +71,7 @@ mod fairing_before_head_strip {
}) })
})); }));
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.head("/").dispatch(); let response = client.head("/").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert!(response.body().is_none()); assert!(response.body().is_none());

View File

@ -27,10 +27,9 @@ mod flash_lazy_remove_tests {
#[test] #[test]
fn test() { fn test() {
use super::*; use super::*;
let r = rocket::ignite().mount("/", routes![set, unused, used]);
let client = Client::tracked(r).unwrap();
// Ensure the cookie's not there at first. // Ensure the cookie's not there at first.
let client = Client::debug_with(routes![set, unused, used]).unwrap();
let response = client.get("/unused").dispatch(); let response = client.get("/unused").dispatch();
assert_eq!(response.status(), Status::NotFound); assert_eq!(response.status(), Status::NotFound);

View File

@ -20,7 +20,7 @@ mod tests {
#[test] #[test]
fn method_eval() { fn method_eval() {
let client = Client::tracked(rocket::ignite().mount("/", routes![bug])).unwrap(); let client = Client::debug_with(routes![bug]).unwrap();
let response = client.post("/") let response = client.post("/")
.header(ContentType::Form) .header(ContentType::Form)
.body("_method=patch&form_data=Form+data") .body("_method=patch&form_data=Form+data")
@ -31,7 +31,7 @@ mod tests {
#[test] #[test]
fn get_passes_through() { fn get_passes_through() {
let client = Client::tracked(rocket::ignite().mount("/", routes![bug])).unwrap(); let client = Client::debug_with(routes![bug]).unwrap();
let response = client.get("/") let response = client.get("/")
.header(ContentType::Form) .header(ContentType::Form)
.body("_method=patch&form_data=Form+data") .body("_method=patch&form_data=Form+data")

View File

@ -14,7 +14,7 @@ mod tests {
use rocket::http::Status; use rocket::http::Status;
fn check_decoding(raw: &str, decoded: &str) { fn check_decoding(raw: &str, decoded: &str) {
let client = Client::tracked(rocket::ignite().mount("/", routes![bug])).unwrap(); let client = Client::debug_with(routes![bug]).unwrap();
let response = client.post("/") let response = client.post("/")
.header(ContentType::Form) .header(ContentType::Form)
.body(format!("form_data={}", raw)) .body(format!("form_data={}", raw))

View File

@ -30,7 +30,7 @@ mod head_handling_tests {
#[test] #[test]
fn auto_head() { fn auto_head() {
let client = Client::tracked(rocket::ignite().mount("/", routes())).unwrap(); let client = Client::debug_with(routes()).unwrap();
let response = client.head("/").dispatch(); let response = client.head("/").dispatch();
let content_type: Vec<_> = response.headers().get("Content-Type").collect(); let content_type: Vec<_> = response.headers().get("Content-Type").collect();
@ -46,7 +46,7 @@ mod head_handling_tests {
#[test] #[test]
fn user_head() { fn user_head() {
let client = Client::tracked(rocket::ignite().mount("/", routes())).unwrap(); let client = Client::debug_with(routes()).unwrap();
let response = client.head("/other").dispatch(); let response = client.head("/other").dispatch();
let content_type: Vec<_> = response.headers().get("Content-Type").collect(); let content_type: Vec<_> = response.headers().get("Content-Type").collect();

View File

@ -14,17 +14,14 @@ mod limits_tests {
use rocket::data::Limits; use rocket::data::Limits;
fn rocket_with_forms_limit(limit: u64) -> rocket::Rocket { fn rocket_with_forms_limit(limit: u64) -> rocket::Rocket {
let config = rocket::Config { let mut config = rocket::Config::debug_default();
limits: Limits::default().limit("form", limit.into()), config.limits = Limits::default().limit("form", limit.into());
..rocket::Config::debug_default()
};
rocket::custom(config).mount("/", routes![super::index]) rocket::custom(config).mount("/", routes![super::index])
} }
#[test] #[test]
fn large_enough() { fn large_enough() {
let client = Client::tracked(rocket_with_forms_limit(128)).unwrap(); let client = Client::debug(rocket_with_forms_limit(128)).unwrap();
let response = client.post("/") let response = client.post("/")
.body("value=Hello+world") .body("value=Hello+world")
.header(ContentType::Form) .header(ContentType::Form)
@ -35,7 +32,7 @@ mod limits_tests {
#[test] #[test]
fn just_large_enough() { fn just_large_enough() {
let client = Client::tracked(rocket_with_forms_limit(17)).unwrap(); let client = Client::debug(rocket_with_forms_limit(17)).unwrap();
let response = client.post("/") let response = client.post("/")
.body("value=Hello+world") .body("value=Hello+world")
.header(ContentType::Form) .header(ContentType::Form)
@ -46,7 +43,7 @@ mod limits_tests {
#[test] #[test]
fn much_too_small() { fn much_too_small() {
let client = Client::tracked(rocket_with_forms_limit(4)).unwrap(); let client = Client::debug(rocket_with_forms_limit(4)).unwrap();
let response = client.post("/") let response = client.post("/")
.body("value=Hello+world") .body("value=Hello+world")
.header(ContentType::Form) .header(ContentType::Form)
@ -57,7 +54,7 @@ mod limits_tests {
#[test] #[test]
fn contracted() { fn contracted() {
let client = Client::tracked(rocket_with_forms_limit(10)).unwrap(); let client = Client::debug(rocket_with_forms_limit(10)).unwrap();
let response = client.post("/") let response = client.post("/")
.body("value=Hello+world") .body("value=Hello+world")
.header(ContentType::Form) .header(ContentType::Form)

View File

@ -10,5 +10,5 @@ impl Drop for SpawnBlockingOnDrop {
#[test] #[test]
fn test_access_runtime_in_state_drop() { fn test_access_runtime_in_state_drop() {
Client::tracked(rocket::ignite().manage(SpawnBlockingOnDrop)).unwrap(); Client::debug(rocket::ignite().manage(SpawnBlockingOnDrop)).unwrap();
} }

View File

@ -54,7 +54,7 @@ mod local_request_content_type_tests {
#[test] #[test]
fn has_no_ct() { fn has_no_ct() {
let client = Client::tracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let req = client.post("/"); let req = client.post("/");
assert_eq!(req.clone().dispatch().into_string(), Some("Absent".to_string())); assert_eq!(req.clone().dispatch().into_string(), Some("Absent".to_string()));
@ -69,7 +69,7 @@ mod local_request_content_type_tests {
#[test] #[test]
fn has_ct() { fn has_ct() {
let client = Client::tracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let req = client.post("/").header(ContentType::JSON); let req = client.post("/").header(ContentType::JSON);
assert_eq!(req.clone().dispatch().into_string(), Some("Present".to_string())); assert_eq!(req.clone().dispatch().into_string(), Some("Present".to_string()));

View File

@ -20,7 +20,7 @@ mod tests {
fn private_cookie_is_returned() { fn private_cookie_is_returned() {
let rocket = rocket::ignite().mount("/", routes![return_private_cookie]); let rocket = rocket::ignite().mount("/", routes![return_private_cookie]);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let req = client.get("/").private_cookie(Cookie::new("cookie_name", "cookie_value")); let req = client.get("/").private_cookie(Cookie::new("cookie_name", "cookie_value"));
let response = req.dispatch(); let response = req.dispatch();
@ -32,7 +32,7 @@ mod tests {
fn regular_cookie_is_not_returned() { fn regular_cookie_is_not_returned() {
let rocket = rocket::ignite().mount("/", routes![return_private_cookie]); let rocket = rocket::ignite().mount("/", routes![return_private_cookie]);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let req = client.get("/").cookie(Cookie::new("cookie_name", "cookie_value")); let req = client.get("/").cookie(Cookie::new("cookie_name", "cookie_value"));
let response = req.dispatch(); let response = req.dispatch();

View File

@ -28,7 +28,7 @@ mod many_cookie_jars_tests {
#[test] #[test]
fn test_mutli_add() { fn test_mutli_add() {
let client = Client::tracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let response = client.post("/").dispatch(); let response = client.post("/").dispatch();
let cookies = response.cookies(); let cookies = response.cookies();
assert_eq!(cookies.iter().count(), 2); assert_eq!(cookies.iter().count(), 2);
@ -38,7 +38,7 @@ mod many_cookie_jars_tests {
#[test] #[test]
fn test_mutli_get() { fn test_mutli_get() {
let client = Client::tracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let response = client.get("/") let response = client.get("/")
.cookie(Cookie::new("a", "a_val")) .cookie(Cookie::new("a", "a_val"))
.cookie(Cookie::new("b", "hi!")) .cookie(Cookie::new("b", "hi!"))

View File

@ -30,7 +30,7 @@ mod mapped_base_tests {
#[test] #[test]
fn only_prefix() { fn only_prefix() {
let client = Client::tracked(super::rocket()).unwrap(); let client = Client::debug(super::rocket()).unwrap();
let response = client.get("/a/b/3").dispatch(); let response = client.get("/a/b/3").dispatch();
assert_eq!(response.into_string().unwrap(), "3"); assert_eq!(response.into_string().unwrap(), "3");
@ -44,7 +44,7 @@ mod mapped_base_tests {
#[test] #[test]
fn prefix_and_base() { fn prefix_and_base() {
let client = Client::tracked(super::rocket()).unwrap(); let client = Client::debug(super::rocket()).unwrap();
let response = client.get("/foo/a/b/23").dispatch(); let response = client.get("/foo/a/b/23").dispatch();
assert_eq!(response.into_string().unwrap(), "23"); assert_eq!(response.into_string().unwrap(), "23");

View File

@ -46,7 +46,7 @@ mod nested_fairing_attaches_tests {
#[test] #[test]
fn test_counts() { fn test_counts() {
let client = Client::tracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let response = client.get("/").dispatch(); let response = client.get("/").dispatch();
assert_eq!(response.into_string(), Some("1, 1".into())); assert_eq!(response.into_string(), Some("1, 1".into()));

View File

@ -5,7 +5,7 @@ use rocket::futures::channel::oneshot;
#[rocket::async_test] #[rocket::async_test]
async fn on_launch_fairing_can_inspect_port() { async fn on_launch_fairing_can_inspect_port() {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let rocket = rocket::custom(Config { port: 0, ..Default::default() }) let rocket = rocket::custom(Config { port: 0, ..Config::debug_default() })
.attach(AdHoc::on_launch("Send Port -> Channel", move |rocket| { .attach(AdHoc::on_launch("Send Port -> Channel", move |rocket| {
tx.send(rocket.config().port).unwrap(); tx.send(rocket.config().port).unwrap();
})); }));

View File

@ -32,7 +32,7 @@ fn rocket() -> Rocket {
#[test] #[test]
fn catches_route_panic() { fn catches_route_panic() {
let client = Client::untracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let response = client.get("/panic").dispatch(); let response = client.get("/panic").dispatch();
assert_eq!(response.status(), Status::InternalServerError); assert_eq!(response.status(), Status::InternalServerError);
assert_eq!(response.into_string().unwrap(), "Hey, sorry! :("); assert_eq!(response.into_string().unwrap(), "Hey, sorry! :(");
@ -40,7 +40,7 @@ fn catches_route_panic() {
} }
#[test] #[test]
fn catches_catcher_panic() { fn catches_catcher_panic() {
let client = Client::untracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let response = client.get("/noroute").dispatch(); let response = client.get("/noroute").dispatch();
assert_eq!(response.status(), Status::InternalServerError); assert_eq!(response.status(), Status::InternalServerError);
assert_eq!(response.into_string().unwrap(), "Hey, sorry! :("); assert_eq!(response.into_string().unwrap(), "Hey, sorry! :(");
@ -49,7 +49,7 @@ fn catches_catcher_panic() {
#[test] #[test]
fn catches_double_panic() { fn catches_double_panic() {
let rocket = rocket().register(catchers![double_panic]); let rocket = rocket().register(catchers![double_panic]);
let client = Client::untracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.get("/noroute").dispatch(); let response = client.get("/noroute").dispatch();
assert_eq!(response.status(), Status::InternalServerError); assert_eq!(response.status(), Status::InternalServerError);
assert!(!response.into_string().unwrap().contains(":(")); assert!(!response.into_string().unwrap().contains(":("));

View File

@ -35,7 +35,7 @@ mod tests {
macro_rules! check_dispatch { macro_rules! check_dispatch {
($mount:expr, $ct:expr, $body:expr) => ( ($mount:expr, $ct:expr, $body:expr) => (
let client = Client::tracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let mut req = client.post($mount); let mut req = client.post($mount);
let ct: Option<ContentType> = $ct; let ct: Option<ContentType> = $ct;
if let Some(ct) = ct { if let Some(ct) = ct {

View File

@ -14,7 +14,7 @@ mod tests {
#[test] #[test]
fn error_catcher_redirect() { fn error_catcher_redirect() {
let client = Client::tracked(rocket::ignite().register(catchers![not_found])).unwrap(); let client = Client::debug(rocket::ignite().register(catchers![not_found])).unwrap();
let response = client.get("/unknown").dispatch(); let response = client.get("/unknown").dispatch();
let location: Vec<_> = response.headers().get("location").collect(); let location: Vec<_> = response.headers().get("location").collect();

View File

@ -22,7 +22,7 @@ fn rocket() -> rocket::Rocket {
#[test] #[test]
fn check_fairing_changes_content_type() { fn check_fairing_changes_content_type() {
let client = Client::untracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let response = client.post("/").header(ContentType::PNG).dispatch(); let response = client.post("/").header(ContentType::PNG).dispatch();
assert_eq!(response.into_string().unwrap(), "other"); assert_eq!(response.into_string().unwrap(), "other");

View File

@ -24,7 +24,7 @@ mod route_guard_tests {
.mount("/first", routes![files]) .mount("/first", routes![files])
.mount("/second", routes![files]); .mount("/second", routes![files]);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
assert_path(&client, "/first/some/path"); assert_path(&client, "/first/some/path");
assert_path(&client, "/second/some/path"); assert_path(&client, "/second/some/path");
assert_path(&client, "/first/second/b/c"); assert_path(&client, "/first/second/b/c");

View File

@ -36,7 +36,7 @@ mod tests {
let rocket = rocket::ignite() let rocket = rocket::ignite()
.mount("/", routes![test, two, one_two, none, dual]) .mount("/", routes![test, two, one_two, none, dual])
.mount("/point", routes![test, two, one_two, dual]); .mount("/point", routes![test, two, one_two, dual]);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
// We construct a path that matches each of the routes above. We ensure the // We construct a path that matches each of the routes above. We ensure the
// prefix is stripped, confirming that dynamic segments are working. // prefix is stripped, confirming that dynamic segments are working.

View File

@ -15,7 +15,7 @@ mod test_session_cookies {
#[test] #[test]
fn session_cookie_is_session() { fn session_cookie_is_session() {
let rocket = rocket::ignite().mount("/", rocket::routes![index]); let rocket = rocket::ignite().mount("/", rocket::routes![index]);
let client = Client::tracked(rocket).unwrap(); let client = Client::debug(rocket).unwrap();
let response = client.get("/").dispatch(); let response = client.get("/").dispatch();
let cookie = response.cookies().get_private("key").unwrap(); let cookie = response.cookies().get_private("key").unwrap();

View File

@ -25,7 +25,7 @@ mod strict_and_lenient_forms_tests {
const FIELD_VALUE: &str = "just_some_value"; const FIELD_VALUE: &str = "just_some_value";
fn client() -> Client { fn client() -> Client {
Client::tracked(rocket::ignite().mount("/", routes![strict, lenient])).unwrap() Client::debug_with(routes![strict, lenient]).unwrap()
} }
#[test] #[test]

View File

@ -3,7 +3,7 @@ use rocket::local::blocking::Client;
#[test] #[test]
fn test_local_request_clone_soundness() { fn test_local_request_clone_soundness() {
let client = Client::tracked(rocket::ignite()).unwrap(); let client = Client::debug_with(vec![]).unwrap();
// creates two LocalRequest instances that shouldn't share the same req // creates two LocalRequest instances that shouldn't share the same req
let r1 = client.get("/").header(Header::new("key", "val1")); let r1 = client.get("/").header(Header::new("key", "val1"));

View File

@ -19,7 +19,8 @@ mod many_cookie_jars_tests {
use rocket::http::Status; use rocket::http::Status;
fn rocket() -> rocket::Rocket { fn rocket() -> rocket::Rocket {
rocket::ignite().mount("/", routes![add, get]) rocket::custom(rocket::Config::debug_default())
.mount("/", routes![add, get])
} }
#[test] #[test]

View File

@ -33,7 +33,7 @@ mod tests {
#[test] #[test]
fn uri_percent_encoding_redirect() { fn uri_percent_encoding_redirect() {
let expected_location = vec!["/hello/John%5B%5D%7C%5C%25@%5E"]; let expected_location = vec!["/hello/John%5B%5D%7C%5C%25@%5E"];
let client = Client::tracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let response = client.get("/raw").dispatch(); let response = client.get("/raw").dispatch();
let location: Vec<_> = response.headers().get("location").collect(); let location: Vec<_> = response.headers().get("location").collect();
@ -48,7 +48,7 @@ mod tests {
#[test] #[test]
fn uri_percent_encoding_get() { fn uri_percent_encoding_get() {
let client = Client::tracked(rocket()).unwrap(); let client = Client::debug(rocket()).unwrap();
let name = Uri::percent_encode(NAME); let name = Uri::percent_encode(NAME);
let response = client.get(format!("/hello/{}", name)).dispatch(); let response = client.get(format!("/hello/{}", name)).dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);

View File

@ -47,6 +47,7 @@ CORE_CODEGEN_ROOT=$(relative "core/codegen") || exit $?
CORE_HTTP_ROOT=$(relative "core/http") || exit $? CORE_HTTP_ROOT=$(relative "core/http") || exit $?
CONTRIB_LIB_ROOT=$(relative "contrib/lib") || exit $? CONTRIB_LIB_ROOT=$(relative "contrib/lib") || exit $?
CONTRIB_CODEGEN_ROOT=$(relative "contrib/codegen") || exit $? CONTRIB_CODEGEN_ROOT=$(relative "contrib/codegen") || exit $?
GUIDE_TESTS_ROOT=$(relative "site/tests") || exit $?
# Root of infrastructure directories. # Root of infrastructure directories.
EXAMPLES_DIR=$(relative "examples") || exit $? EXAMPLES_DIR=$(relative "examples") || exit $?
@ -97,6 +98,7 @@ function print_environment() {
echo " CORE_HTTP_ROOT: ${CORE_HTTP_ROOT}" echo " CORE_HTTP_ROOT: ${CORE_HTTP_ROOT}"
echo " CONTRIB_LIB_ROOT: ${CONTRIB_LIB_ROOT}" echo " CONTRIB_LIB_ROOT: ${CONTRIB_LIB_ROOT}"
echo " CONTRIB_CODEGEN_ROOT: ${CONTRIB_CODEGEN_ROOT}" echo " CONTRIB_CODEGEN_ROOT: ${CONTRIB_CODEGEN_ROOT}"
echo " GUIDE_TESTS_ROOT: ${GUIDE_TESTS_ROOT}"
echo " EXAMPLES_DIR: ${EXAMPLES_DIR}" echo " EXAMPLES_DIR: ${EXAMPLES_DIR}"
echo " DOC_DIR: ${DOC_DIR}" echo " DOC_DIR: ${DOC_DIR}"
echo " ALL_PROJECT_DIRS: ${ALL_PROJECT_DIRS[*]}" echo " ALL_PROJECT_DIRS: ${ALL_PROJECT_DIRS[*]}"

View File

@ -23,11 +23,10 @@ if ! [ -z "$(git status --porcelain)" ]; then
fi fi
# Ensure everything passes before trying to publish. # Ensure everything passes before trying to publish.
echo ":::: Running test suite..." echo ":::: Running complete test suite..."
cargo clean cargo clean
bash "${SCRIPT_DIR}/test.sh" bash "${SCRIPT_DIR}/test.sh" --all
bash "${SCRIPT_DIR}/test.sh" --contrib bash "${SCRIPT_DIR}/test.sh" --all --release
bash "${SCRIPT_DIR}/test.sh" --release
# Temporarily remove dev-dependencies so crates.io verifies. # Temporarily remove dev-dependencies so crates.io verifies.
echo ":::: Stripping [dev-dependencies]..." echo ":::: Stripping [dev-dependencies]..."

View File

@ -10,9 +10,6 @@ export PATH=${HOME}/.cargo/bin:${PATH}
export CARGO_INCREMENTAL=0 export CARGO_INCREMENTAL=0
CARGO="cargo" CARGO="cargo"
# We set a `cfg` so that a missing `secret_key` doesn't abort tests.
export RUSTFLAGS="--cfg rocket_unsafe_secret_key"
# Checks that the versions for Cargo projects $@ all match # Checks that the versions for Cargo projects $@ all match
function check_versions_match() { function check_versions_match() {
local last_version="" local last_version=""
@ -54,40 +51,7 @@ function ensure_trailing_whitespace_free() {
fi fi
} }
if [[ $1 == +* ]]; then function test_contrib() {
CARGO="$CARGO $1"
shift
fi
# The kind of test we'll be running.
TEST_KIND="all"
if [[ "$1" == "--contrib" ]] || [[ "$1" == "--core" ]]; then
TEST_KIND=${1#"--"}
shift
fi
echo ":: Preparing. Environment is..."
print_environment
echo " CARGO: $CARGO"
echo " RUSTFLAGS: $RUSTFLAGS"
echo " EXTRA FLAGS: $@"
echo ":: Ensuring all crate versions match..."
check_versions_match "${ALL_PROJECT_DIRS[@]}"
echo ":: Checking for tabs..."
ensure_tab_free
echo ":: Checking for trailing whitespace..."
ensure_trailing_whitespace_free
echo ":: Updating dependencies..."
if ! $CARGO update ; then
echo " WARNING: Update failed! Proceeding with possibly outdated deps..."
fi
if [ $TEST_KIND = "contrib" ]; then
FEATURES=( FEATURES=(
json json
msgpack msgpack
@ -106,18 +70,21 @@ if [ $TEST_KIND = "contrib" ]; then
gzip_compression gzip_compression
) )
echo ":: Building and testing contrib [default]..."
pushd "${CONTRIB_LIB_ROOT}" > /dev/null 2>&1 pushd "${CONTRIB_LIB_ROOT}" > /dev/null 2>&1
echo ":: Building and testing contrib [default]..." $CARGO test $@
$CARGO test $@
for feature in "${FEATURES[@]}"; do for feature in "${FEATURES[@]}"; do
echo ":: Building and testing contrib [${feature}]..." echo ":: Building and testing contrib [${feature}]..."
$CARGO test --no-default-features --features "${feature}" $@ $CARGO test --no-default-features --features "${feature}" $@
done done
popd > /dev/null 2>&1 popd > /dev/null 2>&1
elif [ $TEST_KIND = "core" ]; then }
function test_core() {
FEATURES=( FEATURES=(
secrets secrets
tls tls
@ -125,16 +92,97 @@ elif [ $TEST_KIND = "core" ]; then
pushd "${CORE_LIB_ROOT}" > /dev/null 2>&1 pushd "${CORE_LIB_ROOT}" > /dev/null 2>&1
echo ":: Building and testing core [no features]..." echo ":: Building and testing core [no features]..."
$CARGO test --no-default-features $@ $CARGO test --no-default-features $@
for feature in "${FEATURES[@]}"; do for feature in "${FEATURES[@]}"; do
echo ":: Building and testing core [${feature}]..." echo ":: Building and testing core [${feature}]..."
$CARGO test --no-default-features --features "${feature}" $@ $CARGO test --no-default-features --features "${feature}" $@
done done
popd > /dev/null 2>&1 popd > /dev/null 2>&1
else }
echo ":: Building and testing libraries..."
$CARGO test --all-features --all $@ function test_examples() {
for dir in $(find "${EXAMPLES_DIR}" -maxdepth 1 -mindepth 1 -type d); do
echo ":: Building and testing example [${dir#"${EXAMPLES_DIR}/"}]..."
pushd "${dir}" > /dev/null 2>&1
$CARGO test $@
popd > /dev/null 2>&1
done
}
function test_guide() {
echo ":: Building and testing guide..."
pushd "${GUIDE_TESTS_ROOT}" > /dev/null 2>&1
$CARGO test $@
popd > /dev/null 2>&1
}
function test_default() {
for project in "${ALL_PROJECT_DIRS[@]}"; do
echo ":: Building and testing ${project#"${PROJECT_ROOT}/"}..."
pushd "${project}" > /dev/null 2>&1
$CARGO test --all-features $@
popd > /dev/null 2>&1
done
}
if [[ $1 == +* ]]; then
CARGO="$CARGO $1"
shift
fi fi
# The kind of test we'll be running.
TEST_KIND="default"
KINDS=("contrib" "core" "examples" "guide" "all")
if [[ " ${KINDS[@]} " =~ " ${1#"--"} " ]]; then
TEST_KIND=${1#"--"}
shift
fi
echo ":: Preparing. Environment is..."
print_environment
echo " CARGO: $CARGO"
echo " EXTRA FLAGS: $@"
echo ":: Ensuring all crate versions match..."
check_versions_match "${ALL_PROJECT_DIRS[@]}"
echo ":: Checking for tabs..."
ensure_tab_free
echo ":: Checking for trailing whitespace..."
ensure_trailing_whitespace_free
echo ":: Updating dependencies..."
if ! $CARGO update ; then
echo " WARNING: Update failed! Proceeding with possibly outdated deps..."
fi
case $TEST_KIND in
contrib) test_contrib $@ ;;
core) test_core $@ ;;
examples) test_examples $@ ;;
guide) test_guide $@ ;;
default)
test_examples $@ & examples=$!
test_default $@ & default=$!
test_guide $@ & guide=$!
wait $examples && wait $default && wait $guide
;;
all)
test_core $@ & core=$!
test_contrib $@ & contrib=$!
test_examples $@ & examples=$!
test_default $@ & default=$!
test_guide $@ & guide=$!
wait $core && wait $contrib && wait $examples && wait $default && wait $guide
;;
esac

View File

@ -14,14 +14,14 @@ instance. Usage is straightforward:
1. Construct a `Rocket` instance that represents the application. 1. Construct a `Rocket` instance that represents the application.
```rust ```rust,no_run
let rocket = rocket::ignite(); let rocket = rocket::ignite();
# let _ = rocket; # let _ = rocket;
``` ```
2. Construct a `Client` using the `Rocket` instance. 2. Construct a `Client` using the `Rocket` instance.
```rust ```rust,no_run
# use rocket::local::blocking::Client; # use rocket::local::blocking::Client;
# let rocket = rocket::ignite(); # let rocket = rocket::ignite();
let client = Client::tracked(rocket).unwrap(); let client = Client::tracked(rocket).unwrap();
@ -30,7 +30,7 @@ instance. Usage is straightforward:
3. Construct requests using the `Client` instance. 3. Construct requests using the `Client` instance.
```rust ```rust,no_run
# use rocket::local::blocking::Client; # use rocket::local::blocking::Client;
# let rocket = rocket::ignite(); # let rocket = rocket::ignite();
# let client = Client::tracked(rocket).unwrap(); # let client = Client::tracked(rocket).unwrap();
@ -40,7 +40,7 @@ instance. Usage is straightforward:
4. Dispatch the request to retrieve the response. 4. Dispatch the request to retrieve the response.
```rust ```rust,no_run
# use rocket::local::blocking::Client; # use rocket::local::blocking::Client;
# let rocket = rocket::ignite(); # let rocket = rocket::ignite();
# let client = Client::tracked(rocket).unwrap(); # let client = Client::tracked(rocket).unwrap();
@ -97,11 +97,11 @@ These methods are typically used in combination with the `assert_eq!` or
# .finalize() # .finalize()
# } # }
use rocket::local::blocking::Client; # use rocket::local::blocking::Client;
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Status};
let rocket = rocket::ignite().mount("/", routes![hello]); # let rocket = rocket::ignite().mount("/", routes![hello]);
let client = Client::tracked(rocket).expect("valid rocket instance"); # let client = Client::debug(rocket).expect("valid rocket instance");
let mut response = client.get("/").dispatch(); let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
@ -165,7 +165,9 @@ To test our "Hello, world!" application, we create a `Client` for our
testing: we _want_ our tests to panic when something goes wrong. testing: we _want_ our tests to panic when something goes wrong.
```rust ```rust
# fn rocket() -> rocket::Rocket { rocket::ignite() } # fn rocket() -> rocket::Rocket {
# rocket::ignite().reconfigure(rocket::Config::debug_default())
# }
# use rocket::local::blocking::Client; # use rocket::local::blocking::Client;
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::tracked(rocket()).expect("valid rocket instance");
@ -175,7 +177,9 @@ Then, we create a new `GET /` request and dispatch it, getting back our
application's response: application's response:
```rust ```rust
# fn rocket() -> rocket::Rocket { rocket::ignite() } # fn rocket() -> rocket::Rocket {
# rocket::ignite().reconfigure(rocket::Config::debug_default())
# }
# use rocket::local::blocking::Client; # use rocket::local::blocking::Client;
# let client = Client::tracked(rocket()).expect("valid rocket instance"); # let client = Client::tracked(rocket()).expect("valid rocket instance");
let mut response = client.get("/").dispatch(); let mut response = client.get("/").dispatch();
@ -199,7 +203,7 @@ We do this by checking the `Response` object directly:
use rocket::http::{ContentType, Status}; use rocket::http::{ContentType, Status};
# #
# let rocket = rocket::ignite().mount("/", routes![hello]); # let rocket = rocket::ignite().mount("/", routes![hello]);
# let client = Client::tracked(rocket).expect("valid rocket instance"); # let client = Client::debug(rocket).expect("valid rocket instance");
# let mut response = client.get("/").dispatch(); # let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
@ -232,7 +236,10 @@ mod test {
#[test] #[test]
# */ pub # */ pub
fn hello_world() { fn hello_world() {
# /*
let client = Client::tracked(rocket()).expect("valid rocket instance"); let client = Client::tracked(rocket()).expect("valid rocket instance");
# */
# let client = Client::debug(rocket()).expect("valid rocket instance");
let mut response = client.get("/").dispatch(); let mut response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.into_string(), Some("Hello, world!".into())); assert_eq!(response.into_string(), Some("Hello, world!".into()));

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rocket_guide_tests" name = "rocket_guide_tests"
version = "0.0.0" version = "0.5.0-dev"
workspace = "../../" workspace = "../../"
edition = "2018" edition = "2018"
publish = false publish = false

View File

@ -50,5 +50,5 @@ macro_rules! assert_form_parses_ok {
} }
pub fn client(routes: Vec<rocket::Route>) -> rocket::local::blocking::Client { pub fn client(routes: Vec<rocket::Route>) -> rocket::local::blocking::Client {
rocket::local::blocking::Client::debug("/", routes).unwrap() rocket::local::blocking::Client::debug_with(routes).unwrap()
} }