Finalize 'tracing' migration.

This commit complete the migration to 'tracing' for all logging. Below
is a summary of all relevant commits, including this one:

Log improvements:
  - All log (trace) messages are structured which means they contain fields
    that can formatted by any subscriber.
  - Logging can be disabled entirely by disabling the default `trace` feature.
  - Routes and catchers now contain location (file/line) information.
  - Two log format kinds: pretty and compact via ROCKET_LOG_FORMAT
  - Coloring is not disabled globally. Thus applications can color even if
    Rocket is configured not to.
  - Rocket is more conservative about 'warn' and 'error' messages, reserving
    those log levels for messages useful in production.
  - Errors from guards logged by codegen now use the 'Display' implementation of
    those errors when one exists.
  - Secrets are never logged, even when directly asked for.

New features:
  - Many Rocket types know how to trace themselves via a new `Trace` trait.
  - `Either` types can now be used in `uri!()` calls.
  - A `RequestIdLayer` tags all requests with a unique ID.

Breaking changes to configuration:
  - `Config::log_level` is of type `Option<Level>`. `None` disables tracing.
  - `log_level` now uses the traditional log level names: "off", "error",
    "warn", "info", "debug", "trace", or 0-5. This replace the Rocket-specific
    "normal", "debug", "critical".
  - A new option, `log_format`, which is either `compact` or `pretty`,
    determines how Rocket's tracing subscriber log trace messages.

Breaking changes:
  - Hidden `rocket::Either` is now publicly available at `rocket::either::Either`.
  - `rocket::Error` no longer panics when dropped.
  - `main` generated by `#[launch]` returns an `ExitCode`.
  - `FromParam` `Err` now always returns the actual error as opposed to the
    string that failed to parse. To recover the original string, use `Either<T,
    &str>`, where `T: FromParam`, as a parameter guard.
  - Many types that implemented `Display` now instead implement `Trace`.
  - `Error::pretty_print()` was removed. Use `Error::trace()` via `Trace` impl.

Internal improvements:
  - Made more space in CI machines for tasks.
  - Cleaned up testbench code using `inventory`.

Resolves #21.
This commit is contained in:
Sergio Benitez 2024-05-16 20:23:26 -07:00
parent 45264de8c9
commit 926e06ef3c
66 changed files with 1256 additions and 823 deletions

View File

@ -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<L
fn client(routes: Vec<Route>) -> 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()

View File

@ -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

View File

@ -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<D> = <<D as sqlx::Database>::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::<LogLevel>(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)

View File

@ -42,20 +42,20 @@ impl Engine for Environment<'static> {
Some(env)
}
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> {
let Ok(template) = self.get_template(name) else {
error!("Minijinja template '{name}' was not found.");
fn render<C: Serialize>(&self, template: &str, context: C) -> Option<String> {
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();
}
});

View File

@ -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}");

View File

@ -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::<ContextManager>()
.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);
});

View File

@ -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
})?;

View File

@ -68,7 +68,7 @@ impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
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<K: 'static, C: Poolable> ConnectionPool<K, C> {
_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)) => {

View File

@ -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!()),
}
}

View File

@ -67,8 +67,7 @@ impl EntryAttr for Launch {
// Always infer the type as `Rocket<Build>`.
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() =>

View File

@ -105,9 +105,10 @@ fn query_decls(route: &Route) -> Option<TokenStream> {
)*
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<TokenStream> {
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<TokenStream> {
format: #format,
rank: #rank,
sentinels: #sentinels,
location: (::core::file!(), ::core::line!(), ::core::column!()),
}
}

View File

@ -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,

View File

@ -1328,7 +1328,7 @@ pub fn catchers(input: TokenStream) -> TokenStream {
/// assert_eq!(bob2.to_string(), "/person/Bob%20Smith");
///
/// #[get("/person/<age>")]
/// fn ok(age: Result<u8, &str>) { }
/// fn ok(age: Result<u8, std::num::ParseIntError>) { }
///
/// let kid1 = uri!(ok(age = 10));
/// let kid2 = uri!(ok(12));

View File

@ -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<usize, &str>) { }
assert_uri_eq! {
uri!(f(Left::<usize, &str>(123))) => "/123",
uri!(f(_foo = Left::<usize, &str>(710))) => "/710",
uri!(f(Right::<usize, &str>("hello world"))) => "/hello%20world",
uri!(f(_foo = Right::<usize, &str>("bye?"))) => "/bye%3F",
}
}

