diff --git a/benchmarks/src/routing.rs b/benchmarks/src/routing.rs index 3d57b40974..37b9273b3b 100644 --- a/benchmarks/src/routing.rs +++ b/benchmarks/src/routing.rs @@ -1,5 +1,3 @@ -use std::collections::hash_set::HashSet; - use criterion::{criterion_group, Criterion}; use rocket::{route, config, Request, Data, Route, Config}; @@ -80,12 +78,12 @@ fn generate_matching_requests<'c>(client: &'c Client, routes: &[Route]) -> Vec) -> Client { let config = Config { profile: Config::RELEASE_PROFILE, - // log_level: rocket::config::LogLevel::Off, + log_level: None, cli_colors: config::CliColors::Never, shutdown: config::ShutdownConfig { ctrlc: false, #[cfg(unix)] - signals: HashSet::new(), + signals: std::collections::hash_set::HashSet::new(), ..Default::default() }, ..Default::default() diff --git a/contrib/db_pools/lib/Cargo.toml b/contrib/db_pools/lib/Cargo.toml index 74ba232d9a..d2b91081f8 100644 --- a/contrib/db_pools/lib/Cargo.toml +++ b/contrib/db_pools/lib/Cargo.toml @@ -21,9 +21,9 @@ workspace = true deadpool_postgres = ["deadpool-postgres", "deadpool"] deadpool_redis = ["deadpool-redis", "deadpool"] # sqlx features -sqlx_mysql = ["sqlx", "sqlx/mysql"] -sqlx_postgres = ["sqlx", "sqlx/postgres"] -sqlx_sqlite = ["sqlx", "sqlx/sqlite"] +sqlx_mysql = ["sqlx", "sqlx/mysql", "log"] +sqlx_postgres = ["sqlx", "sqlx/postgres", "log"] +sqlx_sqlite = ["sqlx", "sqlx/sqlite", "log"] sqlx_macros = ["sqlx/macros"] # diesel features diesel_postgres = ["diesel-async/postgres", "diesel-async/deadpool", "diesel", "deadpool_09"] @@ -86,6 +86,11 @@ default-features = false features = ["runtime-tokio-rustls"] optional = true +[dependencies.log] +version = "0.4" +default-features = false +optional = true + [dev-dependencies.rocket] path = "../../../core/lib" default-features = false diff --git a/contrib/db_pools/lib/src/pool.rs b/contrib/db_pools/lib/src/pool.rs index 0c0381b383..73219a6d5e 100644 --- a/contrib/db_pools/lib/src/pool.rs +++ b/contrib/db_pools/lib/src/pool.rs @@ -269,7 +269,7 @@ mod deadpool_old { mod sqlx { use sqlx::ConnectOptions; use super::{Duration, Error, Config, Figment}; - // use rocket::config::LogLevel; + use rocket::tracing::level_filters::LevelFilter; type Options = <::Connection as sqlx::Connection>::Options; @@ -301,12 +301,21 @@ mod sqlx { specialize(&mut opts, &config); opts = opts.disable_statement_logging(); - // if let Ok(level) = figment.extract_inner::(rocket::Config::LOG_LEVEL) { - // if !matches!(level, LogLevel::Normal | LogLevel::Off) { - // opts = opts.log_statements(level.into()) - // .log_slow_statements(level.into(), Duration::default()); - // } - // } + if let Ok(value) = figment.find_value(rocket::Config::LOG_LEVEL) { + if let Some(level) = value.as_str().and_then(|v| v.parse().ok()) { + let log_level = match level { + LevelFilter::OFF => log::LevelFilter::Off, + LevelFilter::ERROR => log::LevelFilter::Error, + LevelFilter::WARN => log::LevelFilter::Warn, + LevelFilter::INFO => log::LevelFilter::Info, + LevelFilter::DEBUG => log::LevelFilter::Debug, + LevelFilter::TRACE => log::LevelFilter::Trace, + }; + + opts = opts.log_statements(log_level) + .log_slow_statements(log_level, Duration::default()); + } + } sqlx::pool::PoolOptions::new() .max_connections(config.max_connections as u32) diff --git a/contrib/dyn_templates/src/engine/minijinja.rs b/contrib/dyn_templates/src/engine/minijinja.rs index 461b3209cc..121aafbd29 100644 --- a/contrib/dyn_templates/src/engine/minijinja.rs +++ b/contrib/dyn_templates/src/engine/minijinja.rs @@ -42,20 +42,20 @@ impl Engine for Environment<'static> { Some(env) } - fn render(&self, name: &str, context: C) -> Option { - let Ok(template) = self.get_template(name) else { - error!("Minijinja template '{name}' was not found."); + fn render(&self, template: &str, context: C) -> Option { + let Ok(templ) = self.get_template(template) else { + error!(template, "requested template does not exist"); return None; }; - match template.render(context) { + match templ.render(context) { Ok(result) => Some(result), Err(e) => { - error_span!("Error rendering Minijinja template '{name}': {e}" => { - let mut error = &e as &dyn std::error::Error; - while let Some(source) = error.source() { - error_!("caused by: {source}"); - error = source; + span_error!("templating", template, "failed to render Minijinja template" => { + let mut error = Some(&e as &dyn std::error::Error); + while let Some(err) = error { + error!("{err}"); + error = err.source(); } }); diff --git a/contrib/dyn_templates/src/engine/tera.rs b/contrib/dyn_templates/src/engine/tera.rs index af548b7bd7..33817c38f4 100644 --- a/contrib/dyn_templates/src/engine/tera.rs +++ b/contrib/dyn_templates/src/engine/tera.rs @@ -21,7 +21,7 @@ impl Engine for Tera { // Finally try to tell Tera about all of the templates. if let Err(e) = tera.add_template_files(files) { - error_span!("Tera templating initialization failed" => { + span_error!("templating", "Tera templating initialization failed" => { let mut error = Some(&e as &dyn Error); while let Some(err) = error { error!("{err}"); @@ -48,7 +48,7 @@ impl Engine for Tera { match Tera::render(self, template, &tera_ctx) { Ok(string) => Some(string), Err(e) => { - error_span!("failed to render Tera template {name}" [template] => { + span_error!("templating", template, "failed to render Tera template" => { let mut error = Some(&e as &dyn Error); while let Some(err) = error { error!("{err}"); diff --git a/contrib/dyn_templates/src/fairing.rs b/contrib/dyn_templates/src/fairing.rs index 4079de8a53..8decf1de1e 100644 --- a/contrib/dyn_templates/src/fairing.rs +++ b/contrib/dyn_templates/src/fairing.rs @@ -1,6 +1,7 @@ use rocket::{Rocket, Build, Orbit}; use rocket::fairing::{self, Fairing, Info, Kind}; use rocket::figment::{Source, value::magic::RelativePathBuf}; +use rocket::trace::Trace; use crate::context::{Callback, Context, ContextManager}; use crate::template::DEFAULT_TEMPLATE_DIR; @@ -40,7 +41,7 @@ impl Fairing for TemplateFairing { Ok(dir) => dir, Err(e) if e.missing() => DEFAULT_TEMPLATE_DIR.into(), Err(e) => { - rocket::config::pretty_print_error(e); + e.trace_error(); return Err(rocket); } }; @@ -57,7 +58,7 @@ impl Fairing for TemplateFairing { let cm = rocket.state::() .expect("Template ContextManager registered in on_ignite"); - info_span!("templating" => { + span_info!("templating" => { info!(directory = %Source::from(&*cm.context().root)); info!(engines = ?Engines::ENABLED_EXTENSIONS); }); diff --git a/contrib/dyn_templates/src/template.rs b/contrib/dyn_templates/src/template.rs index 3c92aa93f8..97a73b7b76 100644 --- a/contrib/dyn_templates/src/template.rs +++ b/contrib/dyn_templates/src/template.rs @@ -248,7 +248,7 @@ impl Template { })?; let value = self.value.map_err(|e| { - error_span!("template context failed to serialize" => e.trace_error()); + span_error!("templating", "template context failed to serialize" => e.trace_error()); Status::InternalServerError })?; diff --git a/contrib/sync_db_pools/lib/src/connection.rs b/contrib/sync_db_pools/lib/src/connection.rs index b5f030e54a..bd5c2b4b7c 100644 --- a/contrib/sync_db_pools/lib/src/connection.rs +++ b/contrib/sync_db_pools/lib/src/connection.rs @@ -68,7 +68,7 @@ impl ConnectionPool { let config = match Config::from(database, &rocket) { Ok(config) => config, Err(e) => { - error_span!("database configuration error" [database] => e.trace_error()); + span_error!("database configuration error", database => e.trace_error()); return Err(rocket); } }; @@ -82,7 +82,7 @@ impl ConnectionPool { _marker: PhantomData, })), Err(Error::Config(e)) => { - error_span!("database configuration error" [database] => e.trace_error()); + span_error!("database configuration error", database => e.trace_error()); Err(rocket) } Err(Error::Pool(reason)) => { diff --git a/core/codegen/src/attribute/catch/mod.rs b/core/codegen/src/attribute/catch/mod.rs index 09528c71e2..57c898a059 100644 --- a/core/codegen/src/attribute/catch/mod.rs +++ b/core/codegen/src/attribute/catch/mod.rs @@ -80,9 +80,10 @@ pub fn _catch( } #_catcher::StaticInfo { - name: stringify!(#user_catcher_fn_name), + name: ::core::stringify!(#user_catcher_fn_name), code: #status_code, handler: monomorphized_function, + location: (::core::file!(), ::core::line!(), ::core::column!()), } } diff --git a/core/codegen/src/attribute/entry/launch.rs b/core/codegen/src/attribute/entry/launch.rs index eb70ceffba..a97c5eaa5d 100644 --- a/core/codegen/src/attribute/entry/launch.rs +++ b/core/codegen/src/attribute/entry/launch.rs @@ -67,8 +67,7 @@ impl EntryAttr for Launch { // Always infer the type as `Rocket`. if let syn::ReturnType::Type(_, ref mut ty) = &mut f.sig.output { if let syn::Type::Infer(_) = &mut **ty { - let new = quote_spanned!(ty.span() => ::rocket::Rocket<::rocket::Build>); - *ty = syn::parse2(new).expect("path is type"); + *ty = syn::parse_quote_spanned!(ty.span() => ::rocket::Rocket<::rocket::Build>); } } @@ -105,8 +104,9 @@ impl EntryAttr for Launch { } let (vis, mut sig) = (&f.vis, f.sig.clone()); - sig.ident = syn::Ident::new("main", sig.ident.span()); - sig.output = syn::parse_quote!(-> #_ExitCode); + sig.ident = syn::Ident::new("main", f.sig.ident.span()); + let ret_ty = _ExitCode.respanned(ty.span()); + sig.output = syn::parse_quote_spanned!(ty.span() => -> #ret_ty); sig.asyncness = None; Ok(quote_spanned!(block.span() => diff --git a/core/codegen/src/attribute/route/mod.rs b/core/codegen/src/attribute/route/mod.rs index 7850e73efe..de05fa0dce 100644 --- a/core/codegen/src/attribute/route/mod.rs +++ b/core/codegen/src/attribute/route/mod.rs @@ -105,9 +105,10 @@ fn query_decls(route: &Route) -> Option { )* if !__e.is_empty() { - ::rocket::info_span!("query string failed to match route declaration" => { - for _err in __e { ::rocket::info!("{_err}"); } - }); + ::rocket::trace::span_info!("codegen", + "query string failed to match route declaration" => + { for _err in __e { ::rocket::trace::info!("{_err}"); } } + ); return #Outcome::Forward((#__data, #Status::UnprocessableEntity)); } @@ -120,18 +121,25 @@ fn query_decls(route: &Route) -> Option { fn request_guard_decl(guard: &Guard) -> TokenStream { let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty); define_spanned_export!(ty.span() => - __req, __data, _request, FromRequest, Outcome + __req, __data, _request, display_hack, FromRequest, Outcome ); quote_spanned! { ty.span() => let #ident: #ty = match <#ty as #FromRequest>::from_request(#__req).await { #Outcome::Success(__v) => __v, #Outcome::Forward(__e) => { - ::rocket::info!(type_name = stringify!(#ty), "guard forwarding"); + ::rocket::trace::info!(name: "forward", parameter = stringify!(#ident), + type_name = stringify!(#ty), status = __e.code, + "request guard forwarding"); + return #Outcome::Forward((#__data, __e)); }, + #[allow(unreachable_code)] #Outcome::Error((__c, __e)) => { - ::rocket::info!(type_name = stringify!(#ty), "guard failed: {__e:?}"); + ::rocket::trace::info!(name: "failure", parameter = stringify!(#ident), + type_name = stringify!(#ty), reason = %#display_hack!(__e), + "request guard failed"); + return #Outcome::Error(__c); } }; @@ -142,14 +150,14 @@ fn param_guard_decl(guard: &Guard) -> TokenStream { let (i, name, ty) = (guard.index, &guard.name, &guard.ty); define_spanned_export!(ty.span() => __req, __data, _None, _Some, _Ok, _Err, - Outcome, FromSegments, FromParam, Status + Outcome, FromSegments, FromParam, Status, display_hack ); // Returned when a dynamic parameter fails to parse. let parse_error = quote!({ - ::rocket::info!(name: "forward", - reason = %__error, parameter = #name, "type" = stringify!(#ty), - "parameter forwarding"); + ::rocket::trace::info!(name: "forward", parameter = #name, + type_name = stringify!(#ty), reason = %#display_hack!(__error), + "path guard forwarding"); #Outcome::Forward((#__data, #Status::UnprocessableEntity)) }); @@ -161,10 +169,11 @@ fn param_guard_decl(guard: &Guard) -> TokenStream { match #__req.routed_segment(#i) { #_Some(__s) => match <#ty as #FromParam>::from_param(__s) { #_Ok(__v) => __v, + #[allow(unreachable_code)] #_Err(__error) => return #parse_error, }, #_None => { - ::rocket::error!( + ::rocket::trace::error!( "Internal invariant broken: dyn param {} not found.\n\ Please report this to the Rocket issue tracker.\n\ https://github.com/rwf2/Rocket/issues", #i); @@ -176,6 +185,7 @@ fn param_guard_decl(guard: &Guard) -> TokenStream { true => quote_spanned! { ty.span() => match <#ty as #FromSegments>::from_segments(#__req.routed_segments(#i..)) { #_Ok(__v) => __v, + #[allow(unreachable_code)] #_Err(__error) => return #parse_error, } }, @@ -187,17 +197,24 @@ fn param_guard_decl(guard: &Guard) -> TokenStream { fn data_guard_decl(guard: &Guard) -> TokenStream { let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty); - define_spanned_export!(ty.span() => __req, __data, FromData, Outcome); + define_spanned_export!(ty.span() => __req, __data, display_hack, FromData, Outcome); quote_spanned! { ty.span() => let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await { #Outcome::Success(__d) => __d, #Outcome::Forward((__d, __e)) => { - ::rocket::info!(type_name = stringify!(#ty), "data guard forwarding"); + ::rocket::trace::info!(name: "forward", parameter = stringify!(#ident), + type_name = stringify!(#ty), status = __e.code, + "data guard forwarding"); + return #Outcome::Forward((__d, __e)); } + #[allow(unreachable_code)] #Outcome::Error((__c, __e)) => { - ::rocket::info!(type_name = stringify!(#ty), "data guard failed: {__e:?}"); + ::rocket::trace::info!(name: "failure", parameter = stringify!(#ident), + type_name = stringify!(#ty), reason = %#display_hack!(__e), + "data guard failed"); + return #Outcome::Error(__c); } }; @@ -232,7 +249,7 @@ fn internal_uri_macro_decl(route: &Route) -> TokenStream { /// Rocket generated URI macro. macro_rules! #inner_macro_name { ($($token:tt)*) => {{ - rocket::rocket_internal_uri!(#route_uri, (#(#uri_args),*), $($token)*) + ::rocket::rocket_internal_uri!(#route_uri, (#(#uri_args),*), $($token)*) }}; } @@ -385,6 +402,7 @@ fn codegen_route(route: Route) -> Result { format: #format, rank: #rank, sentinels: #sentinels, + location: (::core::file!(), ::core::line!(), ::core::column!()), } } diff --git a/core/codegen/src/exports.rs b/core/codegen/src/exports.rs index 323576aeb6..50470b46b9 100644 --- a/core/codegen/src/exports.rs +++ b/core/codegen/src/exports.rs @@ -86,6 +86,7 @@ define_exported_paths! { _Vec => ::std::vec::Vec, _Cow => ::std::borrow::Cow, _ExitCode => ::std::process::ExitCode, + display_hack => ::rocket::error::display_hack, BorrowMut => ::std::borrow::BorrowMut, Outcome => ::rocket::outcome::Outcome, FromForm => ::rocket::form::FromForm, diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs index 8dda938862..39401f1c5d 100644 --- a/core/codegen/src/lib.rs +++ b/core/codegen/src/lib.rs @@ -1328,7 +1328,7 @@ pub fn catchers(input: TokenStream) -> TokenStream { /// assert_eq!(bob2.to_string(), "/person/Bob%20Smith"); /// /// #[get("/person/")] -/// fn ok(age: Result) { } +/// fn ok(age: Result) { } /// /// let kid1 = uri!(ok(age = 10)); /// let kid2 = uri!(ok(12)); diff --git a/core/codegen/tests/typed-uris.rs b/core/codegen/tests/typed-uris.rs index aabe94e536..0dbcc502a9 100644 --- a/core/codegen/tests/typed-uris.rs +++ b/core/codegen/tests/typed-uris.rs @@ -709,3 +709,19 @@ fn test_vec_in_query() { uri!(h(v = &[1, 2, 3][..])) => "/?v=%01%02%03", } } + +#[test] +fn test_either() { + use rocket::either::{Either, Left, Right}; + + #[get("/<_foo>")] + fn f(_foo: Either) { } + + assert_uri_eq! { + uri!(f(Left::(123))) => "/123", + uri!(f(_foo = Left::(710))) => "/710", + + uri!(f(Right::("hello world"))) => "/hello%20world", + uri!(f(_foo = Right::("bye?"))) => "/bye%3F", + } +} diff --git a/core/codegen/tests/ui-fail-nightly/async-entry.stderr b/core/codegen/tests/ui-fail-nightly/async-entry.stderr index a1cba990e5..51c80d63ba 100644 --- a/core/codegen/tests/ui-fail-nightly/async-entry.stderr +++ b/core/codegen/tests/ui-fail-nightly/async-entry.stderr @@ -114,14 +114,6 @@ error[E0728]: `await` is only allowed inside `async` functions and blocks 73 | let _ = rocket::build().launch().await; | ^^^^^ only allowed inside `async` functions and blocks -error[E0728]: `await` is only allowed inside `async` functions and blocks - --> tests/ui-fail-nightly/async-entry.rs:73:42 - | -72 | fn rocket() -> _ { - | ----------- this is not `async` -73 | let _ = rocket::build().launch().await; - | ^^^^^ only allowed inside `async` functions and blocks - error[E0277]: `main` has invalid return type `Rocket` --> tests/ui-fail-nightly/async-entry.rs:94:20 | diff --git a/core/codegen/tests/ui-fail-nightly/route-type-errors.stderr b/core/codegen/tests/ui-fail-nightly/route-type-errors.stderr index d0b2538391..d433134fe8 100644 --- a/core/codegen/tests/ui-fail-nightly/route-type-errors.stderr +++ b/core/codegen/tests/ui-fail-nightly/route-type-errors.stderr @@ -5,14 +5,14 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied | ^ the trait `FromParam<'_>` is not implemented for `Q` | = help: the following other types implement trait `FromParam<'a>`: - &'a str - IpAddr - Ipv4Addr - Ipv6Addr - NonZero - NonZero - NonZero - NonZero + <&'a str as FromParam<'a>> + > + > + > + as FromParam<'a>> + as FromParam<'a>> + as FromParam<'a>> + as FromParam<'a>> and $N others error[E0277]: the trait bound `Q: FromSegments<'_>` is not satisfied @@ -104,14 +104,14 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied | ^ the trait `FromParam<'_>` is not implemented for `Q` | = help: the following other types implement trait `FromParam<'a>`: - &'a str - IpAddr - Ipv4Addr - Ipv6Addr - NonZero - NonZero - NonZero - NonZero + <&'a str as FromParam<'a>> + > + > + > + as FromParam<'a>> + as FromParam<'a>> + as FromParam<'a>> + as FromParam<'a>> and $N others error[E0277]: the trait bound `Q: FromRequest<'_>` is not satisfied @@ -138,14 +138,14 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied | ^ the trait `FromParam<'_>` is not implemented for `Q` | = help: the following other types implement trait `FromParam<'a>`: - &'a str - IpAddr - Ipv4Addr - Ipv6Addr - NonZero - NonZero - NonZero - NonZero + <&'a str as FromParam<'a>> + > + > + > + as FromParam<'a>> + as FromParam<'a>> + as FromParam<'a>> + as FromParam<'a>> and $N others error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied @@ -155,12 +155,12 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied | ^ the trait `FromParam<'_>` is not implemented for `Q` | = help: the following other types implement trait `FromParam<'a>`: - &'a str - IpAddr - Ipv4Addr - Ipv6Addr - NonZero - NonZero - NonZero - NonZero + <&'a str as FromParam<'a>> + > + > + > + as FromParam<'a>> + as FromParam<'a>> + as FromParam<'a>> + as FromParam<'a>> and $N others diff --git a/core/codegen/tests/ui-fail-nightly/typed-uri-bad-type.stderr b/core/codegen/tests/ui-fail-nightly/typed-uri-bad-type.stderr index 228beed621..f41127a9a2 100644 --- a/core/codegen/tests/ui-fail-nightly/typed-uri-bad-type.stderr +++ b/core/codegen/tests/ui-fail-nightly/typed-uri-bad-type.stderr @@ -86,7 +86,7 @@ error[E0277]: the trait bound `S: FromUriParam` <&'a std::path::Path as FromUriParam> <&'a std::path::Path as FromUriParam> and $N others - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `i32: FromUriParam>` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:24:18 @@ -190,7 +190,7 @@ error[E0277]: the trait bound `S: FromUriParam <&'a std::path::Path as FromUriParam> <&'a std::path::Path as FromUriParam> and $N others - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `S: FromUriParam` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:41:29 @@ -229,7 +229,7 @@ error[E0277]: the trait bound `S: FromUriParam <&'a std::path::Path as FromUriParam> <&'a std::path::Path as FromUriParam> and $N others - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `S: Ignorable` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:68:25 @@ -298,7 +298,7 @@ error[E0277]: the trait bound `S: FromUriParam <&'a std::path::Path as FromUriParam> <&'a std::path::Path as FromUriParam> and $N others - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:15:15 @@ -450,7 +450,7 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:49:17 @@ -462,7 +462,7 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:51:22 @@ -474,7 +474,7 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `i32: FromUriParam>` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:58:25 @@ -487,7 +487,7 @@ error[E0277]: the trait bound `i32: FromUriParam> > = note: required for `std::option::Option` to implement `FromUriParam>` - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `isize: FromUriParam` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:60:19 @@ -499,7 +499,7 @@ error[E0277]: the trait bound `isize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `isize: FromUriParam` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:62:24 @@ -511,7 +511,7 @@ error[E0277]: the trait bound `isize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:79:40 @@ -523,7 +523,7 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:80:33 @@ -535,7 +535,7 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:83:25 @@ -547,7 +547,7 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-nightly/typed-uri-bad-type.rs:84:25 @@ -559,4 +559,4 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/core/codegen/tests/ui-fail-stable/catch_type_errors.stderr b/core/codegen/tests/ui-fail-stable/catch_type_errors.stderr index 9d718a2fae..844df3c831 100644 --- a/core/codegen/tests/ui-fail-stable/catch_type_errors.stderr +++ b/core/codegen/tests/ui-fail-stable/catch_type_errors.stderr @@ -10,7 +10,7 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied as Responder<'r, 'static>> as Responder<'r, 'static>> as Responder<'r, 'o>> - as Responder<'r, 'o>> + as Responder<'r, 'o>> as Responder<'r, 'o>> > as Responder<'r, 'r>> @@ -29,7 +29,7 @@ error[E0277]: the trait bound `bool: Responder<'_, '_>` is not satisfied as Responder<'r, 'static>> as Responder<'r, 'static>> as Responder<'r, 'o>> - as Responder<'r, 'o>> + as Responder<'r, 'o>> as Responder<'r, 'o>> > as Responder<'r, 'r>> @@ -62,7 +62,7 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied as Responder<'r, 'static>> as Responder<'r, 'static>> as Responder<'r, 'o>> - as Responder<'r, 'o>> + as Responder<'r, 'o>> as Responder<'r, 'o>> > as Responder<'r, 'r>> @@ -81,7 +81,7 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied as Responder<'r, 'static>> as Responder<'r, 'static>> as Responder<'r, 'o>> - as Responder<'r, 'o>> + as Responder<'r, 'o>> as Responder<'r, 'o>> > as Responder<'r, 'r>> diff --git a/core/codegen/tests/ui-fail-stable/from_form_type_errors.stderr b/core/codegen/tests/ui-fail-stable/from_form_type_errors.stderr index e41f7ecc2f..c07b03d8b8 100644 --- a/core/codegen/tests/ui-fail-stable/from_form_type_errors.stderr +++ b/core/codegen/tests/ui-fail-stable/from_form_type_errors.stderr @@ -137,9 +137,9 @@ error[E0277]: the trait bound `Unknown: FromForm<'r>` is not satisfied as FromForm<'v>> as FromForm<'v>> as FromForm<'v>> + as FromForm<'v>> as FromForm<'r>> as FromForm<'r>> - as FromForm<'r>> and $N others = note: required for `Unknown` to implement `FromForm<'r>` @@ -196,9 +196,9 @@ error[E0277]: the trait bound `Foo: FromForm<'r>` is not satisfied as FromForm<'v>> as FromForm<'v>> as FromForm<'v>> + as FromForm<'v>> as FromForm<'r>> as FromForm<'r>> - as FromForm<'r>> and $N others = note: required for `Foo` to implement `FromForm<'r>` diff --git a/core/codegen/tests/ui-fail-stable/responder-types.stderr b/core/codegen/tests/ui-fail-stable/responder-types.stderr index bce840f4e1..75b5198513 100644 --- a/core/codegen/tests/ui-fail-stable/responder-types.stderr +++ b/core/codegen/tests/ui-fail-stable/responder-types.stderr @@ -12,7 +12,7 @@ error[E0277]: the trait bound `u8: Responder<'_, '_>` is not satisfied as Responder<'r, 'static>> as Responder<'r, 'static>> as Responder<'r, 'o>> - as Responder<'r, 'o>> + as Responder<'r, 'o>> and $N others error[E0277]: the trait bound `Header<'_>: From` is not satisfied @@ -52,7 +52,7 @@ error[E0277]: the trait bound `u8: Responder<'_, '_>` is not satisfied as Responder<'r, 'static>> as Responder<'r, 'static>> as Responder<'r, 'o>> - as Responder<'r, 'o>> + as Responder<'r, 'o>> and $N others error[E0277]: the trait bound `Header<'_>: From` is not satisfied @@ -117,7 +117,7 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied as Responder<'r, 'static>> as Responder<'r, 'static>> as Responder<'r, 'o>> - as Responder<'r, 'o>> + as Responder<'r, 'o>> and $N others note: required by a bound in `route::handler::, Status, (rocket::Data<'o>, Status)>>::from` --> $WORKSPACE/core/lib/src/route/handler.rs diff --git a/core/codegen/tests/ui-fail-stable/route-type-errors.stderr b/core/codegen/tests/ui-fail-stable/route-type-errors.stderr index e7fcb308ff..0fdfc510d7 100644 --- a/core/codegen/tests/ui-fail-stable/route-type-errors.stderr +++ b/core/codegen/tests/ui-fail-stable/route-type-errors.stderr @@ -5,14 +5,14 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied | ^ the trait `FromParam<'_>` is not implemented for `Q` | = help: the following other types implement trait `FromParam<'a>`: - bool - isize - i8 - i16 - i32 - i64 - i128 - usize + > + > + > + > + > + > + > + > and $N others error[E0277]: the trait bound `Q: FromSegments<'_>` is not satisfied @@ -104,14 +104,14 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied | ^ the trait `FromParam<'_>` is not implemented for `Q` | = help: the following other types implement trait `FromParam<'a>`: - bool - isize - i8 - i16 - i32 - i64 - i128 - usize + > + > + > + > + > + > + > + > and $N others error[E0277]: the trait bound `Q: FromRequest<'_>` is not satisfied @@ -138,14 +138,14 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied | ^ the trait `FromParam<'_>` is not implemented for `Q` | = help: the following other types implement trait `FromParam<'a>`: - bool - isize - i8 - i16 - i32 - i64 - i128 - usize + > + > + > + > + > + > + > + > and $N others error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied @@ -155,12 +155,12 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied | ^ the trait `FromParam<'_>` is not implemented for `Q` | = help: the following other types implement trait `FromParam<'a>`: - bool - isize - i8 - i16 - i32 - i64 - i128 - usize + > + > + > + > + > + > + > + > and $N others diff --git a/core/codegen/tests/ui-fail-stable/typed-uri-bad-type.stderr b/core/codegen/tests/ui-fail-stable/typed-uri-bad-type.stderr index 391abb10a6..6b34949422 100644 --- a/core/codegen/tests/ui-fail-stable/typed-uri-bad-type.stderr +++ b/core/codegen/tests/ui-fail-stable/typed-uri-bad-type.stderr @@ -86,7 +86,7 @@ error[E0277]: the trait bound `S: FromUriParam` > > and $N others - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `i32: FromUriParam>` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:24:18 @@ -190,7 +190,7 @@ error[E0277]: the trait bound `S: FromUriParam > > and $N others - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `S: FromUriParam` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:41:29 @@ -229,7 +229,7 @@ error[E0277]: the trait bound `S: FromUriParam > > and $N others - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `S: Ignorable` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:68:25 @@ -298,7 +298,7 @@ error[E0277]: the trait bound `S: FromUriParam > > and $N others - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:15:15 @@ -450,7 +450,7 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:49:17 @@ -462,7 +462,7 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:51:22 @@ -474,7 +474,7 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `i32: FromUriParam>` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:58:25 @@ -487,7 +487,7 @@ error[E0277]: the trait bound `i32: FromUriParam> > = note: required for `std::option::Option` to implement `FromUriParam>` - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `isize: FromUriParam` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:60:19 @@ -499,7 +499,7 @@ error[E0277]: the trait bound `isize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `isize: FromUriParam` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:62:24 @@ -511,7 +511,7 @@ error[E0277]: the trait bound `isize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:79:40 @@ -523,7 +523,7 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:80:33 @@ -535,7 +535,7 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:83:25 @@ -547,7 +547,7 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `usize: FromUriParam` is not satisfied --> tests/ui-fail-stable/typed-uri-bad-type.rs:84:25 @@ -559,4 +559,4 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/core/http/src/uri/fmt/from_uri_param.rs b/core/http/src/uri/fmt/from_uri_param.rs index f1c5fc01ea..bc25171467 100644 --- a/core/http/src/uri/fmt/from_uri_param.rs +++ b/core/http/src/uri/fmt/from_uri_param.rs @@ -1,5 +1,7 @@ -use std::collections::{BTreeMap, HashMap}; use std::path::{Path, PathBuf}; +use std::collections::{BTreeMap, HashMap}; + +use either::Either; use crate::uri::fmt::UriDisplay; use crate::uri::fmt::{self, Part}; @@ -61,7 +63,7 @@ use crate::uri::fmt::{self, Part}; /// /// * `String`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `u8`, `u16`, /// `u32`, `u64`, `u128`, `usize`, `f32`, `f64`, `bool`, `IpAddr`, -/// `Ipv4Addr`, `Ipv6Addr`, `&str`, `Cow` +/// `Ipv4Addr`, `Ipv6Addr`, `&str`, `Cow`, `Either` /// /// The following types have _identity_ implementations _only in [`Path`]_: /// @@ -375,7 +377,9 @@ impl> FromUriParam for Option } /// A no cost conversion allowing `T` to be used in place of an `Result`. -impl> FromUriParam for Result { +impl FromUriParam for Result + where T: FromUriParam +{ type Target = T::Target; #[inline(always)] @@ -384,6 +388,19 @@ impl> FromUriParam for Result< } } +impl FromUriParam> for Either + where T: FromUriParam, U: FromUriParam +{ + type Target = Either; + + fn from_uri_param(param: Either) -> Self::Target { + match param { + Either::Left(a) => Either::Left(T::from_uri_param(a)), + Either::Right(b) => Either::Right(U::from_uri_param(b)), + } + } +} + impl> FromUriParam> for Option { type Target = Option; diff --git a/core/http/src/uri/fmt/uri_display.rs b/core/http/src/uri/fmt/uri_display.rs index 271b2f55ae..621f3337ba 100644 --- a/core/http/src/uri/fmt/uri_display.rs +++ b/core/http/src/uri/fmt/uri_display.rs @@ -2,6 +2,7 @@ use std::collections::{BTreeMap, HashMap}; use std::{fmt, path}; use std::borrow::Cow; +use either::Either; use time::{macros::format_description, format_description::FormatItem}; use crate::RawStr; @@ -421,6 +422,17 @@ impl + ?Sized> UriDisplay

for &T { } } +/// Defers to `T` or `U` in `Either`. +impl, U: UriDisplay

> UriDisplay

for Either { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result { + match self { + Either::Left(t) => UriDisplay::fmt(t, f), + Either::Right(u) => UriDisplay::fmt(u, f), + } + } +} + /// Defers to the `UriDisplay

` implementation for `T`. impl + ?Sized> UriDisplay

for &mut T { #[inline(always)] diff --git a/core/lib/fuzz/targets/collision-matching.rs b/core/lib/fuzz/targets/collision-matching.rs index 4c4bc8a63a..1fb035efa2 100644 --- a/core/lib/fuzz/targets/collision-matching.rs +++ b/core/lib/fuzz/targets/collision-matching.rs @@ -185,7 +185,7 @@ type TestData<'a> = ( fn fuzz((route_a, route_b, req): TestData<'_>) { let rocket = rocket::custom(rocket::Config { workers: 2, - // log_level: rocket::log::LogLevel::Off, + log_level: None, cli_colors: rocket::config::CliColors::Never, ..rocket::Config::debug_default() }); diff --git a/core/lib/src/catcher/catcher.rs b/core/lib/src/catcher/catcher.rs index 2a82b05c47..2aa1402ada 100644 --- a/core/lib/src/catcher/catcher.rs +++ b/core/lib/src/catcher/catcher.rs @@ -127,6 +127,9 @@ pub struct Catcher { /// /// This is -(number of nonempty segments in base). pub(crate) rank: isize, + + /// The catcher's file, line, and column location. + pub(crate) location: Option<(&'static str, u32, u32)>, } // The rank is computed as -(number of nonempty segments in base) => catchers @@ -185,7 +188,8 @@ impl Catcher { base: uri::Origin::root().clone(), handler: Box::new(handler), rank: rank(uri::Origin::root().path()), - code + code, + location: None, } } @@ -328,6 +332,8 @@ pub struct StaticInfo { pub code: Option, /// The catcher's handler, i.e, the annotated function. pub handler: for<'r> fn(Status, &'r Request<'_>) -> BoxFuture<'r>, + /// The file, line, and column where the catcher was defined. + pub location: (&'static str, u32, u32), } #[doc(hidden)] @@ -336,6 +342,7 @@ impl From for Catcher { fn from(info: StaticInfo) -> Catcher { let mut catcher = Catcher::new(info.code, info.handler); catcher.name = Some(info.name.into()); + catcher.location = Some(info.location); catcher } } diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs index cbf984e50f..2686c65ace 100644 --- a/core/lib/src/config/config.rs +++ b/core/lib/src/config/config.rs @@ -2,15 +2,13 @@ use figment::{Figment, Profile, Provider, Metadata, error::Result}; use figment::providers::{Serialized, Env, Toml, Format}; use figment::value::{Map, Dict, magic::RelativePathBuf}; use serde::{Deserialize, Serialize}; -use tracing::Level; #[cfg(feature = "secrets")] use crate::config::SecretKey; -use crate::config::{ShutdownConfig, Ident, CliColors}; +use crate::config::{ShutdownConfig, Level, TraceFormat, Ident, CliColors}; use crate::request::{self, Request, FromRequest}; use crate::http::uncased::Uncased; use crate::data::Limits; -use crate::trace::{Trace, TraceFormat}; /// Rocket server configuration. /// @@ -288,7 +286,7 @@ impl Config { /// /// # Panics /// - /// If extraction fails, prints an error message indicating the error and + /// If extraction fails, logs an error message indicating the error and /// panics. For a version that doesn't panic, use [`Config::try_from()`]. /// /// # Example @@ -306,7 +304,12 @@ impl Config { /// let config = Config::from(figment); /// ``` pub fn from(provider: T) -> Self { - Self::try_from(provider).unwrap_or_else(bail_with_config_error) + use crate::trace::Trace; + + Self::try_from(provider).unwrap_or_else(|e| { + e.trace_error(); + panic!("aborting due to configuration error(s)") + }) } } @@ -433,15 +436,3 @@ impl<'r> FromRequest<'r> for &'r Config { request::Outcome::Success(req.rocket().config()) } } - -#[doc(hidden)] -pub fn bail_with_config_error(error: figment::Error) -> T { - pretty_print_error(error); - panic!("aborting due to configuration error(s)") -} - -#[doc(hidden)] -// FIXME: Remove this function. -pub fn pretty_print_error(error: figment::Error) { - error.trace_error() -} diff --git a/core/lib/src/config/mod.rs b/core/lib/src/config/mod.rs index 376e878ff4..d6969fcb97 100644 --- a/core/lib/src/config/mod.rs +++ b/core/lib/src/config/mod.rs @@ -122,7 +122,7 @@ pub use ident::Ident; pub use config::Config; pub use cli_colors::CliColors; -// pub use crate::log::LogLevel; +pub use crate::trace::{TraceFormat, Level}; pub use crate::shutdown::ShutdownConfig; #[cfg(feature = "tls")] @@ -139,6 +139,3 @@ pub use crate::shutdown::Sig; #[cfg(feature = "secrets")] pub use secret_key::SecretKey; - -#[doc(hidden)] -pub use config::{pretty_print_error, bail_with_config_error}; diff --git a/core/lib/src/error.rs b/core/lib/src/error.rs index 5f56af39af..05e955a625 100644 --- a/core/lib/src/error.rs +++ b/core/lib/src/error.rs @@ -7,8 +7,8 @@ use std::sync::Arc; use figment::Profile; use crate::listener::Endpoint; -use crate::trace::Trace; use crate::{Ignite, Orbit, Phase, Rocket}; +use crate::trace::Trace; /// An error that occurs during launch. /// @@ -84,7 +84,7 @@ impl Error { match result { Ok(_) => process::ExitCode::SUCCESS, Err(e) => { - error_span!("aborting launch due to error" => e.trace_error()); + span_error!("error", "aborting launch due to error" => e.trace_error()); process::ExitCode::SUCCESS } } @@ -200,14 +200,14 @@ impl fmt::Display for ServerError<'_> { pub(crate) fn log_server_error(error: &(dyn StdError + 'static)) { let mut error: &(dyn StdError + 'static) = error; if error.downcast_ref::().is_some() { - warn_span!("minor server error" ["{}", ServerError(error)] => { + span_warn!("request error", "{}", ServerError(error) => { while let Some(source) = error.source() { error = source; warn!("{}", ServerError(error)); } }); } else { - error_span!("server error" ["{}", ServerError(error)] => { + span_error!("server error", "{}", ServerError(error) => { while let Some(source) = error.source() { error = source; error!("{}", ServerError(error)); @@ -215,3 +215,59 @@ pub(crate) fn log_server_error(error: &(dyn StdError + 'static)) { }); } } + +#[doc(hidden)] +pub mod display_hack_impl { + use super::*; + use crate::util::Formatter; + + /// The *magic*. + /// + /// This type implements a `display()` method using an internal `T` that is + /// either `fmt::Display` _or_ `fmt::Debug`, using the former when + /// available. It does so by using a "specialization" hack: it has a blanket + /// DefaultDisplay trait impl for all types that are `fmt::Debug` and a + /// "specialized" inherent impl for all types that are `fmt::Display`. + /// + /// As long as `T: Display`, the "specialized" impl is what Rust will + /// resolve `DisplayHack(v).display()` to when `T: fmt::Display` as it is an + /// inherent impl. Otherwise, Rust will fall back to the blanket impl. + pub struct DisplayHack(pub T); + + pub trait DefaultDisplay { + fn display(&self) -> impl fmt::Display; + } + + /// Blanket implementation for `T: Debug`. This is what Rust will resolve + /// `DisplayHack::display` to when `T: Debug`. + impl DefaultDisplay for DisplayHack { + #[inline(always)] + fn display(&self) -> impl fmt::Display { + Formatter(|f| fmt::Debug::fmt(&self.0, f)) + } + } + + /// "Specialized" implementation for `T: Display`. This is what Rust will + /// resolve `DisplayHack::display` to when `T: Display`. + impl DisplayHack { + #[inline(always)] + pub fn display(&self) -> impl fmt::Display + '_ { + Formatter(|f| fmt::Display::fmt(&self.0, f)) + } + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! display_hack { + ($v:expr) => ({ + #[allow(unused_imports)] + use $crate::error::display_hack_impl::{DisplayHack, DefaultDisplay as _}; + + #[allow(unreachable_code)] + DisplayHack($v).display() + }) +} + +#[doc(hidden)] +pub use display_hack as display_hack; diff --git a/core/lib/src/fairing/ad_hoc.rs b/core/lib/src/fairing/ad_hoc.rs index 58e985f959..b6dfe16b78 100644 --- a/core/lib/src/fairing/ad_hoc.rs +++ b/core/lib/src/fairing/ad_hoc.rs @@ -1,9 +1,10 @@ -use futures::future::{Future, BoxFuture, FutureExt}; use parking_lot::Mutex; +use futures::future::{Future, BoxFuture, FutureExt}; -use crate::route::RouteUri; use crate::{Rocket, Request, Response, Data, Build, Orbit}; use crate::fairing::{Fairing, Kind, Info, Result}; +use crate::route::RouteUri; +use crate::trace::Trace; /// A ad-hoc fairing that can be created from a function or closure. /// @@ -235,7 +236,7 @@ impl AdHoc { let app_config = match rocket.figment().extract::() { Ok(config) => config, Err(e) => { - crate::config::pretty_print_error(e); + e.trace_error(); return Err(rocket); } }; diff --git a/core/lib/src/fairing/fairings.rs b/core/lib/src/fairing/fairings.rs index 0d50837277..16316c50e5 100644 --- a/core/lib/src/fairing/fairings.rs +++ b/core/lib/src/fairing/fairings.rs @@ -51,16 +51,6 @@ impl Fairings { iter!(self, self.active().collect::>().into_iter()) .map(|v| v.1) .collect() - // .into_iter() - // .map(|i| ) - // if !active_fairings.is_empty() { - // tracing::info_span!("fairings").in_scope(|| { - // for (_, fairing) in iter!(self, active_fairings.into_iter()) { - // let (name, kind) = (fairing.info().name, fairing.info().kind); - // info!(name: "fairing", name, %kind) - // } - // }); - // } } pub fn add(&mut self, fairing: Box) { diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index b5cbc0339d..097b3b8e44 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -123,6 +123,7 @@ pub use tokio; pub use figment; pub use time; pub use tracing; +pub use either; #[macro_use] pub mod trace; @@ -164,8 +165,6 @@ mod router; mod phase; mod erased; -#[doc(hidden)] pub use either::Either; - #[doc(inline)] pub use rocket_codegen::*; #[doc(inline)] pub use crate::response::Response; @@ -254,6 +253,11 @@ pub fn async_test(fut: impl std::future::Future) -> R { /// WARNING: This is unstable! Do not use this method outside of Rocket! #[doc(hidden)] pub fn async_main(fut: impl std::future::Future + Send) -> R { + fn bail(e: E) -> T { + e.trace_error(); + panic!("aborting due to error") + } + // FIXME: We need to run `fut` to get the user's `Figment` to properly set // up the async env, but we need the async env to run `fut`. So we're stuck. // Tokio doesn't let us take the state from one async env and migrate it to @@ -263,8 +267,6 @@ pub fn async_main(fut: impl std::future::Future + Send) -> R { // values won't reflect swaps of `Rocket` in attach fairings with different // config values, or values from non-Rocket configs. See tokio-rs/tokio#3329 // for a necessary resolution in `tokio`. - use config::bail_with_config_error as bail; - let fig = Config::figment(); let workers = fig.extract_inner(Config::WORKERS).unwrap_or_else(bail); let max_blocking = fig.extract_inner(Config::MAX_BLOCKING).unwrap_or_else(bail); diff --git a/core/lib/src/lifecycle.rs b/core/lib/src/lifecycle.rs index 6dccd0580a..6f51c959e7 100644 --- a/core/lib/src/lifecycle.rs +++ b/core/lib/src/lifecycle.rs @@ -1,12 +1,12 @@ use futures::future::{FutureExt, Future}; -use crate::{route, catcher, Rocket, Orbit, Request, Response, Data}; use crate::trace::Trace; use crate::util::Formatter; use crate::data::IoHandler; use crate::http::{Method, Status, Header}; use crate::outcome::Outcome; use crate::form::Form; +use crate::{route, catcher, Rocket, Orbit, Request, Response, Data}; // A token returned to force the execution of one method before another. pub(crate) struct RequestToken; @@ -199,6 +199,7 @@ impl Rocket { let mut status = Status::NotFound; for route in self.router.route(request) { // Retrieve and set the requests parameters. + route.trace_info(); request.set_route(route); let name = route.name.as_deref(); @@ -207,7 +208,6 @@ impl Rocket { // Check if the request processing completed (Some) or if the // request needs to be forwarded. If it does, continue the loop - route.trace_info(); outcome.trace_info(); match outcome { o@Outcome::Success(_) | o@Outcome::Error(_) => return o, @@ -215,9 +215,7 @@ impl Rocket { } } - let outcome = Outcome::Forward((data, status)); - outcome.trace_info(); - outcome + Outcome::Forward((data, status)) } // Invokes the catcher for `status`. Returns the response on success. diff --git a/core/lib/src/local/client.rs b/core/lib/src/local/client.rs index 9dad0c4749..f7566e41dd 100644 --- a/core/lib/src/local/client.rs +++ b/core/lib/src/local/client.rs @@ -138,7 +138,7 @@ macro_rules! pub_client_impl { use crate::config; let figment = rocket.figment().clone() - // .merge((config::Config::LOG_LEVEL, config::LogLevel::Debug)) + .merge((config::Config::LOG_LEVEL, "debug")) .select(config::Config::DEBUG_PROFILE); Self::tracked(rocket.reconfigure(figment)) $(.$suffix)? diff --git a/core/lib/src/request/from_param.rs b/core/lib/src/request/from_param.rs index d53a2cb399..9df9842254 100644 --- a/core/lib/src/request/from_param.rs +++ b/core/lib/src/request/from_param.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use std::path::PathBuf; use crate::error::Empty; +use crate::either::Either; use crate::http::uri::{Segments, error::PathError, fmt::Path}; /// Trait to convert a dynamic path segment string to a concrete value. @@ -40,29 +41,56 @@ use crate::http::uri::{Segments, error::PathError, fmt::Path}; /// /// Sometimes, a forward is not desired, and instead, we simply want to know /// that the dynamic path segment could not be parsed into some desired type -/// `T`. In these cases, types of `Option` or `Result` can be -/// used. These types implement `FromParam` themselves. Their implementations -/// always return successfully, so they never forward. They can be used to -/// determine if the `FromParam` call failed and to retrieve the error value -/// from the failed `from_param` call. +/// `T`. In these cases, types of `Option`, `Result`, or +/// `Either` can be used, which implement `FromParam` themselves. /// -/// For instance, imagine you've asked for an `` as a `usize`. To determine -/// when the `` was not a valid `usize` and retrieve the string that failed -/// to parse, you can use a `Result` type for the `` parameter -/// as follows: +/// * **`Option`** _where_ **`T: FromParam`** +/// +/// Always returns successfully. +/// +/// If the conversion to `T` fails, `None` is returned. If the conversion +/// succeeds, `Some(value)` is returned. +/// +/// * **`Result`** _where_ **`T: FromParam`** +/// +/// Always returns successfully. +/// +/// If the conversion to `T` fails, `Err(error)` is returned. If the +/// conversion succeeds, `Ok(value)` is returned. +/// +/// * **`Either`** _where_ **`A: FromParam`** _and_ **`B: FromParam`** +/// +/// Fails only when both `A::from_param` and `B::from_param` fail. If one +/// of the two succeeds, the successful value is returned in +/// `Either::Left(A)` or `Either::Right(B)` variant, respectively. If both +/// fail, the error values from both calls are returned in a tuple in the +/// `Err` variant. +/// +/// `Either` is particularly useful with a `B` type of `&str`, allowing +/// you to retrieve the invalid path segment. Because `&str`'s implementation of +/// `FromParam` always succeeds, the `Right` variant of the `Either` will always +/// contain the path segment in case of failure. +/// +/// For instance, consider the following route and handler: /// /// ```rust /// # #[macro_use] extern crate rocket; +/// use rocket::either::{Either, Left, Right}; +/// /// #[get("/")] -/// fn hello(id: Result) -> String { +/// fn hello(id: Either) -> String { /// match id { -/// Ok(id_num) => format!("usize: {}", id_num), -/// Err(string) => format!("Not a usize: {}", string) +/// Left(id_num) => format!("usize: {}", id_num), +/// Right(string) => format!("Not a usize: {}", string) /// } /// } /// # fn main() { } /// ``` /// +/// In the above example, if the dynamic path segment cannot be parsed into a +/// `usize`, the raw path segment is returned in the `Right` variant of the +/// `Either` value. +/// /// # Provided Implementations /// /// Rocket implements `FromParam` for several standard library types. Their @@ -219,11 +247,11 @@ impl<'a> FromParam<'a> for String { macro_rules! impl_with_fromstr { ($($T:ty),+) => ($( impl<'a> FromParam<'a> for $T { - type Error = &'a str; + type Error = <$T as FromStr>::Err; #[inline(always)] fn from_param(param: &'a str) -> Result { - <$T as FromStr>::from_str(param).map_err(|_| param) + <$T as FromStr>::from_str(param) } } )+) @@ -361,3 +389,23 @@ impl<'r, T: FromSegments<'r>> FromSegments<'r> for Option { } } } + +/// Implements `FromParam` for `Either`, where `A` and `B` both implement +/// `FromParam`. If `A::from_param` returns `Ok(a)`, `Either::Left(a)` is +/// returned. If `B::from_param` returns `Ok(b)`, `Either::Right(b)` is +/// returned. If both `A::from_param` and `B::from_param` return `Err(a)` and +/// `Err(b)`, respectively, then `Err((a, b))` is returned. +impl<'v, A: FromParam<'v>, B: FromParam<'v>> FromParam<'v> for Either { + type Error = (A::Error, B::Error); + + #[inline(always)] + fn from_param(param: &'v str) -> Result { + match A::from_param(param) { + Ok(a) => Ok(Either::Left(a)), + Err(a) => match B::from_param(param) { + Ok(b) => Ok(Either::Right(b)), + Err(b) => Err((a, b)), + } + } + } +} diff --git a/core/lib/src/response/responder.rs b/core/lib/src/response/responder.rs index 2bc14a7c51..f31262c7fc 100644 --- a/core/lib/src/response/responder.rs +++ b/core/lib/src/response/responder.rs @@ -484,7 +484,8 @@ impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Option { match self { Some(r) => r.respond_to(req), None => { - debug!("{} responder returned `None`", std::any::type_name::()); + let type_name = std::any::type_name::(); + debug!(type_name, "`Option` responder returned `None`"); Err(Status::NotFound) }, } @@ -506,13 +507,13 @@ impl<'r, 'o: 'r, 't: 'o, 'e: 'o, T, E> Responder<'r, 'o> for Result /// Responds with the wrapped `Responder` in `self`, whether it is `Left` or /// `Right`. -impl<'r, 'o: 'r, 't: 'o, 'e: 'o, T, E> Responder<'r, 'o> for crate::Either +impl<'r, 'o: 'r, 't: 'o, 'e: 'o, T, E> Responder<'r, 'o> for either::Either where T: Responder<'r, 't>, E: Responder<'r, 'e> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> { match self { - crate::Either::Left(r) => r.respond_to(req), - crate::Either::Right(r) => r.respond_to(req), + either::Either::Left(r) => r.respond_to(req), + either::Either::Right(r) => r.respond_to(req), } } } diff --git a/core/lib/src/rocket.rs b/core/lib/src/rocket.rs index c2c047c555..cafd674a10 100644 --- a/core/lib/src/rocket.rs +++ b/core/lib/src/rocket.rs @@ -185,16 +185,9 @@ impl Rocket { /// ``` #[must_use] pub fn custom(provider: T) -> Self { - // We initialize the logger here so that logging from fairings and so on - // are visible; we use the final config to set a max log-level in ignite - crate::trace::init(None); - - let rocket: Rocket = Rocket(Building { - figment: Figment::from(provider), - ..Default::default() - }); - - rocket.attach(Shield::default()) + Rocket::(Building::default()) + .reconfigure(provider) + .attach(Shield::default()) } /// Overrides the current configuration provider with `provider`. @@ -237,7 +230,12 @@ impl Rocket { /// ``` #[must_use] pub fn reconfigure(mut self, provider: T) -> Self { + // We initialize the logger here so that logging from fairings and so on + // are visible; we use the final config to set a max log-level in ignite self.figment = Figment::from(provider); + crate::trace::init(Config::try_from(&self.figment).ok().as_ref()); + span_trace!("reconfigure" => self.figment().trace_trace()); + self } @@ -566,14 +564,14 @@ impl Rocket { // Log everything we know: config, routes, catchers, fairings. // TODO: Store/print managed state type names? let fairings = self.fairings.unique_set(); - info_span!("config" [profile = %self.figment().profile()] => { + span_info!("config", profile = %self.figment().profile() => { config.trace_info(); self.figment().trace_debug(); }); - info_span!("routes" [count = self.routes.len()] => self.routes().trace_all_info()); - info_span!("catchers" [count = self.catchers.len()] => self.catchers().trace_all_info()); - info_span!("fairings" [count = fairings.len()] => fairings.trace_all_info()); + span_info!("routes", count = self.routes.len() => self.routes().trace_all_info()); + span_info!("catchers", count = self.catchers.len() => self.catchers().trace_all_info()); + span_info!("fairings", count = fairings.len() => fairings.trace_all_info()); // Ignite the rocket. let rocket: Rocket = Rocket(Igniting { @@ -592,19 +590,6 @@ impl Rocket { } } -#[tracing::instrument(name = "items", skip_all, fields(kind = kind))] -fn log_items(kind: &str, items: I, base: B, origin: O) - where T: fmt::Display + Copy, I: Iterator, - B: Fn(&T) -> &Origin<'_>, O: Fn(&T) -> &Origin<'_> -{ - let mut items: Vec<_> = items.collect(); - items.sort_by_key(|i| origin(i).path().as_str().chars().count()); - items.sort_by_key(|i| origin(i).path().segments().count()); - items.sort_by_key(|i| base(i).path().as_str().chars().count()); - items.sort_by_key(|i| base(i).path().segments().count()); - items.iter().for_each(|item| info!(name: "item", %item)); -} - impl Rocket { /// Returns the finalized, active configuration. This is guaranteed to /// remain stable through ignition and into orbit. diff --git a/core/lib/src/route/route.rs b/core/lib/src/route/route.rs index e4e0c8597f..2305ea2c47 100644 --- a/core/lib/src/route/route.rs +++ b/core/lib/src/route/route.rs @@ -176,6 +176,8 @@ pub struct Route { pub format: Option, /// The discovered sentinels. pub(crate) sentinels: Vec, + /// The file, line, and column where the route was defined, if known. + pub(crate) location: Option<(&'static str, u32, u32)>, } impl Route { @@ -250,6 +252,7 @@ impl Route { format: None, sentinels: Vec::new(), handler: Box::new(handler), + location: None, rank, uri, method, } } @@ -371,6 +374,8 @@ pub struct StaticInfo { /// Route-derived sentinels, if any. /// This isn't `&'static [SentryInfo]` because `type_name()` isn't `const`. pub sentinels: Vec, + /// The file, line, and column where the route was defined. + pub location: (&'static str, u32, u32), } #[doc(hidden)] @@ -386,6 +391,7 @@ impl From for Route { rank: info.rank.unwrap_or_else(|| uri.default_rank()), format: info.format, sentinels: info.sentinels.into_iter().collect(), + location: Some(info.location), uri, } } diff --git a/core/lib/src/server.rs b/core/lib/src/server.rs index 7cf265289c..badfb44c95 100644 --- a/core/lib/src/server.rs +++ b/core/lib/src/server.rs @@ -39,7 +39,7 @@ impl Rocket { Request::from_hyp(rocket, parts, connection).unwrap_or_else(|e| e) }); - debug_span!("request headers" => request.inner().headers().iter().trace_all_debug()); + span_debug!("request headers" => request.inner().headers().iter().trace_all_debug()); let mut response = request.into_response( stream, |rocket, request, data| Box::pin(rocket.preprocess(request, data)), @@ -54,7 +54,7 @@ impl Rocket { // TODO: Should upgrades be handled in dispatch? response.inner().trace_info(); - debug_span!("response headers" => response.inner().headers().iter().trace_all_debug()); + span_debug!("response headers" => response.inner().headers().iter().trace_all_debug()); let io_handler = response.make_io_handler(Rocket::extract_io_handler); if let (Some((proto, handler)), Some(upgrade)) = (io_handler, upgrade) { let upgrade = upgrade.map_ok(IoStream::from).map_err(io::Error::other); @@ -92,7 +92,7 @@ async fn io_handler_task(proto: String, stream: S, mut handler: ErasedIoHandl Err(e) => return warn!(error = %e, "i/o upgrade failed"), }; - info!("i/o upgrade succeeded"); + debug!("i/o upgrade succeeded"); if let Err(e) = handler.take().io(stream).await { match e.kind() { io::ErrorKind::BrokenPipe => warn!("i/o handler closed"), diff --git a/core/lib/src/shield/shield.rs b/core/lib/src/shield/shield.rs index 905f37d3b9..9ffbad8666 100644 --- a/core/lib/src/shield/shield.rs +++ b/core/lib/src/shield/shield.rs @@ -4,8 +4,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; use crate::{Rocket, Request, Response, Orbit, Config}; use crate::fairing::{Fairing, Info, Kind}; use crate::http::{Header, uncased::UncasedStr}; -use crate::shield::*; -use crate::trace::*; +use crate::shield::{Frame, Hsts, NoSniff, Permission, Policy}; +use crate::trace::{Trace, TraceAll}; /// A [`Fairing`] that injects browser security and privacy headers into all /// outgoing responses. @@ -195,7 +195,7 @@ impl Fairing for Shield { self.force_hsts.store(true, Ordering::Release); } - info_span!("shield" [policies = self.policies.len()] => { + span_info!("shield", policies = self.policies.len() => { self.policies.values().trace_all_info(); if force_hsts { @@ -211,7 +211,7 @@ impl Fairing for Shield { // the header is not already in the response. for header in self.policies.values() { if response.headers().contains(header.name()) { - warn_span!("shield refusing to overwrite existing response header" => { + span_warn!("shield", "shield refusing to overwrite existing response header" => { header.trace_warn(); }); diff --git a/core/lib/src/trace/macros.rs b/core/lib/src/trace/macros.rs index 4bd759f183..eafa7c4164 100644 --- a/core/lib/src/trace/macros.rs +++ b/core/lib/src/trace/macros.rs @@ -1,39 +1,71 @@ -macro_rules! declare_macro { - ($($name:ident $level:ident),* $(,)?) => ( - $(declare_macro!([$] $name $level);)* +macro_rules! declare_span_macro { + ($name:ident $level:ident) => ( + declare_span_macro!([$] $name $level); ); ([$d:tt] $name:ident $level:ident) => ( #[doc(hidden)] #[macro_export] macro_rules! $name { - ($d ($t:tt)*) => ($crate::tracing::$level!($d ($t)*)); + (@[$d ($t:tt)+] => $in_scope:expr) => ({ + $crate::tracing::span!($crate::tracing::Level::$level, $d ($t)+) + .in_scope(|| $in_scope); + }); + + (@[$d ($t:tt)+] $token:tt $d ($rest:tt)*) => ({ + $crate::trace::$name!(@[$d ($t)+ $token] $d ($rest)*); + }); + + // base case + ($t:tt $d ($rest:tt)*) => ({ + $crate::trace::$name!(@[$t] $d ($rest)*); + }); } - // pub use $name as $name; + #[doc(hidden)] + pub use $name as $name; ); } -macro_rules! declare_span_macro { - ($($name:ident $level:ident),* $(,)?) => ( - $(declare_span_macro!([$] $name $level);)* +#[doc(hidden)] +#[macro_export] +macro_rules! event { + ($level:expr, $($args:tt)*) => {{ + match $level { + $crate::tracing::Level::ERROR => event!(@$crate::tracing::Level::ERROR, $($args)*), + $crate::tracing::Level::WARN => event!(@$crate::tracing::Level::WARN, $($args)*), + $crate::tracing::Level::INFO => event!(@$crate::tracing::Level::INFO, $($args)*), + $crate::tracing::Level::DEBUG => event!(@$crate::tracing::Level::DEBUG, $($args)*), + $crate::tracing::Level::TRACE => event!(@$crate::tracing::Level::TRACE, $($args)*), + } + }}; + + (@$level:expr, $n:expr, $($args:tt)*) => {{ + $crate::tracing::event!(name: $n, target: concat!("rocket::", $n), $level, $($args)*); + }}; +} + +// Re-exports the macro at $path with the name $name. The point is to allow +// a `#[macro_use] extern crate rocket` to also automatically import the +// relevant tracing macros. +macro_rules! reexport { + ($path:ident::$name:ident) => ( + reexport!([$] $path::$name); ); - ([$d:tt] $name:ident $level:ident) => ( + ([ $d:tt ] $path:ident::$name:ident) => { #[doc(hidden)] #[macro_export] macro_rules! $name { - ($n:literal $d ([ $d ($f:tt)* ])? => $in_scope:expr) => ({ - $crate::tracing::span!($crate::tracing::Level::$level, $n $d (, $d ($f)* )?) - .in_scope(|| $in_scope); - }) + ($d ($f:tt)*) => { + $crate::$path::$name!($d ($f)*) + } } - - #[doc(inline)] - pub use $name as $name; - ); + }; } +#[doc(hidden)] +#[macro_export] macro_rules! span { ($level:expr, $($args:tt)*) => {{ match $level { @@ -51,39 +83,26 @@ macro_rules! span { }}; } -#[doc(hidden)] -#[macro_export] -macro_rules! event { - ($level:expr, $($args:tt)*) => {{ - match $level { - $crate::tracing::Level::ERROR => event!(@$crate::tracing::Level::ERROR, $($args)*), - $crate::tracing::Level::WARN => event!(@$crate::tracing::Level::WARN, $($args)*), - $crate::tracing::Level::INFO => event!(@$crate::tracing::Level::INFO, $($args)*), - $crate::tracing::Level::DEBUG => event!(@$crate::tracing::Level::DEBUG, $($args)*), - $crate::tracing::Level::TRACE => event!(@$crate::tracing::Level::TRACE, $($args)*), - } - }}; +#[doc(inline)] +pub use span as span; - (@$level:expr, $n:expr, $($args:tt)*) => {{ - $crate::tracing::event!(name: $n, target: concat!("rocket::", $n), $level, $($args)*); - }}; -} +declare_span_macro!(span_error ERROR); +declare_span_macro!(span_warn WARN); +declare_span_macro!(span_info INFO); +declare_span_macro!(span_debug DEBUG); +declare_span_macro!(span_trace TRACE); #[doc(inline)] pub use event as event; -declare_macro!( - error error, - info info, - trace trace, - debug debug, - warn warn -); +reexport!(tracing::error); +reexport!(tracing::warn); +reexport!(tracing::info); +reexport!(tracing::debug); +reexport!(tracing::trace); -declare_span_macro!( - error_span ERROR, - warn_span WARN, - info_span INFO, - trace_span TRACE, - debug_span DEBUG, -); +#[doc(hidden)] pub use tracing::error; +#[doc(hidden)] pub use tracing::warn; +#[doc(hidden)] pub use tracing::info; +#[doc(hidden)] pub use tracing::debug; +#[doc(hidden)] pub use tracing::trace; diff --git a/core/lib/src/trace/mod.rs b/core/lib/src/trace/mod.rs index 12972da306..30f05143b0 100644 --- a/core/lib/src/trace/mod.rs +++ b/core/lib/src/trace/mod.rs @@ -8,14 +8,18 @@ pub mod subscriber; pub(crate) mod level; +#[doc(inline)] +pub use macros::*; + #[doc(inline)] pub use traceable::{Trace, TraceAll}; #[doc(inline)] -pub use macros::*; +pub use tracing::{Level, level_filters::LevelFilter}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)] #[serde(crate = "rocket::serde")] +#[non_exhaustive] pub enum TraceFormat { #[serde(rename = "pretty")] #[serde(alias = "PRETTY")] @@ -27,6 +31,9 @@ pub enum TraceFormat { #[cfg_attr(nightly, doc(cfg(feature = "trace")))] pub fn init<'a, T: Into>>(config: T) { + #[cfg(not(feature = "trace"))] + let _ = config; + #[cfg(feature = "trace")] crate::trace::subscriber::RocketDynFmt::init(config.into()) } diff --git a/core/lib/src/trace/subscriber/pretty.rs b/core/lib/src/trace/subscriber/pretty.rs index c914c6793a..c7263cdd45 100644 --- a/core/lib/src/trace/subscriber/pretty.rs +++ b/core/lib/src/trace/subscriber/pretty.rs @@ -28,9 +28,9 @@ impl RocketFmt { MARKER.get(self.state().depth as usize).copied().unwrap_or("-- ") } - fn emoji(&self, emoji: &'static str) -> Painted<&'static str> { + fn emoji(&self, _emoji: &'static str) -> Painted<&'static str> { #[cfg(windows)] { "".paint(self.style).mask() } - #[cfg(not(windows))] { emoji.paint(self.style).mask() } + #[cfg(not(windows))] { _emoji.paint(self.style).mask() } } fn prefix<'a>(&self, meta: &'a Metadata<'_>) -> impl fmt::Display + 'a { @@ -79,7 +79,7 @@ impl LookupSpan<'a>> Layer for RocketFmt { "liftoff" => { let prefix = self.prefix(meta); println!("{prefix}{}{} {}", self.emoji("🚀 "), - "Rocket has launched from".paint(style).primary().bold(), + "Rocket has launched on".paint(style).primary().bold(), &data["endpoint"].paint(style).primary().bold().underline()); }, "route" => println!("{}", Formatter(|f| { @@ -98,7 +98,13 @@ impl LookupSpan<'a>> Layer for RocketFmt { )?; if let Some(name) = data.get("name") { - write!(f, " ({})", name.paint(style.bold().bright()))?; + write!(f, " ({}", name.paint(style.bold().bright()))?; + + if let Some(location) = data.get("location") { + write!(f, " {}", location.paint(style.dim()))?; + } + + write!(f, ")")?; } Ok(()) @@ -113,7 +119,13 @@ impl LookupSpan<'a>> Layer for RocketFmt { write!(f, "{}", &data["uri.base"].paint(style.primary()))?; if let Some(name) = data.get("name") { - write!(f, " ({})", name.paint(style.bold().bright()))?; + write!(f, " ({}", name.paint(style.bold().bright()))?; + + if let Some(location) = data.get("location") { + write!(f, " {}", location.paint(style.dim()))?; + } + + write!(f, ")")?; } Ok(()) diff --git a/core/lib/src/trace/traceable.rs b/core/lib/src/trace/traceable.rs index 6816241f09..bbf11fa01b 100644 --- a/core/lib/src/trace/traceable.rs +++ b/core/lib/src/trace/traceable.rs @@ -47,10 +47,15 @@ impl Trace for Figment { fn trace(&self, level: Level) { for param in Config::PARAMETERS { if let Some(source) = self.find_metadata(param) { + if param.contains("secret") { + continue; + } + event! { level, "figment", param, %source.name, source.source = source.source.as_ref().map(display), + value = self.find_value(param).ok().map(debug), } } } @@ -142,6 +147,9 @@ impl Trace for Route { uri.base = %self.uri.base(), uri.unmounted = %self.uri.unmounted(), format = self.format.as_ref().map(display), + location = self.location.as_ref() + .map(|(file, line, _)| Formatter(move |f| write!(f, "{file}:{line}"))) + .map(display), } event! { Level::DEBUG, "sentinels", @@ -165,6 +173,9 @@ impl Trace for Catcher { }), rank = self.rank, uri.base = %self.base(), + location = self.location.as_ref() + .map(|(file, line, _)| Formatter(move |f| write!(f, "{file}:{line}"))) + .map(display), } } } @@ -205,17 +216,14 @@ impl Trace for figment::error::Kind { impl Trace for figment::Error { fn trace(&self, _: Level) { for e in self.clone() { - let span = tracing::error_span! { - "config", + span_error!("config", key = (!e.path.is_empty()).then_some(&e.path).and_then(|path| { let (profile, metadata) = (e.profile.as_ref()?, e.metadata.as_ref()?); Some(metadata.interpolate(profile, path)) }), source.name = e.metadata.as_ref().map(|m| &*m.name), - source.source = e.metadata.as_ref().and_then(|m| m.source.as_ref()).map(display), - }; - - span.in_scope(|| e.kind.trace_error()); + source.source = e.metadata.as_ref().and_then(|m| m.source.as_ref()).map(display) + => e.kind.trace_error()); } } } @@ -298,7 +306,7 @@ impl Trace for ErrorKind { e.trace(level); } else { event!(level, "error::bind", - ?error, + reason = %error, endpoint = endpoint.as_ref().map(display), "binding to network interface failed" ) diff --git a/core/lib/tests/sentinel.rs b/core/lib/tests/sentinel.rs index 4221e3acfd..d88e99b98d 100644 --- a/core/lib/tests/sentinel.rs +++ b/core/lib/tests/sentinel.rs @@ -1,4 +1,4 @@ -use rocket::{*, error::ErrorKind::SentinelAborts}; +use rocket::{*, either::Either, error::ErrorKind::SentinelAborts}; #[get("/two")] fn two_states(_one: &State, _two: &State) {} diff --git a/examples/config/Rocket.toml b/examples/config/Rocket.toml index 7ee88f060c..f0284e3117 100644 --- a/examples/config/Rocket.toml +++ b/examples/config/Rocket.toml @@ -19,6 +19,7 @@ port = 8000 workers = 1 keep_alive = 0 log_level = "info" +log_format = "pretty" [release] address = "127.0.0.1" @@ -26,6 +27,7 @@ port = 8000 workers = 12 keep_alive = 5 log_level = "error" +log_format = "compact" # NOTE: Don't (!) use this key! Generate your own and keep it private! # e.g. via `head -c64 /dev/urandom | base64` secret_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk=" diff --git a/examples/config/src/tests.rs b/examples/config/src/tests.rs index a5f08d580c..0f13a35782 100644 --- a/examples/config/src/tests.rs +++ b/examples/config/src/tests.rs @@ -1,4 +1,5 @@ -use rocket::config::{Config, /* LogLevel */}; +use rocket::config::Config; +use rocket::trace::{Level, TraceFormat}; async fn test_config(profile: &str) { let provider = Config::figment().select(profile); @@ -8,12 +9,14 @@ async fn test_config(profile: &str) { "debug" => { assert_eq!(config.workers, 1); assert_eq!(config.keep_alive, 0); - // assert_eq!(config.log_level, LogLevel::Normal); + assert_eq!(config.log_level, Some(Level::INFO)); + assert_eq!(config.log_format, TraceFormat::Pretty); } "release" => { assert_eq!(config.workers, 12); assert_eq!(config.keep_alive, 5); - // assert_eq!(config.log_level, LogLevel::Critical); + assert_eq!(config.log_level, Some(Level::ERROR)); + assert_eq!(config.log_format, TraceFormat::Compact); assert!(!config.secret_key.is_zero()); } _ => { diff --git a/examples/fairings/src/main.rs b/examples/fairings/src/main.rs index 754a6965f3..0e826a1c69 100644 --- a/examples/fairings/src/main.rs +++ b/examples/fairings/src/main.rs @@ -76,7 +76,7 @@ fn rocket() -> _ { .attach(AdHoc::on_request("PUT Rewriter", |req, _| { Box::pin(async move { if req.uri().path() == "/" { - info_span!("PUT rewriter" => { + span_info!("PUT rewriter" => { req.trace_info(); info!("changing method to `PUT`"); req.set_method(Method::Put); diff --git a/examples/hello/Cargo.toml b/examples/hello/Cargo.toml index 03a01b1bbb..4546b8c81d 100644 --- a/examples/hello/Cargo.toml +++ b/examples/hello/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -rocket = { path = "../../core/lib", features = ["secrets"] } +rocket = { path = "../../core/lib" } diff --git a/examples/hello/src/main.rs b/examples/hello/src/main.rs index 5d66842f82..0f8c55cb1a 100644 --- a/examples/hello/src/main.rs +++ b/examples/hello/src/main.rs @@ -38,9 +38,6 @@ fn wave(name: &str, age: u8) -> String { format!("👋 Hello, {} year old named {}!", age, name) } -#[get("//")] -fn f(a: usize, b: usize) { } - // Note: without the `..` in `opt..`, we'd need to pass `opt.emoji`, `opt.name`. // // Try visiting: @@ -54,7 +51,7 @@ fn f(a: usize, b: usize) { } // http://127.0.0.1:8000/?name=Rocketeer&lang=en&emoji // http://127.0.0.1:8000/?lang=ru&emoji&name=Rocketeer #[get("/?&")] -async fn hello(lang: Option, opt: Options<'_>) -> String { +fn hello(lang: Option, opt: Options<'_>) -> String { let mut greeting = String::new(); if opt.emoji { greeting.push_str("👋 "); diff --git a/examples/responders/src/main.rs b/examples/responders/src/main.rs index 4e067095ff..90b65b3be2 100644 --- a/examples/responders/src/main.rs +++ b/examples/responders/src/main.rs @@ -159,7 +159,7 @@ fn not_found(request: &Request<'_>) -> content::RawHtml { /******************************* `Either` Responder ***************************/ -use rocket::Either; +use rocket::either::Either; use rocket::response::content::{RawJson, RawMsgPack}; use rocket::http::uncased::AsUncased; diff --git a/examples/tls/src/redirector.rs b/examples/tls/src/redirector.rs index 07bc4de814..2e4a5ce320 100644 --- a/examples/tls/src/redirector.rs +++ b/examples/tls/src/redirector.rs @@ -45,7 +45,7 @@ impl Redirector { pub async fn try_launch(self, config: Config) -> Result, Error> { use rocket::http::Method::*; - rocket::info_span!("HTTP -> HTTPS Redirector" => { + rocket::span_info!("HTTP -> HTTPS Redirector" => { info!(from = self.0, to = config.tls_addr.port(), "redirecting"); }); @@ -75,7 +75,7 @@ impl Fairing for Redirector { async fn on_liftoff(&self, rocket: &Rocket) { let Some(tls_addr) = rocket.endpoints().find_map(|e| e.tls()?.tcp()) else { - rocket::warn_span!("HTTP -> HTTPS Redirector" => { + rocket::span_warn!("HTTP -> HTTPS Redirector" => { warn!("Main instance is not being served over TLS/TCP.\n\ Redirector refusing to start."); }); @@ -95,7 +95,7 @@ impl Fairing for Redirector { let shutdown = rocket.shutdown(); rocket::tokio::spawn(async move { if let Err(e) = this.try_launch(config).await { - error_span!("failed to start HTTP -> HTTPS redirector" => { + span_error!("HTTP -> HTTPS Redirector", "failed to start" => { e.trace_error(); info!("shutting down main instance"); }); diff --git a/testbench/Cargo.toml b/testbench/Cargo.toml index c640a9720c..01e048fe56 100644 --- a/testbench/Cargo.toml +++ b/testbench/Cargo.toml @@ -13,6 +13,7 @@ procspawn = "1" pretty_assertions = "1.4.0" ipc-channel = "0.18" rustls-pemfile = "2.1" +inventory = "0.3.15" [dependencies.nix] version = "0.28" diff --git a/testbench/src/client.rs b/testbench/src/client.rs index c3f3fda3d2..953b2f907a 100644 --- a/testbench/src/client.rs +++ b/testbench/src/client.rs @@ -1,7 +1,7 @@ -use std::time::Duration; +use std::{str::FromStr, time::Duration}; use reqwest::blocking::{ClientBuilder, RequestBuilder}; -use rocket::http::{ext::IntoOwned, uri::{Absolute, Uri}}; +use rocket::http::{ext::IntoOwned, uri::{Absolute, Uri}, Method}; use crate::{Result, Error, Server}; @@ -26,7 +26,7 @@ impl Client { .connect_timeout(Duration::from_secs(5)) } - pub fn get(&self, server: &Server, url: &str) -> Result { + pub fn request(&self, server: &Server, method: Method, url: &str) -> Result { let uri = match Uri::parse_any(url).map_err(|e| e.into_owned())? { Uri::Origin(uri) => { let proto = if server.tls { "https" } else { "http" }; @@ -45,7 +45,16 @@ impl Client { uri => return Err(Error::InvalidUri(uri.into_owned())), }; - Ok(self.client.get(uri.to_string())) + let method = reqwest::Method::from_str(method.as_str()).unwrap(); + Ok(self.client.request(method, uri.to_string())) + } + + pub fn get(&self, server: &Server, url: &str) -> Result { + self.request(server, Method::Get, url) + } + + pub fn post(&self, server: &Server, url: &str) -> Result { + self.request(server, Method::Post, url) } } diff --git a/testbench/src/config.rs b/testbench/src/config.rs new file mode 100644 index 0000000000..c5d19a97d7 --- /dev/null +++ b/testbench/src/config.rs @@ -0,0 +1,62 @@ +use rocket::{Build, Rocket}; + +use testbench::{Result, Error}; + +pub static DEFAULT_CONFIG: &str = r#" + [default] + address = "tcp:127.0.0.1" + workers = 2 + port = 0 + cli_colors = false + log_level = "debug" + secret_key = "itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg=" + + [default.shutdown] + grace = 1 + mercy = 1 +"#; + +pub static TLS_CONFIG: &str = r#" + [default.tls] + certs = "{ROCKET}/examples/tls/private/rsa_sha256_cert.pem" + key = "{ROCKET}/examples/tls/private/rsa_sha256_key.pem" +"#; + +pub trait RocketExt { + fn default() -> Self; + fn tls_default() -> Self; + fn reconfigure_with_toml(self, toml: &str) -> Self; +} + +impl RocketExt for Rocket { + fn default() -> Self { + rocket::build().reconfigure_with_toml(DEFAULT_CONFIG) + } + + fn tls_default() -> Self { + rocket::build() + .reconfigure_with_toml(DEFAULT_CONFIG) + .reconfigure_with_toml(TLS_CONFIG) + } + + fn reconfigure_with_toml(self, toml: &str) -> Self { + use rocket::figment::{Figment, providers::{Format, Toml}}; + + let toml = toml.replace("{ROCKET}", rocket::fs::relative!("../")); + let config = Figment::from(self.figment()) + .merge(Toml::string(&toml).nested()); + + self.reconfigure(config) + } +} + +pub fn read(path: &str) -> Result> { + let path = path.replace("{ROCKET}", rocket::fs::relative!("../")); + Ok(std::fs::read(path)?) +} + +pub fn cert(path: &str) -> Result> { + let mut data = std::io::Cursor::new(read(path)?); + let cert = rustls_pemfile::certs(&mut data).last(); + Ok(cert.ok_or(Error::MissingCertificate)??.to_vec()) +} diff --git a/testbench/src/main.rs b/testbench/src/main.rs index 2ea7a81c53..3145ea3469 100644 --- a/testbench/src/main.rs +++ b/testbench/src/main.rs @@ -1,480 +1,19 @@ -use std::net::{Ipv4Addr, SocketAddr}; -use std::process::ExitCode; -use std::time::Duration; +mod runner; +mod servers; +mod config; -use rocket::tokio::net::TcpListener; -use rocket::yansi::Paint; -use rocket::{get, routes, Build, Rocket, State}; -use rocket::listener::{unix::UnixListener, Endpoint}; -use rocket::tls::TlsListener; +pub mod prelude { + pub use rocket::*; + pub use rocket::fairing::*; + pub use rocket::response::stream::*; -use reqwest::{tls::TlsInfo, Identity}; - -use testbench::*; - -static DEFAULT_CONFIG: &str = r#" - [default] - address = "tcp:127.0.0.1" - workers = 2 - port = 0 - cli_colors = false - secret_key = "itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg=" - - [default.shutdown] - grace = 1 - mercy = 1 -"#; - -static TLS_CONFIG: &str = r#" - [default.tls] - certs = "{ROCKET}/examples/tls/private/rsa_sha256_cert.pem" - key = "{ROCKET}/examples/tls/private/rsa_sha256_key.pem" -"#; - -trait RocketExt { - fn default() -> Self; - fn tls_default() -> Self; - fn reconfigure_with_toml(self, toml: &str) -> Self; -} - -impl RocketExt for Rocket { - fn default() -> Self { - rocket::build().reconfigure_with_toml(DEFAULT_CONFIG) - } - - fn tls_default() -> Self { - rocket::build() - .reconfigure_with_toml(DEFAULT_CONFIG) - .reconfigure_with_toml(TLS_CONFIG) - } - - fn reconfigure_with_toml(self, toml: &str) -> Self { - use rocket::figment::{Figment, providers::{Format, Toml}}; - - let toml = toml.replace("{ROCKET}", rocket::fs::relative!("../")); - let config = Figment::from(self.figment()) - .merge(Toml::string(&toml).nested()); - - self.reconfigure(config) - } -} - -fn read(path: &str) -> Result> { - let path = path.replace("{ROCKET}", rocket::fs::relative!("../")); - Ok(std::fs::read(path)?) -} - -fn cert(path: &str) -> Result> { - let mut data = std::io::Cursor::new(read(path)?); - let cert = rustls_pemfile::certs(&mut data).last(); - Ok(cert.ok_or(Error::MissingCertificate)??.to_vec()) -} - -fn run_fail() -> Result<()> { - use rocket::fairing::AdHoc; - - let server = spawn! { - let fail = AdHoc::try_on_ignite("FailNow", |rocket| async { Err(rocket) }); - Rocket::default().attach(fail) - }; - - if let Err(Error::Liftoff(stdout, _)) = server { - // assert!(stdout.contains("Rocket failed to launch due to failing fairings")); - // assert!(stdout.contains("FailNow")); - } else { - panic!("unexpected result: {server:#?}"); - } - - Ok(()) + pub use testbench::{Error, Result, *}; + pub use crate::register; + pub use crate::config::*; } -fn infinite() -> Result<()> { - use rocket::response::stream::TextStream; - - let mut server = spawn! { - #[get("/")] - fn infinite() -> TextStream![&'static str] { - TextStream! { - loop { - yield rocket::futures::future::pending::<&str>().await; - } - } - } +pub use runner::Test; - Rocket::default().mount("/", routes![infinite]) - }?; - - let client = Client::default(); - client.get(&server, "/")?.send()?; - server.terminate()?; - - let stdout = server.read_stdout()?; - // assert!(stdout.contains("Rocket has launched on http")); - // assert!(stdout.contains("GET /")); - // assert!(stdout.contains("Graceful shutdown completed")); - Ok(()) +fn main() -> std::process::ExitCode { + runner::run() } - -fn tls_info() -> Result<()> { - #[get("/")] - fn hello_world(endpoint: &Endpoint) -> String { - format!("Hello, {endpoint}!") - } - - let mut server = spawn! { - Rocket::tls_default().mount("/", routes![hello_world]) - }?; - - let client = Client::default(); - let response = client.get(&server, "/")?.send()?; - let tls = response.extensions().get::().unwrap(); - assert!(!tls.peer_certificate().unwrap().is_empty()); - assert!(response.text()?.starts_with("Hello, https://127.0.0.1")); - - server.terminate()?; - let stdout = server.read_stdout()?; - // assert!(stdout.contains("Rocket has launched on https")); - // assert!(stdout.contains("Graceful shutdown completed")); - // assert!(stdout.contains("GET /")); - - let server = Server::spawn((), |(token, _)| { - let rocket = rocket::build() - .reconfigure_with_toml(TLS_CONFIG) - .mount("/", routes![hello_world]); - - token.with_launch(rocket, |rocket| { - let config = rocket.figment().extract_inner("tls"); - rocket.try_launch_on(async move { - let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0); - let listener = TcpListener::bind(addr).await?; - TlsListener::from(listener, config?).await - }) - }) - }).unwrap(); - - let client = Client::default(); - let response = client.get(&server, "/")?.send()?; - let tls = response.extensions().get::().unwrap(); - assert!(!tls.peer_certificate().unwrap().is_empty()); - assert!(response.text()?.starts_with("Hello, https://127.0.0.1")); - - Ok(()) -} - -fn tls_resolver() -> Result<()> { - use std::sync::Arc; - use std::sync::atomic::{AtomicUsize, Ordering}; - use rocket::tls::{Resolver, TlsConfig, ClientHello, ServerConfig}; - - struct CountingResolver { - config: Arc, - counter: Arc, - } - - #[rocket::async_trait] - impl Resolver for CountingResolver { - async fn init(rocket: &Rocket) -> rocket::tls::Result { - let config: TlsConfig = rocket.figment().extract_inner("tls")?; - let config = Arc::new(config.server_config().await?); - let counter = rocket.state::>().unwrap().clone(); - Ok(Self { config, counter }) - } - - async fn resolve(&self, _: ClientHello<'_>) -> Option> { - self.counter.fetch_add(1, Ordering::Release); - Some(self.config.clone()) - } - } - - let server = spawn! { - #[get("/count")] - fn count(counter: &State>) -> String { - counter.load(Ordering::Acquire).to_string() - } - - let counter = Arc::new(AtomicUsize::new(0)); - Rocket::tls_default() - .manage(counter) - .mount("/", routes![count]) - .attach(CountingResolver::fairing()) - }?; - - let client = Client::default(); - let response = client.get(&server, "/count")?.send()?; - assert_eq!(response.text()?, "1"); - - // Use a new client so we get a new TLS session. - let client = Client::default(); - let response = client.get(&server, "/count")?.send()?; - assert_eq!(response.text()?, "2"); - Ok(()) -} - -fn test_mtls(mandatory: bool) -> Result<()> { - let server = spawn!(mandatory: bool => { - let mtls_config = format!(r#" - [default.tls.mutual] - ca_certs = "{{ROCKET}}/examples/tls/private/ca_cert.pem" - mandatory = {mandatory} - "#); - - #[get("/")] - fn hello(cert: rocket::mtls::Certificate<'_>) -> String { - format!("{}:{}[{}] {}", cert.serial(), cert.version(), cert.issuer(), cert.subject()) - } - - #[get("/", rank = 2)] - fn hi() -> &'static str { - "Hello!" - } - - Rocket::tls_default() - .reconfigure_with_toml(&mtls_config) - .mount("/", routes![hello, hi]) - })?; - - let pem = read("{ROCKET}/examples/tls/private/client.pem")?; - let client: Client = Client::build() - .identity(Identity::from_pem(&pem)?) - .try_into()?; - - let response = client.get(&server, "/")?.send()?; - assert_eq!(response.text()?, - "611895682361338926795452113263857440769284805738:2\ - [C=US, ST=CA, O=Rocket CA, CN=Rocket Root CA] \ - C=US, ST=California, L=Silicon Valley, O=Rocket, \ - CN=Rocket TLS Example, Email=example@rocket.local"); - - let client = Client::default(); - let response = client.get(&server, "/")?.send(); - if mandatory { - assert!(response.unwrap_err().is_request()); - } else { - assert_eq!(response?.text()?, "Hello!"); - } - - Ok(()) -} - -fn tls_mtls() -> Result<()> { - test_mtls(false)?; - test_mtls(true) -} - -fn sni_resolver() -> Result<()> { - use std::sync::Arc; - use std::collections::HashMap; - - use rocket::http::uri::Host; - use rocket::tls::{Resolver, TlsConfig, ClientHello, ServerConfig}; - - struct SniResolver { - default: Arc, - map: HashMap, Arc> - } - - #[rocket::async_trait] - impl Resolver for SniResolver { - async fn init(rocket: &Rocket) -> rocket::tls::Result { - let default: TlsConfig = rocket.figment().extract_inner("tls")?; - let sni: HashMap, TlsConfig> = rocket.figment().extract_inner("tls.sni")?; - - let default = Arc::new(default.server_config().await?); - let mut map = HashMap::new(); - for (host, config) in sni { - let config = config.server_config().await?; - map.insert(host, Arc::new(config)); - } - - Ok(SniResolver { default, map }) - } - - async fn resolve(&self, hello: ClientHello<'_>) -> Option> { - if let Some(Ok(host)) = hello.server_name().map(Host::parse) { - if let Some(config) = self.map.get(&host) { - return Some(config.clone()); - } - } - - Some(self.default.clone()) - } - } - - static SNI_TLS_CONFIG: &str = r#" - [default.tls] - certs = "{ROCKET}/examples/tls/private/rsa_sha256_cert.pem" - key = "{ROCKET}/examples/tls/private/rsa_sha256_key.pem" - - [default.tls.sni."sni1.dev"] - certs = "{ROCKET}/examples/tls/private/ecdsa_nistp256_sha256_cert.pem" - key = "{ROCKET}/examples/tls/private/ecdsa_nistp256_sha256_key_pkcs8.pem" - - [default.tls.sni."sni2.dev"] - certs = "{ROCKET}/examples/tls/private/ed25519_cert.pem" - key = "{ROCKET}/examples/tls/private/ed25519_key.pem" - "#; - - let server = spawn! { - #[get("/")] fn index() { } - - Rocket::default() - .reconfigure_with_toml(SNI_TLS_CONFIG) - .mount("/", routes![index]) - .attach(SniResolver::fairing()) - }?; - - let client: Client = Client::build() - .resolve("unknown.dev", server.socket_addr()) - .resolve("sni1.dev", server.socket_addr()) - .resolve("sni2.dev", server.socket_addr()) - .try_into()?; - - let response = client.get(&server, "https://unknown.dev")?.send()?; - let tls = response.extensions().get::().unwrap(); - let expected = cert("{ROCKET}/examples/tls/private/rsa_sha256_cert.pem")?; - assert_eq!(tls.peer_certificate().unwrap(), expected); - - let response = client.get(&server, "https://sni1.dev")?.send()?; - let tls = response.extensions().get::().unwrap(); - let expected = cert("{ROCKET}/examples/tls/private/ecdsa_nistp256_sha256_cert.pem")?; - assert_eq!(tls.peer_certificate().unwrap(), expected); - - let response = client.get(&server, "https://sni2.dev")?.send()?; - let tls = response.extensions().get::().unwrap(); - let expected = cert("{ROCKET}/examples/tls/private/ed25519_cert.pem")?; - assert_eq!(tls.peer_certificate().unwrap(), expected); - Ok(()) -} - -fn tcp_unix_listener_fail() -> Result<()> { - let server = spawn! { - Rocket::default().reconfigure_with_toml("[default]\naddress = 123") - }; - - if let Err(Error::Liftoff(stdout, _)) = server { - // assert!(stdout.contains("expected valid TCP (ip) or unix (path)")); - // assert!(stdout.contains("default.address")); - } else { - panic!("unexpected result: {server:#?}"); - } - - let server = Server::spawn((), |(token, _)| { - let rocket = Rocket::default().reconfigure_with_toml("[default]\naddress = \"unix:foo\""); - token.launch_with::(rocket) - }); - - if let Err(Error::Liftoff(stdout, _)) = server { - // assert!(stdout.contains("invalid tcp endpoint: unix:foo")); - } else { - panic!("unexpected result: {server:#?}"); - } - - let server = Server::spawn((), |(token, _)| { - token.launch_with::(Rocket::default()) - }); - - if let Err(Error::Liftoff(stdout, _)) = server { - // assert!(stdout.contains("invalid unix endpoint: tcp:127.0.0.1:8000")); - } else { - panic!("unexpected result: {server:#?}"); - } - - Ok(()) -} - -macro_rules! tests { - ($($f:ident),* $(,)?) => {[ - $(Test { - name: stringify!($f), - run: |_: ()| $f().map_err(|e| e.to_string()), - }),* - ]}; -} - -#[derive(Copy, Clone)] -struct Test { - name: &'static str, - run: fn(()) -> Result<(), String>, -} - -static TESTS: &[Test] = &tests![ - run_fail, infinite, tls_info, tls_resolver, tls_mtls, sni_resolver, - tcp_unix_listener_fail -]; - -fn main() -> ExitCode { - procspawn::init(); - - let filter = std::env::args().nth(1).unwrap_or_default(); - let filtered = TESTS.into_iter().filter(|test| test.name.contains(&filter)); - - println!("running {}/{} tests", filtered.clone().count(), TESTS.len()); - let handles = filtered.map(|test| (test, std::thread::spawn(|| { - let name = test.name; - let start = std::time::SystemTime::now(); - let mut proc = procspawn::spawn((), test.run); - let result = loop { - match proc.join_timeout(Duration::from_secs(10)) { - Err(e) if e.is_timeout() => { - let elapsed = start.elapsed().unwrap().as_secs(); - println!("{name} has been running for {elapsed} seconds..."); - - if elapsed >= 30 { - println!("{name} timeout"); - break Err(e); - } - }, - result => break result, - } - }; - - match result.as_ref().map_err(|e| e.panic_info()) { - Ok(Ok(_)) => println!("test {name} ... {}", "ok".green()), - Ok(Err(e)) => println!("test {name} ... {}\n {e}", "fail".red()), - Err(Some(_)) => println!("test {name} ... {}", "panic".red().underline()), - Err(None) => println!("test {name} ... {}", "error".magenta()), - } - - matches!(result, Ok(Ok(()))) - }))); - - let mut success = true; - for (_, handle) in handles { - success &= handle.join().unwrap_or(false); - } - - match success { - true => ExitCode::SUCCESS, - false => { - println!("note: use `NOCAPTURE=1` to see test output"); - ExitCode::FAILURE - } - } -} - -// TODO: Implement an `UpdatingResolver`. Expose `SniResolver` and -// `UpdatingResolver` in a `contrib` library or as part of `rocket`. -// -// struct UpdatingResolver { -// timestamp: AtomicU64, -// config: ArcSwap -// } -// -// #[crate::async_trait] -// impl Resolver for UpdatingResolver { -// async fn resolve(&self, _: ClientHello<'_>) -> Option> { -// if let Either::Left(path) = self.tls_config.certs() { -// let metadata = tokio::fs::metadata(&path).await.ok()?; -// let modtime = metadata.modified().ok()?; -// let timestamp = modtime.duration_since(UNIX_EPOCH).ok()?.as_secs(); -// let old_timestamp = self.timestamp.load(Ordering::Acquire); -// if timestamp > old_timestamp { -// let new_config = self.tls_config.to_server_config().await.ok()?; -// self.server_config.store(Arc::new(new_config)); -// self.timestamp.store(timestamp, Ordering::Release); -// } -// } -// -// Some(self.server_config.load_full()) -// } -// } diff --git a/testbench/src/runner.rs b/testbench/src/runner.rs new file mode 100644 index 0000000000..bb69e66807 --- /dev/null +++ b/testbench/src/runner.rs @@ -0,0 +1,74 @@ +use std::time::Duration; + +use rocket::yansi::Paint; + +#[derive(Copy, Clone)] +pub struct Test { + pub name: &'static str, + pub run: fn(()) -> Result<(), String>, +} + +#[macro_export] +macro_rules! register { + ($f:ident $( ( $($v:ident: $a:expr),* ) )?) => { + ::inventory::submit!($crate::Test { + name: stringify!($f $(($($v = $a),*))?), + run: |_: ()| $f($($($a),*)?).map_err(|e| e.to_string()), + }); + }; +} + +inventory::collect!(Test); + +pub fn run() -> std::process::ExitCode { + procspawn::init(); + + let filter = std::env::args().nth(1).unwrap_or_default(); + let filtered = inventory::iter:: + .into_iter() + .filter(|t| t.name.contains(&filter)); + + let total_tests = inventory::iter::.into_iter().count(); + println!("running {}/{total_tests} tests", filtered.clone().count()); + let handles = filtered.map(|test| (test, std::thread::spawn(|| { + let name = test.name; + let start = std::time::SystemTime::now(); + let mut proc = procspawn::spawn((), test.run); + let result = loop { + match proc.join_timeout(Duration::from_secs(10)) { + Err(e) if e.is_timeout() => { + let elapsed = start.elapsed().unwrap().as_secs(); + println!("{name} has been running for {elapsed} seconds..."); + + if elapsed >= 30 { + println!("{name} timeout"); + break Err(e); + } + }, + result => break result, + } + }; + + match result.as_ref().map_err(|e| e.panic_info()) { + Ok(Ok(_)) => println!("test {name} ... {}", "ok".green()), + Ok(Err(e)) => println!("test {name} ... {}\n {e}", "fail".red()), + Err(Some(_)) => println!("test {name} ... {}", "panic".red().underline()), + Err(None) => println!("test {name} ... {}", "error".magenta()), + } + + matches!(result, Ok(Ok(()))) + }))); + + let mut success = true; + for (_, handle) in handles { + success &= handle.join().unwrap_or(false); + } + + match success { + true => std::process::ExitCode::SUCCESS, + false => { + println!("note: use `NOCAPTURE=1` to see test output"); + std::process::ExitCode::FAILURE + } + } +} diff --git a/testbench/src/servers/bind.rs b/testbench/src/servers/bind.rs new file mode 100644 index 0000000000..b9981b3cc7 --- /dev/null +++ b/testbench/src/servers/bind.rs @@ -0,0 +1,45 @@ +use rocket::{tokio::net::TcpListener}; + +use crate::prelude::*; + +#[cfg(unix)] +fn tcp_unix_listener_fail() -> Result<()> { + use rocket::listener::unix::UnixListener; + + let server = spawn! { + Rocket::default().reconfigure_with_toml("[default]\naddress = 123") + }; + + if let Err(Error::Liftoff(stdout, _)) = server { + assert!(stdout.contains("expected: valid TCP (ip) or unix (path)")); + assert!(stdout.contains("default.address")); + } else { + panic!("unexpected result: {server:#?}"); + } + + let server = Server::spawn((), |(token, _)| { + let rocket = Rocket::default().reconfigure_with_toml("[default]\naddress = \"unix:foo\""); + token.launch_with::(rocket) + }); + + if let Err(Error::Liftoff(stdout, _)) = server { + assert!(stdout.contains("invalid tcp endpoint: unix:foo")); + } else { + panic!("unexpected result: {server:#?}"); + } + + let server = Server::spawn((), |(token, _)| { + token.launch_with::(Rocket::default()) + }); + + if let Err(Error::Liftoff(stdout, _)) = server { + assert!(stdout.contains("invalid unix endpoint: tcp:127.0.0.1:8000")); + } else { + panic!("unexpected result: {server:#?}"); + } + + Ok(()) +} + +#[cfg(unix)] +register!(tcp_unix_listener_fail); diff --git a/testbench/src/servers/ignite_failure.rs b/testbench/src/servers/ignite_failure.rs new file mode 100644 index 0000000000..6a3da0cdd3 --- /dev/null +++ b/testbench/src/servers/ignite_failure.rs @@ -0,0 +1,19 @@ +use crate::prelude::*; + +fn test_ignite_failure() -> Result<()> { + let server = spawn! { + let fail = AdHoc::try_on_ignite("FailNow", |rocket| async { Err(rocket) }); + Rocket::default().attach(fail) + }; + + if let Err(Error::Liftoff(stdout, _)) = server { + assert!(stdout.contains("ignition failure")); + assert!(stdout.contains("FailNow")); + } else { + panic!("unexpected result: {server:#?}"); + } + + Ok(()) +} + +register!(test_ignite_failure); diff --git a/testbench/src/servers/infinite_stream.rs b/testbench/src/servers/infinite_stream.rs new file mode 100644 index 0000000000..b4a16914c2 --- /dev/null +++ b/testbench/src/servers/infinite_stream.rs @@ -0,0 +1,29 @@ +use crate::prelude::*; + +#[get("/")] +fn infinite() -> TextStream![&'static str] { + TextStream! { + loop { + yield rocket::futures::future::pending::<&str>().await; + } + } +} + +pub fn test_inifinite_streams_end() -> Result<()> { + let mut server = spawn! { + Rocket::default().mount("/", routes![infinite]) + }?; + + let client = Client::default(); + client.get(&server, "/")?.send()?; + server.terminate()?; + + let stdout = server.read_stdout()?; + assert!(stdout.contains("Rocket has launched on http")); + assert!(stdout.contains("GET /")); + assert!(stdout.contains("Graceful shutdown completed")); + + Ok(()) +} + +register!(test_inifinite_streams_end); diff --git a/testbench/src/servers/mod.rs b/testbench/src/servers/mod.rs new file mode 100644 index 0000000000..30fd6ff97c --- /dev/null +++ b/testbench/src/servers/mod.rs @@ -0,0 +1,8 @@ +pub mod ignite_failure; +pub mod bind; +pub mod infinite_stream; +pub mod tls_resolver; +pub mod mtls; +pub mod sni_resolver; +pub mod tracing; +pub mod tls; diff --git a/testbench/src/servers/mtls.rs b/testbench/src/servers/mtls.rs new file mode 100644 index 0000000000..1fdaea7ae0 --- /dev/null +++ b/testbench/src/servers/mtls.rs @@ -0,0 +1,50 @@ +use crate::prelude::*; + +fn test_mtls(mandatory: bool) -> Result<()> { + let server = spawn!(mandatory: bool => { + let mtls_config = format!(r#" + [default.tls.mutual] + ca_certs = "{{ROCKET}}/examples/tls/private/ca_cert.pem" + mandatory = {mandatory} + "#); + + #[get("/")] + fn hello(cert: rocket::mtls::Certificate<'_>) -> String { + format!("{}:{}[{}] {}", cert.serial(), cert.version(), cert.issuer(), cert.subject()) + } + + #[get("/", rank = 2)] + fn hi() -> &'static str { + "Hello!" + } + + Rocket::tls_default() + .reconfigure_with_toml(&mtls_config) + .mount("/", routes![hello, hi]) + })?; + + let pem = read("{ROCKET}/examples/tls/private/client.pem")?; + let client: Client = Client::build() + .identity(reqwest::Identity::from_pem(&pem)?) + .try_into()?; + + let response = client.get(&server, "/")?.send()?; + assert_eq!(response.text()?, + "611895682361338926795452113263857440769284805738:2\ + [C=US, ST=CA, O=Rocket CA, CN=Rocket Root CA] \ + C=US, ST=California, L=Silicon Valley, O=Rocket, \ + CN=Rocket TLS Example, Email=example@rocket.local"); + + let client = Client::default(); + let response = client.get(&server, "/")?.send(); + if mandatory { + assert!(response.unwrap_err().is_request()); + } else { + assert_eq!(response?.text()?, "Hello!"); + } + + Ok(()) +} + +register!(test_mtls(mandatory: true)); +register!(test_mtls(mandatory: false)); diff --git a/testbench/src/servers/sni_resolver.rs b/testbench/src/servers/sni_resolver.rs new file mode 100644 index 0000000000..cb38993258 --- /dev/null +++ b/testbench/src/servers/sni_resolver.rs @@ -0,0 +1,136 @@ +use std::sync::Arc; +use std::collections::HashMap; +use std::sync::atomic::{Ordering, AtomicUsize}; + +use rocket::http::uri::Host; +use rocket::tls::{Resolver, TlsConfig, ClientHello, ServerConfig}; +use reqwest::tls::TlsInfo; + +use crate::prelude::*; + +static SNI_TLS_CONFIG: &str = r#" + [default.tls] + certs = "{ROCKET}/examples/tls/private/rsa_sha256_cert.pem" + key = "{ROCKET}/examples/tls/private/rsa_sha256_key.pem" + + [default.tls.sni."sni1.dev"] + certs = "{ROCKET}/examples/tls/private/ecdsa_nistp256_sha256_cert.pem" + key = "{ROCKET}/examples/tls/private/ecdsa_nistp256_sha256_key_pkcs8.pem" + + [default.tls.sni."sni2.dev"] + certs = "{ROCKET}/examples/tls/private/ed25519_cert.pem" + key = "{ROCKET}/examples/tls/private/ed25519_key.pem" +"#; + +struct SniResolver { + default: Arc, + map: HashMap, Arc> +} + +#[rocket::async_trait] +impl Resolver for SniResolver { + async fn init(rocket: &Rocket) -> rocket::tls::Result { + let default: TlsConfig = rocket.figment().extract_inner("tls")?; + let sni: HashMap, TlsConfig> = rocket.figment().extract_inner("tls.sni")?; + + let default = Arc::new(default.server_config().await?); + let mut map = HashMap::new(); + for (host, config) in sni { + let config = config.server_config().await?; + map.insert(host, Arc::new(config)); + } + + Ok(SniResolver { default, map }) + } + + async fn resolve(&self, hello: ClientHello<'_>) -> Option> { + if let Some(Ok(host)) = hello.server_name().map(Host::parse) { + if let Some(config) = self.map.get(&host) { + return Some(config.clone()); + } + } + + Some(self.default.clone()) + } +} + +fn sni_resolver() -> Result<()> { + let server = spawn! { + #[get("/")] fn index() { } + + Rocket::default() + .reconfigure_with_toml(SNI_TLS_CONFIG) + .mount("/", routes![index]) + .attach(SniResolver::fairing()) + }?; + + let client: Client = Client::build() + .resolve("unknown.dev", server.socket_addr()) + .resolve("sni1.dev", server.socket_addr()) + .resolve("sni2.dev", server.socket_addr()) + .try_into()?; + + let response = client.get(&server, "https://unknown.dev")?.send()?; + let tls = response.extensions().get::().unwrap(); + let expected = cert("{ROCKET}/examples/tls/private/rsa_sha256_cert.pem")?; + assert_eq!(tls.peer_certificate().unwrap(), expected); + + let response = client.get(&server, "https://sni1.dev")?.send()?; + let tls = response.extensions().get::().unwrap(); + let expected = cert("{ROCKET}/examples/tls/private/ecdsa_nistp256_sha256_cert.pem")?; + assert_eq!(tls.peer_certificate().unwrap(), expected); + + let response = client.get(&server, "https://sni2.dev")?.send()?; + let tls = response.extensions().get::().unwrap(); + let expected = cert("{ROCKET}/examples/tls/private/ed25519_cert.pem")?; + assert_eq!(tls.peer_certificate().unwrap(), expected); + Ok(()) +} + +struct CountingResolver { + config: Arc, + counter: Arc, +} + +#[rocket::async_trait] +impl Resolver for CountingResolver { + async fn init(rocket: &Rocket) -> rocket::tls::Result { + let config: TlsConfig = rocket.figment().extract_inner("tls")?; + let config = Arc::new(config.server_config().await?); + let counter = rocket.state::>().unwrap().clone(); + Ok(Self { config, counter }) + } + + async fn resolve(&self, _: ClientHello<'_>) -> Option> { + self.counter.fetch_add(1, Ordering::Release); + Some(self.config.clone()) + } +} + +#[get("/count")] +fn count(counter: &State>) -> String { + counter.load(Ordering::Acquire).to_string() +} + +fn counting_resolver() -> Result<()> { + let server = spawn! { + let counter = Arc::new(AtomicUsize::new(0)); + Rocket::tls_default() + .manage(counter) + .mount("/", routes![count]) + .attach(CountingResolver::fairing()) + }?; + + let client = Client::default(); + let response = client.get(&server, "/count")?.send()?; + assert_eq!(response.text()?, "1"); + + // Use a new client so we get a new TLS session. + let client = Client::default(); + let response = client.get(&server, "/count")?.send()?; + assert_eq!(response.text()?, "2"); + Ok(()) +} + +register!(counting_resolver); +register!(sni_resolver); diff --git a/testbench/src/servers/tls.rs b/testbench/src/servers/tls.rs new file mode 100644 index 0000000000..7288594588 --- /dev/null +++ b/testbench/src/servers/tls.rs @@ -0,0 +1,58 @@ +use crate::prelude::*; + +use std::net::{Ipv4Addr, SocketAddr}; + +use rocket::tokio::net::TcpListener; +use rocket::{get, routes, Rocket}; +use rocket::listener::Endpoint; +use rocket::tls::TlsListener; + +use reqwest::tls::TlsInfo; + +#[get("/")] +fn hello_world(endpoint: &Endpoint) -> String { + format!("Hello, {endpoint}!") +} + +fn test_tls_works() -> Result<()> { + let mut server = spawn! { + Rocket::tls_default().mount("/", routes![hello_world]) + }?; + + let client = Client::default(); + let response = client.get(&server, "/")?.send()?; + let tls = response.extensions().get::().unwrap(); + assert!(!tls.peer_certificate().unwrap().is_empty()); + assert!(response.text()?.starts_with("Hello, https://127.0.0.1")); + + server.terminate()?; + let stdout = server.read_stdout()?; + assert!(stdout.contains("Rocket has launched on https")); + assert!(stdout.contains("Graceful shutdown completed")); + assert!(stdout.contains("GET /")); + + let server = Server::spawn((), |(token, _)| { + let rocket = rocket::build() + .reconfigure_with_toml(TLS_CONFIG) + .mount("/", routes![hello_world]); + + token.with_launch(rocket, |rocket| { + let config = rocket.figment().extract_inner("tls"); + rocket.try_launch_on(async move { + let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 0); + let listener = TcpListener::bind(addr).await?; + TlsListener::from(listener, config?).await + }) + }) + }).unwrap(); + + let client = Client::default(); + let response = client.get(&server, "/")?.send()?; + let tls = response.extensions().get::().unwrap(); + assert!(!tls.peer_certificate().unwrap().is_empty()); + assert!(response.text()?.starts_with("Hello, https://127.0.0.1")); + + Ok(()) +} + +register!(test_tls_works); diff --git a/testbench/src/servers/tls_resolver.rs b/testbench/src/servers/tls_resolver.rs new file mode 100644 index 0000000000..af23d1157c --- /dev/null +++ b/testbench/src/servers/tls_resolver.rs @@ -0,0 +1,80 @@ +use crate::prelude::*; + +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use rocket::tls::{ClientHello, Resolver, ServerConfig, TlsConfig}; + +struct CountingResolver { + config: Arc, + counter: Arc, +} + +#[rocket::async_trait] +impl Resolver for CountingResolver { + async fn init(rocket: &Rocket) -> rocket::tls::Result { + let config: TlsConfig = rocket.figment().extract_inner("tls")?; + let config = Arc::new(config.server_config().await?); + let counter = rocket.state::>().unwrap().clone(); + Ok(Self { config, counter }) + } + + async fn resolve(&self, _: ClientHello<'_>) -> Option> { + self.counter.fetch_add(1, Ordering::Release); + Some(self.config.clone()) + } +} + +#[get("/count")] +fn count(counter: &State>) -> String { + counter.load(Ordering::Acquire).to_string() +} + +fn test_tls_resolver() -> Result<()> { + let server = spawn! { + let counter = Arc::new(AtomicUsize::new(0)); + Rocket::tls_default() + .manage(counter) + .mount("/", routes![count]) + .attach(CountingResolver::fairing()) + }?; + + let client = Client::default(); + let response = client.get(&server, "/count")?.send()?; + assert_eq!(response.text()?, "1"); + + // Use a new client so we get a new TLS session. + let client = Client::default(); + let response = client.get(&server, "/count")?.send()?; + assert_eq!(response.text()?, "2"); + Ok(()) +} + +register!(test_tls_resolver); + +// TODO: Implement an `UpdatingResolver`. Expose `SniResolver` and +// `UpdatingResolver` in a `contrib` library or as part of `rocket`. +// +// struct UpdatingResolver { +// timestamp: AtomicU64, +// config: ArcSwap +// } +// +// #[crate::async_trait] +// impl Resolver for UpdatingResolver { +// async fn resolve(&self, _: ClientHello<'_>) -> Option> { +// if let Either::Left(path) = self.tls_config.certs() { +// let metadata = tokio::fs::metadata(&path).await.ok()?; +// let modtime = metadata.modified().ok()?; +// let timestamp = modtime.duration_since(UNIX_EPOCH).ok()?.as_secs(); +// let old_timestamp = self.timestamp.load(Ordering::Acquire); +// if timestamp > old_timestamp { +// let new_config = self.tls_config.to_server_config().await.ok()?; +// self.server_config.store(Arc::new(new_config)); +// self.timestamp.store(timestamp, Ordering::Release); +// } +// } +// +// Some(self.server_config.load_full()) +// } +// } diff --git a/testbench/src/servers/tracing.rs b/testbench/src/servers/tracing.rs new file mode 100644 index 0000000000..b4ada1b3fa --- /dev/null +++ b/testbench/src/servers/tracing.rs @@ -0,0 +1,123 @@ +//! Check that guard failures result in trace with `Display` message for guard +//! types that implement `Display` and otherwise uses `Debug`. + +use std::fmt; + +use rocket::http::Status; +use rocket::data::{self, FromData}; +use rocket::http::uri::{Segments, fmt::Path}; +use rocket::request::{self, FromParam, FromRequest, FromSegments}; + +use crate::prelude::*; + +#[derive(Debug)] +struct UseDisplay(&'static str); + +#[derive(Debug)] +struct UseDebug; + +impl fmt::Display for UseDisplay { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "this is the display impl: {}", self.0) + } +} + +impl FromParam<'_> for UseDisplay { + type Error = Self; + fn from_param(_: &str) -> Result { Err(Self("param")) } +} + +impl FromParam<'_> for UseDebug { + type Error = Self; + fn from_param(_: &str) -> Result { Err(Self) } +} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for UseDisplay { + type Error = Self; + async fn from_request(_: &'r Request<'_>) -> request::Outcome { + request::Outcome::Error((Status::InternalServerError, Self("req"))) + } +} + +#[rocket::async_trait] +impl<'r> FromRequest<'r> for UseDebug { + type Error = Self; + async fn from_request(_: &'r Request<'_>) -> request::Outcome { + request::Outcome::Error((Status::InternalServerError, Self)) + } +} + +#[rocket::async_trait] +impl<'r> FromData<'r> for UseDisplay { + type Error = Self; + async fn from_data(_: &'r Request<'_>, _: Data<'r>) -> data::Outcome<'r, Self> { + data::Outcome::Error((Status::InternalServerError, Self("data"))) + } +} + +#[rocket::async_trait] +impl<'r> FromData<'r> for UseDebug { + type Error = Self; + async fn from_data(_: &'r Request<'_>, _: Data<'r>) -> data::Outcome<'r, Self> { + data::Outcome::Error((Status::InternalServerError, Self)) + } +} + +impl<'r> FromSegments<'r> for UseDisplay { + type Error = Self; + fn from_segments(_: Segments<'r, Path>) -> Result { Err(Self("segment")) } +} + +impl<'r> FromSegments<'r> for UseDebug { + type Error = Self; + fn from_segments(_: Segments<'r, Path>) -> Result { Err(Self) } +} + +pub fn test_display_guard_err() -> Result<()> { + #[get("/<_v>", rank = 1)] fn a(_v: UseDisplay) {} + #[get("/<_v..>", rank = 2)] fn b(_v: UseDisplay) {} + #[get("/<_..>", rank = 3)] fn d(_v: UseDisplay) {} + #[post("/<_..>", data = "<_v>")] fn c(_v: UseDisplay) {} + + let mut server = spawn! { + Rocket::default().mount("/", routes![a, b, c, d]) + }?; + + let client = Client::default(); + client.get(&server, "/foo")?.send()?; + client.post(&server, "/foo")?.send()?; + server.terminate()?; + + let stdout = server.read_stdout()?; + assert!(stdout.contains("this is the display impl: param")); + assert!(stdout.contains("this is the display impl: req")); + assert!(stdout.contains("this is the display impl: segment")); + assert!(stdout.contains("this is the display impl: data")); + + Ok(()) +} + +pub fn test_debug_guard_err() -> Result<()> { + #[get("/<_v>", rank = 1)] fn a(_v: UseDebug) {} + #[get("/<_v..>", rank = 2)] fn b(_v: UseDebug) {} + #[get("/<_..>", rank = 3)] fn d(_v: UseDebug) {} + #[post("/<_..>", data = "<_v>")] fn c(_v: UseDebug) {} + + let mut server = spawn! { + Rocket::default().mount("/", routes![a, b, c, d]) + }?; + + let client = Client::default(); + client.get(&server, "/foo")?.send()?; + client.post(&server, "/foo")?.send()?; + server.terminate()?; + + let stdout = server.read_stdout()?; + assert!(!stdout.contains("this is the display impl")); + assert!(stdout.contains("UseDebug")); + Ok(()) +} + +register!(test_display_guard_err); +register!(test_debug_guard_err);