View File

@ -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<Build>`
--> tests/ui-fail-nightly/async-entry.rs:94:20
|

View File

@ -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<i128>
NonZero<i16>
NonZero<i32>
NonZero<i64>
<&'a str as FromParam<'a>>
<IpAddr as FromParam<'a>>
<Ipv4Addr as FromParam<'a>>
<Ipv6Addr as FromParam<'a>>
<NonZero<i128> as FromParam<'a>>
<NonZero<i16> as FromParam<'a>>
<NonZero<i32> as FromParam<'a>>
<NonZero<i64> 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<i128>
NonZero<i16>
NonZero<i32>
NonZero<i64>
<&'a str as FromParam<'a>>
<IpAddr as FromParam<'a>>
<Ipv4Addr as FromParam<'a>>
<Ipv6Addr as FromParam<'a>>
<NonZero<i128> as FromParam<'a>>
<NonZero<i16> as FromParam<'a>>
<NonZero<i32> as FromParam<'a>>
<NonZero<i64> 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<i128>
NonZero<i16>
NonZero<i32>
NonZero<i64>
<&'a str as FromParam<'a>>
<IpAddr as FromParam<'a>>
<Ipv4Addr as FromParam<'a>>
<Ipv6Addr as FromParam<'a>>
<NonZero<i128> as FromParam<'a>>
<NonZero<i16> as FromParam<'a>>
<NonZero<i32> as FromParam<'a>>
<NonZero<i64> 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<i128>
NonZero<i16>
NonZero<i32>
NonZero<i64>
<&'a str as FromParam<'a>>
<IpAddr as FromParam<'a>>
<Ipv4Addr as FromParam<'a>>
<Ipv6Addr as FromParam<'a>>
<NonZero<i128> as FromParam<'a>>
<NonZero<i16> as FromParam<'a>>
<NonZero<i32> as FromParam<'a>>
<NonZero<i64> as FromParam<'a>>
and $N others

View File

@ -86,7 +86,7 @@ error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Path, _>`
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut &'a std::path::Path>>
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>>
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<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` 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<rocket::http::uri::fmt::Query, _>
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut &'a std::path::Path>>
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>>
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<rocket::http::uri::fmt::Query, _>` 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<rocket::http::uri::fmt::Query, _>
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut &'a std::path::Path>>
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>>
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<rocket::http::uri::fmt::Query>` 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<rocket::http::uri::fmt::Query, _>
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut &'a std::path::Path>>
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>>
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<rocket::http::uri::fmt::Path, &str>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, &'x mut usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, usize>>
= 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<rocket::http::uri::fmt::Path, &str>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, &'x mut usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, usize>>
= 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<rocket::http::uri::fmt::Path, i64>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, &'x mut usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, usize>>
= 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<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` 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<rocket::http::uri::fmt::Path, s
<i32 as FromUriParam<P, &'x mut i32>>
<i32 as FromUriParam<P, i32>>
= note: required for `std::option::Option<i32>` to implement `FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>`
= 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<rocket::http::uri::fmt::Query, &str>` 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<rocket::http::uri::fmt::Query
<isize as FromUriParam<P, &'x isize>>
<isize as FromUriParam<P, &'x mut isize>>
<isize as FromUriParam<P, isize>>
= 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<rocket::http::uri::fmt::Query, &str>` 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<rocket::http::uri::fmt::Query
<isize as FromUriParam<P, &'x isize>>
<isize as FromUriParam<P, &'x mut isize>>
<isize as FromUriParam<P, isize>>
= 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<rocket::http::uri::fmt::Path, &str>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, &'x mut usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, usize>>
= 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<rocket::http::uri::fmt::Path, &str>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, &'x mut usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, usize>>
= 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<rocket::http::uri::fmt::Path, &str>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, &'x mut usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, usize>>
= 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<rocket::http::uri::fmt::Path, &str>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, &'x mut usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, usize>>
= 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)

View File

@ -10,7 +10,7 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied
<Box<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>>
<rocket::Either<T, E> as Responder<'r, 'o>>
<rocket::either::Either<T, E> as Responder<'r, 'o>>
<Cow<'o, R> as Responder<'r, 'o>>
<rocket::tokio::fs::File as Responder<'r, 'static>>
<EventStream<S> as Responder<'r, 'r>>
@ -29,7 +29,7 @@ error[E0277]: the trait bound `bool: Responder<'_, '_>` is not satisfied
<Box<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>>
<rocket::Either<T, E> as Responder<'r, 'o>>
<rocket::either::Either<T, E> as Responder<'r, 'o>>
<Cow<'o, R> as Responder<'r, 'o>>
<rocket::tokio::fs::File as Responder<'r, 'static>>
<EventStream<S> as Responder<'r, 'r>>
@ -62,7 +62,7 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied
<Box<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>>
<rocket::Either<T, E> as Responder<'r, 'o>>
<rocket::either::Either<T, E> as Responder<'r, 'o>>
<Cow<'o, R> as Responder<'r, 'o>>
<rocket::tokio::fs::File as Responder<'r, 'static>>
<EventStream<S> as Responder<'r, 'r>>
@ -81,7 +81,7 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied
<Box<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>>
<rocket::Either<T, E> as Responder<'r, 'o>>
<rocket::either::Either<T, E> as Responder<'r, 'o>>
<Cow<'o, R> as Responder<'r, 'o>>
<rocket::tokio::fs::File as Responder<'r, 'static>>
<EventStream<S> as Responder<'r, 'r>>

View File

@ -137,9 +137,9 @@ error[E0277]: the trait bound `Unknown: FromForm<'r>` is not satisfied
<HashMap<K, V> as FromForm<'v>>
<BTreeMap<K, V> as FromForm<'v>>
<Arc<T> as FromForm<'v>>
<Vec<T> as FromForm<'v>>
<form::from_form::_::proxy::Range<T> as FromForm<'r>>
<form::from_form::_::proxy::RangeFrom<T> as FromForm<'r>>
<form::from_form::_::proxy::RangeTo<T> as FromForm<'r>>
and $N others
= note: required for `Unknown` to implement `FromForm<'r>`
@ -196,9 +196,9 @@ error[E0277]: the trait bound `Foo<usize>: FromForm<'r>` is not satisfied
<HashMap<K, V> as FromForm<'v>>
<BTreeMap<K, V> as FromForm<'v>>
<Arc<T> as FromForm<'v>>
<Vec<T> as FromForm<'v>>
<form::from_form::_::proxy::Range<T> as FromForm<'r>>
<form::from_form::_::proxy::RangeFrom<T> as FromForm<'r>>
<form::from_form::_::proxy::RangeTo<T> as FromForm<'r>>
and $N others
= note: required for `Foo<usize>` to implement `FromForm<'r>`

View File

@ -12,7 +12,7 @@ error[E0277]: the trait bound `u8: Responder<'_, '_>` is not satisfied
<Box<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>>
<rocket::Either<T, E> as Responder<'r, 'o>>
<rocket::either::Either<T, E> as Responder<'r, 'o>>
and $N others
error[E0277]: the trait bound `Header<'_>: From<u8>` is not satisfied
@ -52,7 +52,7 @@ error[E0277]: the trait bound `u8: Responder<'_, '_>` is not satisfied
<Box<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>>
<rocket::Either<T, E> as Responder<'r, 'o>>
<rocket::either::Either<T, E> as Responder<'r, 'o>>
and $N others
error[E0277]: the trait bound `Header<'_>: From<u8>` is not satisfied
@ -117,7 +117,7 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied
<Box<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>>
<rocket::Either<T, E> as Responder<'r, 'o>>
<rocket::either::Either<T, E> as Responder<'r, 'o>>
and $N others
note: required by a bound in `route::handler::<impl Outcome<Response<'o>, Status, (rocket::Data<'o>, Status)>>::from`
--> $WORKSPACE/core/lib/src/route/handler.rs

View File

@ -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
<bool as FromParam<'a>>
<isize as FromParam<'a>>
<i8 as FromParam<'a>>
<i16 as FromParam<'a>>
<i32 as FromParam<'a>>
<i64 as FromParam<'a>>
<i128 as FromParam<'a>>
<usize 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>`:
bool
isize
i8
i16
i32
i64
i128
usize
<bool as FromParam<'a>>
<isize as FromParam<'a>>
<i8 as FromParam<'a>>
<i16 as FromParam<'a>>
<i32 as FromParam<'a>>
<i64 as FromParam<'a>>
<i128 as FromParam<'a>>
<usize 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>`:
bool
isize
i8
i16
i32
i64
i128
usize
<bool as FromParam<'a>>
<isize as FromParam<'a>>
<i8 as FromParam<'a>>
<i16 as FromParam<'a>>
<i32 as FromParam<'a>>
<i64 as FromParam<'a>>
<i128 as FromParam<'a>>
<usize 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>`:
bool
isize
i8
i16
i32
i64
i128
usize
<bool as FromParam<'a>>
<isize as FromParam<'a>>
<i8 as FromParam<'a>>
<i16 as FromParam<'a>>
<i32 as FromParam<'a>>
<i64 as FromParam<'a>>
<i128 as FromParam<'a>>
<usize as FromParam<'a>>
and $N others

View File

@ -86,7 +86,7 @@ error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Path, _>`
<i8 as FromUriParam<P, i8>>
<i8 as FromUriParam<P, &'x i8>>
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<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` 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<rocket::http::uri::fmt::Query, _>
<i8 as FromUriParam<P, i8>>
<i8 as FromUriParam<P, &'x i8>>
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<rocket::http::uri::fmt::Query, _>` 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<rocket::http::uri::fmt::Query, _>
<i8 as FromUriParam<P, i8>>
<i8 as FromUriParam<P, &'x i8>>
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<rocket::http::uri::fmt::Query>` 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<rocket::http::uri::fmt::Query, _>
<i8 as FromUriParam<P, i8>>
<i8 as FromUriParam<P, &'x i8>>
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<rocket::http::uri::fmt::Path, &str>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut usize>>
= 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<rocket::http::uri::fmt::Path, &str>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut usize>>
= 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<rocket::http::uri::fmt::Path, i64>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut usize>>
= 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<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` 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<rocket::http::uri::fmt::Path, s
<i32 as FromUriParam<P, &'x i32>>
<i32 as FromUriParam<P, &'x mut i32>>
= note: required for `std::option::Option<i32>` to implement `FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>`
= 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<rocket::http::uri::fmt::Query, &str>` 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<rocket::http::uri::fmt::Query
<isize as FromUriParam<P, isize>>
<isize as FromUriParam<P, &'x isize>>
<isize as FromUriParam<P, &'x mut isize>>
= 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<rocket::http::uri::fmt::Query, &str>` 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<rocket::http::uri::fmt::Query
<isize as FromUriParam<P, isize>>
<isize as FromUriParam<P, &'x isize>>
<isize as FromUriParam<P, &'x mut isize>>
= 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<rocket::http::uri::fmt::Path, &str>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut usize>>
= 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<rocket::http::uri::fmt::Path, &str>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut usize>>
= 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<rocket::http::uri::fmt::Path, &str>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut usize>>
= 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<rocket::http::uri::fmt::Path, &str>` 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<rocket::http::uri::fmt::Path,
<usize as FromUriParam<P, usize>>
<usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut usize>>
= 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)

View File

@ -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<str>`
/// `Ipv4Addr`, `Ipv6Addr`, `&str`, `Cow<str>`, `Either<A, B>`
///
/// The following types have _identity_ implementations _only in [`Path`]_:
///
@ -375,7 +377,9 @@ impl<A, T: FromUriParam<fmt::Path, A>> FromUriParam<fmt::Path, A> for Option<T>
}
/// A no cost conversion allowing `T` to be used in place of an `Result<T, E>`.
impl<A, E, T: FromUriParam<fmt::Path, A>> FromUriParam<fmt::Path, A> for Result<T, E> {
impl<A, E, T> FromUriParam<fmt::Path, A> for Result<T, E>
where T: FromUriParam<fmt::Path, A>
{
type Target = T::Target;
#[inline(always)]
@ -384,6 +388,19 @@ impl<A, E, T: FromUriParam<fmt::Path, A>> FromUriParam<fmt::Path, A> for Result<
}
}
impl<P: Part, A, B, T, U> FromUriParam<P, Either<A, B>> for Either<T, U>
where T: FromUriParam<P, A>, U: FromUriParam<P, B>
{
type Target = Either<T::Target, U::Target>;
fn from_uri_param(param: Either<A, B>) -> 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<A, T: FromUriParam<fmt::Query, A>> FromUriParam<fmt::Query, Option<A>> for Option<T> {
type Target = Option<T::Target>;

View File

@ -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<P: Part, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &T {
}
}
/// Defers to `T` or `U` in `Either<T, U>`.
impl<P: Part, T: UriDisplay<P>, U: UriDisplay<P>> UriDisplay<P> for Either<T, U> {
#[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<P>` implementation for `T`.
impl<P: Part, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &mut T {
#[inline(always)]

View File

@ -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()
});

View File

@ -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<u16>,
/// 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<StaticInfo> 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
}
}

View File

@ -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<T: Provider>(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<T>(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()
}

View File

@ -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};

View File

@ -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::<hyper::Error>().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<T: ?Sized>(pub T);
pub trait DefaultDisplay {
fn display(&self) -> impl fmt::Display;
}
/// Blanket implementation for `T: Debug`. This is what Rust will resolve
/// `DisplayHack<T>::display` to when `T: Debug`.
impl<T: fmt::Debug + ?Sized> DefaultDisplay for DisplayHack<T> {
#[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<T>::display` to when `T: Display`.
impl<T: fmt::Display + fmt::Debug + ?Sized> DisplayHack<T> {
#[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;

View File

@ -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::<T>() {
Ok(config) => config,
Err(e) => {
crate::config::pretty_print_error(e);
e.trace_error();
return Err(rocket);
}
};

View File

@ -51,16 +51,6 @@ impl Fairings {
iter!(self, self.active().collect::<HashSet<_>>().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<dyn Fairing>) {

View File

@ -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<R>(fut: impl std::future::Future<Output = R>) -> R {
/// WARNING: This is unstable! Do not use this method outside of Rocket!
#[doc(hidden)]
pub fn async_main<R>(fut: impl std::future::Future<Output = R> + Send) -> R {
fn bail<T, E: crate::trace::Trace>(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<R>(fut: impl std::future::Future<Output = R> + 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);

View File

@ -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<Orbit> {
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<Orbit> {
// 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<Orbit> {
}
}
let outcome = Outcome::Forward((data, status));
outcome.trace_info();
outcome
Outcome::Forward((data, status))
}
// Invokes the catcher for `status`. Returns the response on success.

View File

@ -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)?

View File

@ -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<T>` or `Result<T, T::Error>` 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<T>`, `Result<T, T::Error>`, or
/// `Either<A, B>` can be used, which implement `FromParam` themselves.
///
/// For instance, imagine you've asked for an `<id>` as a `usize`. To determine
/// when the `<id>` was not a valid `usize` and retrieve the string that failed
/// to parse, you can use a `Result<usize, &str>` type for the `<id>` parameter
/// as follows:
/// * **`Option<T>`** _where_ **`T: FromParam`**
///
/// Always returns successfully.
///
/// If the conversion to `T` fails, `None` is returned. If the conversion
/// succeeds, `Some(value)` is returned.
///
/// * **`Result<T, T::Error>`** _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<A, B>`** _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<A, B>` 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("/<id>")]
/// fn hello(id: Result<usize, &str>) -> String {
/// fn hello(id: Either<usize, &str>) -> 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<usize, &str>` 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<Self, Self::Error> {
<$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<T> {
}
}
}
/// Implements `FromParam` for `Either<A, B>`, 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<A, B> {
type Error = (A::Error, B::Error);
#[inline(always)]
fn from_param(param: &'v str) -> Result<Self, Self::Error> {
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)),
}
}
}
}

View File

@ -484,7 +484,8 @@ impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Option<R> {
match self {
Some(r) => r.respond_to(req),
None => {
debug!("{} responder returned `None`", std::any::type_name::<Self>());
let type_name = std::any::type_name::<Self>();
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<T, E>
/// 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<T, E>
impl<'r, 'o: 'r, 't: 'o, 'e: 'o, T, E> Responder<'r, 'o> for either::Either<T, E>
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),
}
}
}

View File

@ -185,16 +185,9 @@ impl Rocket<Build> {
/// ```
#[must_use]
pub fn custom<T: Provider>(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<Build> = Rocket(Building {
figment: Figment::from(provider),
..Default::default()
});
rocket.attach(Shield::default())
Rocket::<Build>(Building::default())
.reconfigure(provider)
.attach(Shield::default())
}
/// Overrides the current configuration provider with `provider`.
@ -237,7 +230,12 @@ impl Rocket<Build> {
/// ```
#[must_use]
pub fn reconfigure<T: Provider>(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<Build> {
// 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<Ignite> = Rocket(Igniting {
@ -592,19 +590,6 @@ impl Rocket<Build> {
}
}
#[tracing::instrument(name = "items", skip_all, fields(kind = kind))]
fn log_items<T, I, B, O>(kind: &str, items: I, base: B, origin: O)
where T: fmt::Display + Copy, I: Iterator<Item = T>,
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<Ignite> {
/// Returns the finalized, active configuration. This is guaranteed to
/// remain stable through ignition and into orbit.

View File

@ -176,6 +176,8 @@ pub struct Route {
pub format: Option<MediaType>,
/// The discovered sentinels.
pub(crate) sentinels: Vec<Sentry>,
/// 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<Sentry>,
/// The file, line, and column where the route was defined.
pub location: (&'static str, u32, u32),
}
#[doc(hidden)]
@ -386,6 +391,7 @@ impl From<StaticInfo> 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,
}
}

View File

@ -39,7 +39,7 @@ impl Rocket<Orbit> {
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<Orbit> {
// 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<S>(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"),

View File

@ -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();
});

View File

@ -1,56 +1,32 @@
macro_rules! declare_macro {
($($name:ident $level:ident),* $(,)?) => (
$(declare_macro!([$] $name $level);)*
);
([$d:tt] $name:ident $level:ident) => (
#[doc(hidden)]
#[macro_export]
macro_rules! $name {
($d ($t:tt)*) => ($crate::tracing::$level!($d ($t)*));
}
// pub use $name as $name;
);
}
macro_rules! declare_span_macro {
($($name:ident $level:ident),* $(,)?) => (
$(declare_span_macro!([$] $name $level);)*
($name:ident $level:ident) => (
declare_span_macro!([$] $name $level);
);
([$d:tt] $name:ident $level: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)* )?)
(@[$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)*);
});
}
#[doc(inline)]
#[doc(hidden)]
pub use $name as $name;
);
}
macro_rules! span {
($level:expr, $($args:tt)*) => {{
match $level {
$crate::tracing::Level::ERROR =>
$crate::tracing::span!($crate::tracing::Level::ERROR, $($args)*),
$crate::tracing::Level::WARN =>
$crate::tracing::span!($crate::tracing::Level::WARN, $($args)*),
$crate::tracing::Level::INFO =>
$crate::tracing::span!($crate::tracing::Level::INFO, $($args)*),
$crate::tracing::Level::DEBUG =>
$crate::tracing::span!($crate::tracing::Level::DEBUG, $($args)*),
$crate::tracing::Level::TRACE =>
$crate::tracing::span!($crate::tracing::Level::TRACE, $($args)*),
}
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! event {
@ -69,21 +45,64 @@ macro_rules! event {
}};
}
// 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 ] $path:ident::$name:ident) => {
#[doc(hidden)]
#[macro_export]
macro_rules! $name {
($d ($f:tt)*) => {
$crate::$path::$name!($d ($f)*)
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! span {
($level:expr, $($args:tt)*) => {{
match $level {
$crate::tracing::Level::ERROR =>
$crate::tracing::span!($crate::tracing::Level::ERROR, $($args)*),
$crate::tracing::Level::WARN =>
$crate::tracing::span!($crate::tracing::Level::WARN, $($args)*),
$crate::tracing::Level::INFO =>
$crate::tracing::span!($crate::tracing::Level::INFO, $($args)*),
$crate::tracing::Level::DEBUG =>
$crate::tracing::span!($crate::tracing::Level::DEBUG, $($args)*),
$crate::tracing::Level::TRACE =>
$crate::tracing::span!($crate::tracing::Level::TRACE, $($args)*),
}
}};
}
#[doc(inline)]
pub use span as span;
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;

View File

@ -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<Option<&'a crate::Config>>>(config: T) {
#[cfg(not(feature = "trace"))]
let _ = config;
#[cfg(feature = "trace")]
crate::trace::subscriber::RocketDynFmt::init(config.into())
}

View File

@ -28,9 +28,9 @@ impl RocketFmt<Pretty> {
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<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
"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<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
)?;
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<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
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(())

View File

@ -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"
)

View File

@ -1,4 +1,4 @@
use rocket::{*, error::ErrorKind::SentinelAborts};
use rocket::{*, either::Either, error::ErrorKind::SentinelAborts};
#[get("/two")]
fn two_states(_one: &State<u32>, _two: &State<String>) {}

View File

@ -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="

View File

@ -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());
}
_ => {

View File

@ -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);

View File

@ -6,4 +6,4 @@ edition = "2021"
publish = false
[dependencies]
rocket = { path = "../../core/lib", features = ["secrets"] }
rocket = { path = "../../core/lib" }

View File

@ -38,9 +38,6 @@ fn wave(name: &str, age: u8) -> String {
format!("👋 Hello, {} year old named {}!", age, name)
}
#[get("/<a>/<b>")]
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("/?<lang>&<opt..>")]
async fn hello(lang: Option<Lang>, opt: Options<'_>) -> String {
fn hello(lang: Option<Lang>, opt: Options<'_>) -> String {
let mut greeting = String::new();
if opt.emoji {
greeting.push_str("👋 ");

View File

@ -159,7 +159,7 @@ fn not_found(request: &Request<'_>) -> content::RawHtml<String> {
/******************************* `Either` Responder ***************************/
use rocket::Either;
use rocket::either::Either;
use rocket::response::content::{RawJson, RawMsgPack};
use rocket::http::uncased::AsUncased;

View File

@ -45,7 +45,7 @@ impl Redirector {
pub async fn try_launch(self, config: Config) -> Result<Rocket<Ignite>, 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<Orbit>) {
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");
});

View File

@ -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"

View File

@ -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<RequestBuilder> {
pub fn request(&self, server: &Server, method: Method, url: &str) -> Result<RequestBuilder> {
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<RequestBuilder> {
self.request(server, Method::Get, url)
}
pub fn post(&self, server: &Server, url: &str) -> Result<RequestBuilder> {
self.request(server, Method::Post, url)
}
}

62
testbench/src/config.rs Normal file
View File

@ -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<Build> {
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<Vec<u8>> {
let path = path.replace("{ROCKET}", rocket::fs::relative!("../"));
Ok(std::fs::read(path)?)
}
pub fn cert(path: &str) -> Result<Vec<u8>> {
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())
}

View File

@ -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;
pub use testbench::{Error, Result, *};
pub use crate::register;
pub use crate::config::*;
}
impl RocketExt for Rocket<Build> {
fn default() -> Self {
rocket::build().reconfigure_with_toml(DEFAULT_CONFIG)
pub use runner::Test;
fn main() -> std::process::ExitCode {
runner::run()
}
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<Vec<u8>> {
let path = path.replace("{ROCKET}", rocket::fs::relative!("../"));
Ok(std::fs::read(path)?)
}
fn cert(path: &str) -> Result<Vec<u8>> {
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(())
}
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;
}
}
}
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 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::<TlsInfo>().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::<TlsInfo>().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<ServerConfig>,
counter: Arc<AtomicUsize>,
}
#[rocket::async_trait]
impl Resolver for CountingResolver {
async fn init(rocket: &Rocket<Build>) -> rocket::tls::Result<Self> {
let config: TlsConfig = rocket.figment().extract_inner("tls")?;
let config = Arc::new(config.server_config().await?);
let counter = rocket.state::<Arc<AtomicUsize>>().unwrap().clone();
Ok(Self { config, counter })
}
async fn resolve(&self, _: ClientHello<'_>) -> Option<Arc<ServerConfig>> {
self.counter.fetch_add(1, Ordering::Release);
Some(self.config.clone())
}
}
let server = spawn! {
#[get("/count")]
fn count(counter: &State<Arc<AtomicUsize>>) -> 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<ServerConfig>,
map: HashMap<Host<'static>, Arc<ServerConfig>>
}
#[rocket::async_trait]
impl Resolver for SniResolver {
async fn init(rocket: &Rocket<Build>) -> rocket::tls::Result<Self> {
let default: TlsConfig = rocket.figment().extract_inner("tls")?;
let sni: HashMap<Host<'_>, 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<Arc<ServerConfig>> {
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::<TlsInfo>().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::<TlsInfo>().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::<TlsInfo>().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::<TcpListener>(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::<UnixListener>(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<ServerConfig>
// }
//
// #[crate::async_trait]
// impl Resolver for UpdatingResolver {
// async fn resolve(&self, _: ClientHello<'_>) -> Option<Arc<ServerConfig>> {
// 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())
// }
// }

74
testbench/src/runner.rs Normal file
View File

@ -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::<Test>
.into_iter()
.filter(|t| t.name.contains(&filter));
let total_tests = inventory::iter::<Test>.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
}
}
}

View File

@ -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::<TcpListener>(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::<UnixListener>(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);

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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));

View File

@ -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<ServerConfig>,
map: HashMap<Host<'static>, Arc<ServerConfig>>
}
#[rocket::async_trait]
impl Resolver for SniResolver {
async fn init(rocket: &Rocket<Build>) -> rocket::tls::Result<Self> {
let default: TlsConfig = rocket.figment().extract_inner("tls")?;
let sni: HashMap<Host<'_>, 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<Arc<ServerConfig>> {
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::<TlsInfo>().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::<TlsInfo>().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::<TlsInfo>().unwrap();
let expected = cert("{ROCKET}/examples/tls/private/ed25519_cert.pem")?;
assert_eq!(tls.peer_certificate().unwrap(), expected);
Ok(())
}
struct CountingResolver {
config: Arc<ServerConfig>,
counter: Arc<AtomicUsize>,
}
#[rocket::async_trait]
impl Resolver for CountingResolver {
async fn init(rocket: &Rocket<Build>) -> rocket::tls::Result<Self> {
let config: TlsConfig = rocket.figment().extract_inner("tls")?;
let config = Arc::new(config.server_config().await?);
let counter = rocket.state::<Arc<AtomicUsize>>().unwrap().clone();
Ok(Self { config, counter })
}
async fn resolve(&self, _: ClientHello<'_>) -> Option<Arc<ServerConfig>> {
self.counter.fetch_add(1, Ordering::Release);
Some(self.config.clone())
}
}
#[get("/count")]
fn count(counter: &State<Arc<AtomicUsize>>) -> 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);

View File

@ -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::<TlsInfo>().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::<TlsInfo>().unwrap();
assert!(!tls.peer_certificate().unwrap().is_empty());
assert!(response.text()?.starts_with("Hello, https://127.0.0.1"));
Ok(())
}
register!(test_tls_works);

View File

@ -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<ServerConfig>,
counter: Arc<AtomicUsize>,
}
#[rocket::async_trait]
impl Resolver for CountingResolver {
async fn init(rocket: &Rocket<Build>) -> rocket::tls::Result<Self> {
let config: TlsConfig = rocket.figment().extract_inner("tls")?;
let config = Arc::new(config.server_config().await?);
let counter = rocket.state::<Arc<AtomicUsize>>().unwrap().clone();
Ok(Self { config, counter })
}
async fn resolve(&self, _: ClientHello<'_>) -> Option<Arc<ServerConfig>> {
self.counter.fetch_add(1, Ordering::Release);
Some(self.config.clone())
}
}
#[get("/count")]
fn count(counter: &State<Arc<AtomicUsize>>) -> 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<ServerConfig>
// }
//
// #[crate::async_trait]
// impl Resolver for UpdatingResolver {
// async fn resolve(&self, _: ClientHello<'_>) -> Option<Arc<ServerConfig>> {
// 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())
// }
// }

View File

@ -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<Self, Self::Error> { Err(Self("param")) }
}
impl FromParam<'_> for UseDebug {
type Error = Self;
fn from_param(_: &str) -> Result<Self, Self::Error> { Err(Self) }
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for UseDisplay {
type Error = Self;
async fn from_request(_: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
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<Self, Self::Error> {
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<Self, Self::Error> { Err(Self("segment")) }
}
impl<'r> FromSegments<'r> for UseDebug {
type Error = Self;
fn from_segments(_: Segments<'r, Path>) -> Result<Self, Self::Error> { 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);