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 criterion::{criterion_group, Criterion};
use rocket::{route, config, Request, Data, Route, Config}; 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 { fn client(routes: Vec<Route>) -> Client {
let config = Config { let config = Config {
profile: Config::RELEASE_PROFILE, profile: Config::RELEASE_PROFILE,
// log_level: rocket::config::LogLevel::Off, log_level: None,
cli_colors: config::CliColors::Never, cli_colors: config::CliColors::Never,
shutdown: config::ShutdownConfig { shutdown: config::ShutdownConfig {
ctrlc: false, ctrlc: false,
#[cfg(unix)] #[cfg(unix)]
signals: HashSet::new(), signals: std::collections::hash_set::HashSet::new(),
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()

View File

@ -21,9 +21,9 @@ workspace = true
deadpool_postgres = ["deadpool-postgres", "deadpool"] deadpool_postgres = ["deadpool-postgres", "deadpool"]
deadpool_redis = ["deadpool-redis", "deadpool"] deadpool_redis = ["deadpool-redis", "deadpool"]
# sqlx features # sqlx features
sqlx_mysql = ["sqlx", "sqlx/mysql"] sqlx_mysql = ["sqlx", "sqlx/mysql", "log"]
sqlx_postgres = ["sqlx", "sqlx/postgres"] sqlx_postgres = ["sqlx", "sqlx/postgres", "log"]
sqlx_sqlite = ["sqlx", "sqlx/sqlite"] sqlx_sqlite = ["sqlx", "sqlx/sqlite", "log"]
sqlx_macros = ["sqlx/macros"] sqlx_macros = ["sqlx/macros"]
# diesel features # diesel features
diesel_postgres = ["diesel-async/postgres", "diesel-async/deadpool", "diesel", "deadpool_09"] diesel_postgres = ["diesel-async/postgres", "diesel-async/deadpool", "diesel", "deadpool_09"]
@ -86,6 +86,11 @@ default-features = false
features = ["runtime-tokio-rustls"] features = ["runtime-tokio-rustls"]
optional = true optional = true
[dependencies.log]
version = "0.4"
default-features = false
optional = true
[dev-dependencies.rocket] [dev-dependencies.rocket]
path = "../../../core/lib" path = "../../../core/lib"
default-features = false default-features = false

View File

@ -269,7 +269,7 @@ mod deadpool_old {
mod sqlx { mod sqlx {
use sqlx::ConnectOptions; use sqlx::ConnectOptions;
use super::{Duration, Error, Config, Figment}; 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; type Options<D> = <<D as sqlx::Database>::Connection as sqlx::Connection>::Options;
@ -301,12 +301,21 @@ mod sqlx {
specialize(&mut opts, &config); specialize(&mut opts, &config);
opts = opts.disable_statement_logging(); opts = opts.disable_statement_logging();
// if let Ok(level) = figment.extract_inner::<LogLevel>(rocket::Config::LOG_LEVEL) { if let Ok(value) = figment.find_value(rocket::Config::LOG_LEVEL) {
// if !matches!(level, LogLevel::Normal | LogLevel::Off) { if let Some(level) = value.as_str().and_then(|v| v.parse().ok()) {
// opts = opts.log_statements(level.into()) let log_level = match level {
// .log_slow_statements(level.into(), Duration::default()); 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() sqlx::pool::PoolOptions::new()
.max_connections(config.max_connections as u32) .max_connections(config.max_connections as u32)

View File

@ -42,20 +42,20 @@ impl Engine for Environment<'static> {
Some(env) Some(env)
} }
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> { fn render<C: Serialize>(&self, template: &str, context: C) -> Option<String> {
let Ok(template) = self.get_template(name) else { let Ok(templ) = self.get_template(template) else {
error!("Minijinja template '{name}' was not found."); error!(template, "requested template does not exist");
return None; return None;
}; };
match template.render(context) { match templ.render(context) {
Ok(result) => Some(result), Ok(result) => Some(result),
Err(e) => { Err(e) => {
error_span!("Error rendering Minijinja template '{name}': {e}" => { span_error!("templating", template, "failed to render Minijinja template" => {
let mut error = &e as &dyn std::error::Error; let mut error = Some(&e as &dyn std::error::Error);
while let Some(source) = error.source() { while let Some(err) = error {
error_!("caused by: {source}"); error!("{err}");
error = source; error = err.source();
} }
}); });

View File

@ -21,7 +21,7 @@ impl Engine for Tera {
// Finally try to tell Tera about all of the templates. // Finally try to tell Tera about all of the templates.
if let Err(e) = tera.add_template_files(files) { 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); let mut error = Some(&e as &dyn Error);
while let Some(err) = error { while let Some(err) = error {
error!("{err}"); error!("{err}");
@ -48,7 +48,7 @@ impl Engine for Tera {
match Tera::render(self, template, &tera_ctx) { match Tera::render(self, template, &tera_ctx) {
Ok(string) => Some(string), Ok(string) => Some(string),
Err(e) => { 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); let mut error = Some(&e as &dyn Error);
while let Some(err) = error { while let Some(err) = error {
error!("{err}"); error!("{err}");

View File

@ -1,6 +1,7 @@
use rocket::{Rocket, Build, Orbit}; use rocket::{Rocket, Build, Orbit};
use rocket::fairing::{self, Fairing, Info, Kind}; use rocket::fairing::{self, Fairing, Info, Kind};
use rocket::figment::{Source, value::magic::RelativePathBuf}; use rocket::figment::{Source, value::magic::RelativePathBuf};
use rocket::trace::Trace;
use crate::context::{Callback, Context, ContextManager}; use crate::context::{Callback, Context, ContextManager};
use crate::template::DEFAULT_TEMPLATE_DIR; use crate::template::DEFAULT_TEMPLATE_DIR;
@ -40,7 +41,7 @@ impl Fairing for TemplateFairing {
Ok(dir) => dir, Ok(dir) => dir,
Err(e) if e.missing() => DEFAULT_TEMPLATE_DIR.into(), Err(e) if e.missing() => DEFAULT_TEMPLATE_DIR.into(),
Err(e) => { Err(e) => {
rocket::config::pretty_print_error(e); e.trace_error();
return Err(rocket); return Err(rocket);
} }
}; };
@ -57,7 +58,7 @@ impl Fairing for TemplateFairing {
let cm = rocket.state::<ContextManager>() let cm = rocket.state::<ContextManager>()
.expect("Template ContextManager registered in on_ignite"); .expect("Template ContextManager registered in on_ignite");
info_span!("templating" => { span_info!("templating" => {
info!(directory = %Source::from(&*cm.context().root)); info!(directory = %Source::from(&*cm.context().root));
info!(engines = ?Engines::ENABLED_EXTENSIONS); info!(engines = ?Engines::ENABLED_EXTENSIONS);
}); });

View File

@ -248,7 +248,7 @@ impl Template {
})?; })?;
let value = self.value.map_err(|e| { 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 Status::InternalServerError
})?; })?;

View File

@ -68,7 +68,7 @@ impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
let config = match Config::from(database, &rocket) { let config = match Config::from(database, &rocket) {
Ok(config) => config, Ok(config) => config,
Err(e) => { Err(e) => {
error_span!("database configuration error" [database] => e.trace_error()); span_error!("database configuration error", database => e.trace_error());
return Err(rocket); return Err(rocket);
} }
}; };
@ -82,7 +82,7 @@ impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
_marker: PhantomData, _marker: PhantomData,
})), })),
Err(Error::Config(e)) => { 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(rocket)
} }
Err(Error::Pool(reason)) => { Err(Error::Pool(reason)) => {

View File

@ -80,9 +80,10 @@ pub fn _catch(
} }
#_catcher::StaticInfo { #_catcher::StaticInfo {
name: stringify!(#user_catcher_fn_name), name: ::core::stringify!(#user_catcher_fn_name),
code: #status_code, code: #status_code,
handler: monomorphized_function, 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>`. // Always infer the type as `Rocket<Build>`.
if let syn::ReturnType::Type(_, ref mut ty) = &mut f.sig.output { if let syn::ReturnType::Type(_, ref mut ty) = &mut f.sig.output {
if let syn::Type::Infer(_) = &mut **ty { if let syn::Type::Infer(_) = &mut **ty {
let new = quote_spanned!(ty.span() => ::rocket::Rocket<::rocket::Build>); *ty = syn::parse_quote_spanned!(ty.span() => ::rocket::Rocket<::rocket::Build>);
*ty = syn::parse2(new).expect("path is type");
} }
} }
@ -105,8 +104,9 @@ impl EntryAttr for Launch {
} }
let (vis, mut sig) = (&f.vis, f.sig.clone()); let (vis, mut sig) = (&f.vis, f.sig.clone());
sig.ident = syn::Ident::new("main", sig.ident.span()); sig.ident = syn::Ident::new("main", f.sig.ident.span());
sig.output = syn::parse_quote!(-> #_ExitCode); let ret_ty = _ExitCode.respanned(ty.span());
sig.output = syn::parse_quote_spanned!(ty.span() => -> #ret_ty);
sig.asyncness = None; sig.asyncness = None;
Ok(quote_spanned!(block.span() => Ok(quote_spanned!(block.span() =>

View File

@ -105,9 +105,10 @@ fn query_decls(route: &Route) -> Option<TokenStream> {
)* )*
if !__e.is_empty() { if !__e.is_empty() {
::rocket::info_span!("query string failed to match route declaration" => { ::rocket::trace::span_info!("codegen",
for _err in __e { ::rocket::info!("{_err}"); } "query string failed to match route declaration" =>
}); { for _err in __e { ::rocket::trace::info!("{_err}"); } }
);
return #Outcome::Forward((#__data, #Status::UnprocessableEntity)); return #Outcome::Forward((#__data, #Status::UnprocessableEntity));
} }
@ -120,18 +121,25 @@ fn query_decls(route: &Route) -> Option<TokenStream> {
fn request_guard_decl(guard: &Guard) -> TokenStream { fn request_guard_decl(guard: &Guard) -> TokenStream {
let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty); let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty);
define_spanned_export!(ty.span() => define_spanned_export!(ty.span() =>
__req, __data, _request, FromRequest, Outcome __req, __data, _request, display_hack, FromRequest, Outcome
); );
quote_spanned! { ty.span() => quote_spanned! { ty.span() =>
let #ident: #ty = match <#ty as #FromRequest>::from_request(#__req).await { let #ident: #ty = match <#ty as #FromRequest>::from_request(#__req).await {
#Outcome::Success(__v) => __v, #Outcome::Success(__v) => __v,
#Outcome::Forward(__e) => { #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)); return #Outcome::Forward((#__data, __e));
}, },
#[allow(unreachable_code)]
#Outcome::Error((__c, __e)) => { #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); 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); let (i, name, ty) = (guard.index, &guard.name, &guard.ty);
define_spanned_export!(ty.span() => define_spanned_export!(ty.span() =>
__req, __data, _None, _Some, _Ok, _Err, __req, __data, _None, _Some, _Ok, _Err,
Outcome, FromSegments, FromParam, Status Outcome, FromSegments, FromParam, Status, display_hack
); );
// Returned when a dynamic parameter fails to parse. // Returned when a dynamic parameter fails to parse.
let parse_error = quote!({ let parse_error = quote!({
::rocket::info!(name: "forward", ::rocket::trace::info!(name: "forward", parameter = #name,
reason = %__error, parameter = #name, "type" = stringify!(#ty), type_name = stringify!(#ty), reason = %#display_hack!(__error),
"parameter forwarding"); "path guard forwarding");
#Outcome::Forward((#__data, #Status::UnprocessableEntity)) #Outcome::Forward((#__data, #Status::UnprocessableEntity))
}); });
@ -161,10 +169,11 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
match #__req.routed_segment(#i) { match #__req.routed_segment(#i) {
#_Some(__s) => match <#ty as #FromParam>::from_param(__s) { #_Some(__s) => match <#ty as #FromParam>::from_param(__s) {
#_Ok(__v) => __v, #_Ok(__v) => __v,
#[allow(unreachable_code)]
#_Err(__error) => return #parse_error, #_Err(__error) => return #parse_error,
}, },
#_None => { #_None => {
::rocket::error!( ::rocket::trace::error!(
"Internal invariant broken: dyn param {} not found.\n\ "Internal invariant broken: dyn param {} not found.\n\
Please report this to the Rocket issue tracker.\n\ Please report this to the Rocket issue tracker.\n\
https://github.com/rwf2/Rocket/issues", #i); https://github.com/rwf2/Rocket/issues", #i);
@ -176,6 +185,7 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
true => quote_spanned! { ty.span() => true => quote_spanned! { ty.span() =>
match <#ty as #FromSegments>::from_segments(#__req.routed_segments(#i..)) { match <#ty as #FromSegments>::from_segments(#__req.routed_segments(#i..)) {
#_Ok(__v) => __v, #_Ok(__v) => __v,
#[allow(unreachable_code)]
#_Err(__error) => return #parse_error, #_Err(__error) => return #parse_error,
} }
}, },
@ -187,17 +197,24 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
fn data_guard_decl(guard: &Guard) -> TokenStream { fn data_guard_decl(guard: &Guard) -> TokenStream {
let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty); 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() => quote_spanned! { ty.span() =>
let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await { let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await {
#Outcome::Success(__d) => __d, #Outcome::Success(__d) => __d,
#Outcome::Forward((__d, __e)) => { #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)); return #Outcome::Forward((__d, __e));
} }
#[allow(unreachable_code)]
#Outcome::Error((__c, __e)) => { #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); return #Outcome::Error(__c);
} }
}; };
@ -232,7 +249,7 @@ fn internal_uri_macro_decl(route: &Route) -> TokenStream {
/// Rocket generated URI macro. /// Rocket generated URI macro.
macro_rules! #inner_macro_name { macro_rules! #inner_macro_name {
($($token:tt)*) => {{ ($($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, format: #format,
rank: #rank, rank: #rank,
sentinels: #sentinels, sentinels: #sentinels,
location: (::core::file!(), ::core::line!(), ::core::column!()),
} }
} }

View File

@ -86,6 +86,7 @@ define_exported_paths! {
_Vec => ::std::vec::Vec, _Vec => ::std::vec::Vec,
_Cow => ::std::borrow::Cow, _Cow => ::std::borrow::Cow,
_ExitCode => ::std::process::ExitCode, _ExitCode => ::std::process::ExitCode,
display_hack => ::rocket::error::display_hack,
BorrowMut => ::std::borrow::BorrowMut, BorrowMut => ::std::borrow::BorrowMut,
Outcome => ::rocket::outcome::Outcome, Outcome => ::rocket::outcome::Outcome,
FromForm => ::rocket::form::FromForm, FromForm => ::rocket::form::FromForm,

View File

@ -1328,7 +1328,7 @@ pub fn catchers(input: TokenStream) -> TokenStream {
/// assert_eq!(bob2.to_string(), "/person/Bob%20Smith"); /// assert_eq!(bob2.to_string(), "/person/Bob%20Smith");
/// ///
/// #[get("/person/<age>")] /// #[get("/person/<age>")]
/// fn ok(age: Result<u8, &str>) { } /// fn ok(age: Result<u8, std::num::ParseIntError>) { }
/// ///
/// let kid1 = uri!(ok(age = 10)); /// let kid1 = uri!(ok(age = 10));
/// let kid2 = uri!(ok(12)); /// 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", 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; 73 | let _ = rocket::build().launch().await;
| ^^^^^ only allowed inside `async` functions and blocks | ^^^^^ 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>` error[E0277]: `main` has invalid return type `Rocket<Build>`
--> tests/ui-fail-nightly/async-entry.rs:94:20 --> 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` | ^ the trait `FromParam<'_>` is not implemented for `Q`
| |
= help: the following other types implement trait `FromParam<'a>`: = help: the following other types implement trait `FromParam<'a>`:
&'a str <&'a str as FromParam<'a>>
IpAddr <IpAddr as FromParam<'a>>
Ipv4Addr <Ipv4Addr as FromParam<'a>>
Ipv6Addr <Ipv6Addr as FromParam<'a>>
NonZero<i128> <NonZero<i128> as FromParam<'a>>
NonZero<i16> <NonZero<i16> as FromParam<'a>>
NonZero<i32> <NonZero<i32> as FromParam<'a>>
NonZero<i64> <NonZero<i64> as FromParam<'a>>
and $N others and $N others
error[E0277]: the trait bound `Q: FromSegments<'_>` is not satisfied 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` | ^ the trait `FromParam<'_>` is not implemented for `Q`
| |
= help: the following other types implement trait `FromParam<'a>`: = help: the following other types implement trait `FromParam<'a>`:
&'a str <&'a str as FromParam<'a>>
IpAddr <IpAddr as FromParam<'a>>
Ipv4Addr <Ipv4Addr as FromParam<'a>>
Ipv6Addr <Ipv6Addr as FromParam<'a>>
NonZero<i128> <NonZero<i128> as FromParam<'a>>
NonZero<i16> <NonZero<i16> as FromParam<'a>>
NonZero<i32> <NonZero<i32> as FromParam<'a>>
NonZero<i64> <NonZero<i64> as FromParam<'a>>
and $N others and $N others
error[E0277]: the trait bound `Q: FromRequest<'_>` is not satisfied 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` | ^ the trait `FromParam<'_>` is not implemented for `Q`
| |
= help: the following other types implement trait `FromParam<'a>`: = help: the following other types implement trait `FromParam<'a>`:
&'a str <&'a str as FromParam<'a>>
IpAddr <IpAddr as FromParam<'a>>
Ipv4Addr <Ipv4Addr as FromParam<'a>>
Ipv6Addr <Ipv6Addr as FromParam<'a>>
NonZero<i128> <NonZero<i128> as FromParam<'a>>
NonZero<i16> <NonZero<i16> as FromParam<'a>>
NonZero<i32> <NonZero<i32> as FromParam<'a>>
NonZero<i64> <NonZero<i64> as FromParam<'a>>
and $N others and $N others
error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied 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` | ^ the trait `FromParam<'_>` is not implemented for `Q`
| |
= help: the following other types implement trait `FromParam<'a>`: = help: the following other types implement trait `FromParam<'a>`:
&'a str <&'a str as FromParam<'a>>
IpAddr <IpAddr as FromParam<'a>>
Ipv4Addr <Ipv4Addr as FromParam<'a>>
Ipv6Addr <Ipv6Addr as FromParam<'a>>
NonZero<i128> <NonZero<i128> as FromParam<'a>>
NonZero<i16> <NonZero<i16> as FromParam<'a>>
NonZero<i32> <NonZero<i32> as FromParam<'a>>
NonZero<i64> <NonZero<i64> as FromParam<'a>>
and $N others 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 &'a std::path::Path>>
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>> <&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>>
and $N others 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 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 --> 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 &'a std::path::Path>>
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>> <&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>>
and $N others 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 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 --> 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 &'a std::path::Path>>
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>> <&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>>
and $N others 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 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 --> 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 &'a std::path::Path>>
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>> <&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>>
and $N others 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 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 --> 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 mut usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, 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 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 --> 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 mut usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, 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 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 --> 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 mut usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, 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 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 --> 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, &'x mut i32>>
<i32 as FromUriParam<P, 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: 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 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 --> 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 isize>>
<isize as FromUriParam<P, &'x mut isize>> <isize as FromUriParam<P, &'x mut isize>>
<isize as FromUriParam<P, 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 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 --> 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 isize>>
<isize as FromUriParam<P, &'x mut isize>> <isize as FromUriParam<P, &'x mut isize>>
<isize as FromUriParam<P, 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 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 --> 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 mut usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, 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 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 --> 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 mut usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, 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 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 --> 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 mut usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, 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 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 --> 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 mut usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, 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<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>> <Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>> <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>> <Cow<'o, R> as Responder<'r, 'o>>
<rocket::tokio::fs::File as Responder<'r, 'static>> <rocket::tokio::fs::File as Responder<'r, 'static>>
<EventStream<S> as Responder<'r, 'r>> <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<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>> <Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>> <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>> <Cow<'o, R> as Responder<'r, 'o>>
<rocket::tokio::fs::File as Responder<'r, 'static>> <rocket::tokio::fs::File as Responder<'r, 'static>>
<EventStream<S> as Responder<'r, 'r>> <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<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>> <Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>> <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>> <Cow<'o, R> as Responder<'r, 'o>>
<rocket::tokio::fs::File as Responder<'r, 'static>> <rocket::tokio::fs::File as Responder<'r, 'static>>
<EventStream<S> as Responder<'r, 'r>> <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<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>> <Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>> <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>> <Cow<'o, R> as Responder<'r, 'o>>
<rocket::tokio::fs::File as Responder<'r, 'static>> <rocket::tokio::fs::File as Responder<'r, 'static>>
<EventStream<S> as Responder<'r, 'r>> <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>> <HashMap<K, V> as FromForm<'v>>
<BTreeMap<K, V> as FromForm<'v>> <BTreeMap<K, V> as FromForm<'v>>
<Arc<T> 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::Range<T> as FromForm<'r>>
<form::from_form::_::proxy::RangeFrom<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 and $N others
= note: required for `Unknown` to implement `FromForm<'r>` = 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>> <HashMap<K, V> as FromForm<'v>>
<BTreeMap<K, V> as FromForm<'v>> <BTreeMap<K, V> as FromForm<'v>>
<Arc<T> 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::Range<T> as FromForm<'r>>
<form::from_form::_::proxy::RangeFrom<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 and $N others
= note: required for `Foo<usize>` to implement `FromForm<'r>` = 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<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>> <Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>> <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 and $N others
error[E0277]: the trait bound `Header<'_>: From<u8>` is not satisfied 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<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>> <Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>> <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 and $N others
error[E0277]: the trait bound `Header<'_>: From<u8>` is not satisfied 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<str> as Responder<'r, 'static>>
<Box<[u8]> as Responder<'r, 'static>> <Box<[u8]> as Responder<'r, 'static>>
<Box<T> as Responder<'r, 'o>> <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 and $N others
note: required by a bound in `route::handler::<impl Outcome<Response<'o>, Status, (rocket::Data<'o>, Status)>>::from` 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 --> $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` | ^ the trait `FromParam<'_>` is not implemented for `Q`
| |
= help: the following other types implement trait `FromParam<'a>`: = help: the following other types implement trait `FromParam<'a>`:
bool <bool as FromParam<'a>>
isize <isize as FromParam<'a>>
i8 <i8 as FromParam<'a>>
i16 <i16 as FromParam<'a>>
i32 <i32 as FromParam<'a>>
i64 <i64 as FromParam<'a>>
i128 <i128 as FromParam<'a>>
usize <usize as FromParam<'a>>
and $N others and $N others
error[E0277]: the trait bound `Q: FromSegments<'_>` is not satisfied 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` | ^ the trait `FromParam<'_>` is not implemented for `Q`
| |
= help: the following other types implement trait `FromParam<'a>`: = help: the following other types implement trait `FromParam<'a>`:
bool <bool as FromParam<'a>>
isize <isize as FromParam<'a>>
i8 <i8 as FromParam<'a>>
i16 <i16 as FromParam<'a>>
i32 <i32 as FromParam<'a>>
i64 <i64 as FromParam<'a>>
i128 <i128 as FromParam<'a>>
usize <usize as FromParam<'a>>
and $N others and $N others
error[E0277]: the trait bound `Q: FromRequest<'_>` is not satisfied 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` | ^ the trait `FromParam<'_>` is not implemented for `Q`
| |
= help: the following other types implement trait `FromParam<'a>`: = help: the following other types implement trait `FromParam<'a>`:
bool <bool as FromParam<'a>>
isize <isize as FromParam<'a>>
i8 <i8 as FromParam<'a>>
i16 <i16 as FromParam<'a>>
i32 <i32 as FromParam<'a>>
i64 <i64 as FromParam<'a>>
i128 <i128 as FromParam<'a>>
usize <usize as FromParam<'a>>
and $N others and $N others
error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied 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` | ^ the trait `FromParam<'_>` is not implemented for `Q`
| |
= help: the following other types implement trait `FromParam<'a>`: = help: the following other types implement trait `FromParam<'a>`:
bool <bool as FromParam<'a>>
isize <isize as FromParam<'a>>
i8 <i8 as FromParam<'a>>
i16 <i16 as FromParam<'a>>
i32 <i32 as FromParam<'a>>
i64 <i64 as FromParam<'a>>
i128 <i128 as FromParam<'a>>
usize <usize as FromParam<'a>>
and $N others 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, i8>>
<i8 as FromUriParam<P, &'x i8>> <i8 as FromUriParam<P, &'x i8>>
and $N others 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 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 --> 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, i8>>
<i8 as FromUriParam<P, &'x i8>> <i8 as FromUriParam<P, &'x i8>>
and $N others 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 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 --> 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, i8>>
<i8 as FromUriParam<P, &'x i8>> <i8 as FromUriParam<P, &'x i8>>
and $N others 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 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 --> 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, i8>>
<i8 as FromUriParam<P, &'x i8>> <i8 as FromUriParam<P, &'x i8>>
and $N others 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 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 --> 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, usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut 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 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 --> 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, usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut 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 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 --> 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, usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut 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 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 --> 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 i32>>
<i32 as FromUriParam<P, &'x mut 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: 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 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 --> 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, isize>>
<isize as FromUriParam<P, &'x isize>> <isize as FromUriParam<P, &'x isize>>
<isize as FromUriParam<P, &'x mut 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 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 --> 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, isize>>
<isize as FromUriParam<P, &'x isize>> <isize as FromUriParam<P, &'x isize>>
<isize as FromUriParam<P, &'x mut 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 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 --> 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, usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut 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 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 --> 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, usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut 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 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 --> 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, usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut 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 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 --> 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, usize>>
<usize as FromUriParam<P, &'x usize>> <usize as FromUriParam<P, &'x usize>>
<usize as FromUriParam<P, &'x mut 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::path::{Path, PathBuf};
use std::collections::{BTreeMap, HashMap};
use either::Either;
use crate::uri::fmt::UriDisplay; use crate::uri::fmt::UriDisplay;
use crate::uri::fmt::{self, Part}; 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`, /// * `String`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `u8`, `u16`,
/// `u32`, `u64`, `u128`, `usize`, `f32`, `f64`, `bool`, `IpAddr`, /// `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`]_: /// 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>`. /// 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; type Target = T::Target;
#[inline(always)] #[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> { impl<A, T: FromUriParam<fmt::Query, A>> FromUriParam<fmt::Query, Option<A>> for Option<T> {
type Target = Option<T::Target>; type Target = Option<T::Target>;

View File

@ -2,6 +2,7 @@ use std::collections::{BTreeMap, HashMap};
use std::{fmt, path}; use std::{fmt, path};
use std::borrow::Cow; use std::borrow::Cow;
use either::Either;
use time::{macros::format_description, format_description::FormatItem}; use time::{macros::format_description, format_description::FormatItem};
use crate::RawStr; 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`. /// Defers to the `UriDisplay<P>` implementation for `T`.
impl<P: Part, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &mut T { impl<P: Part, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &mut T {
#[inline(always)] #[inline(always)]

View File

@ -185,7 +185,7 @@ type TestData<'a> = (
fn fuzz((route_a, route_b, req): TestData<'_>) { fn fuzz((route_a, route_b, req): TestData<'_>) {
let rocket = rocket::custom(rocket::Config { let rocket = rocket::custom(rocket::Config {
workers: 2, workers: 2,
// log_level: rocket::log::LogLevel::Off, log_level: None,
cli_colors: rocket::config::CliColors::Never, cli_colors: rocket::config::CliColors::Never,
..rocket::Config::debug_default() ..rocket::Config::debug_default()
}); });

View File

@ -127,6 +127,9 @@ pub struct Catcher {
/// ///
/// This is -(number of nonempty segments in base). /// This is -(number of nonempty segments in base).
pub(crate) rank: isize, 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 // The rank is computed as -(number of nonempty segments in base) => catchers
@ -185,7 +188,8 @@ impl Catcher {
base: uri::Origin::root().clone(), base: uri::Origin::root().clone(),
handler: Box::new(handler), handler: Box::new(handler),
rank: rank(uri::Origin::root().path()), rank: rank(uri::Origin::root().path()),
code code,
location: None,
} }
} }
@ -328,6 +332,8 @@ pub struct StaticInfo {
pub code: Option<u16>, pub code: Option<u16>,
/// The catcher's handler, i.e, the annotated function. /// The catcher's handler, i.e, the annotated function.
pub handler: for<'r> fn(Status, &'r Request<'_>) -> BoxFuture<'r>, 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)] #[doc(hidden)]
@ -336,6 +342,7 @@ impl From<StaticInfo> for Catcher {
fn from(info: StaticInfo) -> Catcher { fn from(info: StaticInfo) -> Catcher {
let mut catcher = Catcher::new(info.code, info.handler); let mut catcher = Catcher::new(info.code, info.handler);
catcher.name = Some(info.name.into()); catcher.name = Some(info.name.into());
catcher.location = Some(info.location);
catcher catcher
} }
} }

View File

@ -2,15 +2,13 @@ use figment::{Figment, Profile, Provider, Metadata, error::Result};
use figment::providers::{Serialized, Env, Toml, Format}; use figment::providers::{Serialized, Env, Toml, Format};
use figment::value::{Map, Dict, magic::RelativePathBuf}; use figment::value::{Map, Dict, magic::RelativePathBuf};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::Level;
#[cfg(feature = "secrets")] #[cfg(feature = "secrets")]
use crate::config::SecretKey; 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::request::{self, Request, FromRequest};
use crate::http::uncased::Uncased; use crate::http::uncased::Uncased;
use crate::data::Limits; use crate::data::Limits;
use crate::trace::{Trace, TraceFormat};
/// Rocket server configuration. /// Rocket server configuration.
/// ///
@ -288,7 +286,7 @@ impl Config {
/// ///
/// # Panics /// # 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()`]. /// panics. For a version that doesn't panic, use [`Config::try_from()`].
/// ///
/// # Example /// # Example
@ -306,7 +304,12 @@ impl Config {
/// let config = Config::from(figment); /// let config = Config::from(figment);
/// ``` /// ```
pub fn from<T: Provider>(provider: T) -> Self { 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()) 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 config::Config;
pub use cli_colors::CliColors; pub use cli_colors::CliColors;
// pub use crate::log::LogLevel; pub use crate::trace::{TraceFormat, Level};
pub use crate::shutdown::ShutdownConfig; pub use crate::shutdown::ShutdownConfig;
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
@ -139,6 +139,3 @@ pub use crate::shutdown::Sig;
#[cfg(feature = "secrets")] #[cfg(feature = "secrets")]
pub use secret_key::SecretKey; 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 figment::Profile;
use crate::listener::Endpoint; use crate::listener::Endpoint;
use crate::trace::Trace;
use crate::{Ignite, Orbit, Phase, Rocket}; use crate::{Ignite, Orbit, Phase, Rocket};
use crate::trace::Trace;
/// An error that occurs during launch. /// An error that occurs during launch.
/// ///
@ -84,7 +84,7 @@ impl Error {
match result { match result {
Ok(_) => process::ExitCode::SUCCESS, Ok(_) => process::ExitCode::SUCCESS,
Err(e) => { 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 process::ExitCode::SUCCESS
} }
} }
@ -200,14 +200,14 @@ impl fmt::Display for ServerError<'_> {
pub(crate) fn log_server_error(error: &(dyn StdError + 'static)) { pub(crate) fn log_server_error(error: &(dyn StdError + 'static)) {
let mut error: &(dyn StdError + 'static) = error; let mut error: &(dyn StdError + 'static) = error;
if error.downcast_ref::<hyper::Error>().is_some() { 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() { while let Some(source) = error.source() {
error = source; error = source;
warn!("{}", ServerError(error)); warn!("{}", ServerError(error));
} }
}); });
} else { } else {
error_span!("server error" ["{}", ServerError(error)] => { span_error!("server error", "{}", ServerError(error) => {
while let Some(source) = error.source() { while let Some(source) = error.source() {
error = source; error = source;
error!("{}", ServerError(error)); 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 parking_lot::Mutex;
use futures::future::{Future, BoxFuture, FutureExt};
use crate::route::RouteUri;
use crate::{Rocket, Request, Response, Data, Build, Orbit}; use crate::{Rocket, Request, Response, Data, Build, Orbit};
use crate::fairing::{Fairing, Kind, Info, Result}; 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. /// 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>() { let app_config = match rocket.figment().extract::<T>() {
Ok(config) => config, Ok(config) => config,
Err(e) => { Err(e) => {
crate::config::pretty_print_error(e); e.trace_error();
return Err(rocket); return Err(rocket);
} }
}; };

View File

@ -51,16 +51,6 @@ impl Fairings {
iter!(self, self.active().collect::<HashSet<_>>().into_iter()) iter!(self, self.active().collect::<HashSet<_>>().into_iter())
.map(|v| v.1) .map(|v| v.1)
.collect() .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>) { pub fn add(&mut self, fairing: Box<dyn Fairing>) {

View File

@ -123,6 +123,7 @@ pub use tokio;
pub use figment; pub use figment;
pub use time; pub use time;
pub use tracing; pub use tracing;
pub use either;
#[macro_use] #[macro_use]
pub mod trace; pub mod trace;
@ -164,8 +165,6 @@ mod router;
mod phase; mod phase;
mod erased; mod erased;
#[doc(hidden)] pub use either::Either;
#[doc(inline)] pub use rocket_codegen::*; #[doc(inline)] pub use rocket_codegen::*;
#[doc(inline)] pub use crate::response::Response; #[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! /// WARNING: This is unstable! Do not use this method outside of Rocket!
#[doc(hidden)] #[doc(hidden)]
pub fn async_main<R>(fut: impl std::future::Future<Output = R> + Send) -> R { 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 // 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. // 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 // 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 // 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 // config values, or values from non-Rocket configs. See tokio-rs/tokio#3329
// for a necessary resolution in `tokio`. // for a necessary resolution in `tokio`.
use config::bail_with_config_error as bail;
let fig = Config::figment(); let fig = Config::figment();
let workers = fig.extract_inner(Config::WORKERS).unwrap_or_else(bail); let workers = fig.extract_inner(Config::WORKERS).unwrap_or_else(bail);
let max_blocking = fig.extract_inner(Config::MAX_BLOCKING).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 futures::future::{FutureExt, Future};
use crate::{route, catcher, Rocket, Orbit, Request, Response, Data};
use crate::trace::Trace; use crate::trace::Trace;
use crate::util::Formatter; use crate::util::Formatter;
use crate::data::IoHandler; use crate::data::IoHandler;
use crate::http::{Method, Status, Header}; use crate::http::{Method, Status, Header};
use crate::outcome::Outcome; use crate::outcome::Outcome;
use crate::form::Form; 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. // A token returned to force the execution of one method before another.
pub(crate) struct RequestToken; pub(crate) struct RequestToken;
@ -199,6 +199,7 @@ impl Rocket<Orbit> {
let mut status = Status::NotFound; let mut status = Status::NotFound;
for route in self.router.route(request) { for route in self.router.route(request) {
// Retrieve and set the requests parameters. // Retrieve and set the requests parameters.
route.trace_info();
request.set_route(route); request.set_route(route);
let name = route.name.as_deref(); let name = route.name.as_deref();
@ -207,7 +208,6 @@ impl Rocket<Orbit> {
// Check if the request processing completed (Some) or if the // Check if the request processing completed (Some) or if the
// request needs to be forwarded. If it does, continue the loop // request needs to be forwarded. If it does, continue the loop
route.trace_info();
outcome.trace_info(); outcome.trace_info();
match outcome { match outcome {
o@Outcome::Success(_) | o@Outcome::Error(_) => return o, o@Outcome::Success(_) | o@Outcome::Error(_) => return o,
@ -215,9 +215,7 @@ impl Rocket<Orbit> {
} }
} }
let outcome = Outcome::Forward((data, status)); Outcome::Forward((data, status))
outcome.trace_info();
outcome
} }
// Invokes the catcher for `status`. Returns the response on success. // Invokes the catcher for `status`. Returns the response on success.

View File

@ -138,7 +138,7 @@ macro_rules! pub_client_impl {
use crate::config; use crate::config;
let figment = rocket.figment().clone() let figment = rocket.figment().clone()
// .merge((config::Config::LOG_LEVEL, config::LogLevel::Debug)) .merge((config::Config::LOG_LEVEL, "debug"))
.select(config::Config::DEBUG_PROFILE); .select(config::Config::DEBUG_PROFILE);
Self::tracked(rocket.reconfigure(figment)) $(.$suffix)? Self::tracked(rocket.reconfigure(figment)) $(.$suffix)?

View File

@ -2,6 +2,7 @@ use std::str::FromStr;
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::Empty; use crate::error::Empty;
use crate::either::Either;
use crate::http::uri::{Segments, error::PathError, fmt::Path}; use crate::http::uri::{Segments, error::PathError, fmt::Path};
/// Trait to convert a dynamic path segment string to a concrete value. /// 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 /// 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 /// 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 /// `T`. In these cases, types of `Option<T>`, `Result<T, T::Error>`, or
/// used. These types implement `FromParam` themselves. Their implementations /// `Either<A, B>` can be used, which implement `FromParam` themselves.
/// 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.
/// ///
/// For instance, imagine you've asked for an `<id>` as a `usize`. To determine /// * **`Option<T>`** _where_ **`T: FromParam`**
/// 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 /// Always returns successfully.
/// as follows: ///
/// 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 /// ```rust
/// # #[macro_use] extern crate rocket; /// # #[macro_use] extern crate rocket;
/// use rocket::either::{Either, Left, Right};
///
/// #[get("/<id>")] /// #[get("/<id>")]
/// fn hello(id: Result<usize, &str>) -> String { /// fn hello(id: Either<usize, &str>) -> String {
/// match id { /// match id {
/// Ok(id_num) => format!("usize: {}", id_num), /// Left(id_num) => format!("usize: {}", id_num),
/// Err(string) => format!("Not a usize: {}", string) /// Right(string) => format!("Not a usize: {}", string)
/// } /// }
/// } /// }
/// # fn main() { } /// # 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 /// # Provided Implementations
/// ///
/// Rocket implements `FromParam` for several standard library types. Their /// Rocket implements `FromParam` for several standard library types. Their
@ -219,11 +247,11 @@ impl<'a> FromParam<'a> for String {
macro_rules! impl_with_fromstr { macro_rules! impl_with_fromstr {
($($T:ty),+) => ($( ($($T:ty),+) => ($(
impl<'a> FromParam<'a> for $T { impl<'a> FromParam<'a> for $T {
type Error = &'a str; type Error = <$T as FromStr>::Err;
#[inline(always)] #[inline(always)]
fn from_param(param: &'a str) -> Result<Self, Self::Error> { 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 { match self {
Some(r) => r.respond_to(req), Some(r) => r.respond_to(req),
None => { 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) 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 /// Responds with the wrapped `Responder` in `self`, whether it is `Left` or
/// `Right`. /// `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> where T: Responder<'r, 't>, E: Responder<'r, 'e>
{ {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
match self { match self {
crate::Either::Left(r) => r.respond_to(req), either::Either::Left(r) => r.respond_to(req),
crate::Either::Right(r) => r.respond_to(req), either::Either::Right(r) => r.respond_to(req),
} }
} }
} }

View File

@ -185,16 +185,9 @@ impl Rocket<Build> {
/// ``` /// ```
#[must_use] #[must_use]
pub fn custom<T: Provider>(provider: T) -> Self { pub fn custom<T: Provider>(provider: T) -> Self {
// We initialize the logger here so that logging from fairings and so on Rocket::<Build>(Building::default())
// are visible; we use the final config to set a max log-level in ignite .reconfigure(provider)
crate::trace::init(None); .attach(Shield::default())
let rocket: Rocket<Build> = Rocket(Building {
figment: Figment::from(provider),
..Default::default()
});
rocket.attach(Shield::default())
} }
/// Overrides the current configuration provider with `provider`. /// Overrides the current configuration provider with `provider`.
@ -237,7 +230,12 @@ impl Rocket<Build> {
/// ``` /// ```
#[must_use] #[must_use]
pub fn reconfigure<T: Provider>(mut self, provider: T) -> Self { 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); self.figment = Figment::from(provider);
crate::trace::init(Config::try_from(&self.figment).ok().as_ref());
span_trace!("reconfigure" => self.figment().trace_trace());
self self
} }
@ -566,14 +564,14 @@ impl Rocket<Build> {
// Log everything we know: config, routes, catchers, fairings. // Log everything we know: config, routes, catchers, fairings.
// TODO: Store/print managed state type names? // TODO: Store/print managed state type names?
let fairings = self.fairings.unique_set(); let fairings = self.fairings.unique_set();
info_span!("config" [profile = %self.figment().profile()] => { span_info!("config", profile = %self.figment().profile() => {
config.trace_info(); config.trace_info();
self.figment().trace_debug(); self.figment().trace_debug();
}); });
info_span!("routes" [count = self.routes.len()] => self.routes().trace_all_info()); span_info!("routes", count = self.routes.len() => self.routes().trace_all_info());
info_span!("catchers" [count = self.catchers.len()] => self.catchers().trace_all_info()); span_info!("catchers", count = self.catchers.len() => self.catchers().trace_all_info());
info_span!("fairings" [count = fairings.len()] => fairings.trace_all_info()); span_info!("fairings", count = fairings.len() => fairings.trace_all_info());
// Ignite the rocket. // Ignite the rocket.
let rocket: Rocket<Ignite> = Rocket(Igniting { 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> { impl Rocket<Ignite> {
/// Returns the finalized, active configuration. This is guaranteed to /// Returns the finalized, active configuration. This is guaranteed to
/// remain stable through ignition and into orbit. /// remain stable through ignition and into orbit.

View File

@ -176,6 +176,8 @@ pub struct Route {
pub format: Option<MediaType>, pub format: Option<MediaType>,
/// The discovered sentinels. /// The discovered sentinels.
pub(crate) sentinels: Vec<Sentry>, 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 { impl Route {
@ -250,6 +252,7 @@ impl Route {
format: None, format: None,
sentinels: Vec::new(), sentinels: Vec::new(),
handler: Box::new(handler), handler: Box::new(handler),
location: None,
rank, uri, method, rank, uri, method,
} }
} }
@ -371,6 +374,8 @@ pub struct StaticInfo {
/// Route-derived sentinels, if any. /// Route-derived sentinels, if any.
/// This isn't `&'static [SentryInfo]` because `type_name()` isn't `const`. /// This isn't `&'static [SentryInfo]` because `type_name()` isn't `const`.
pub sentinels: Vec<Sentry>, pub sentinels: Vec<Sentry>,
/// The file, line, and column where the route was defined.
pub location: (&'static str, u32, u32),
} }
#[doc(hidden)] #[doc(hidden)]
@ -386,6 +391,7 @@ impl From<StaticInfo> for Route {
rank: info.rank.unwrap_or_else(|| uri.default_rank()), rank: info.rank.unwrap_or_else(|| uri.default_rank()),
format: info.format, format: info.format,
sentinels: info.sentinels.into_iter().collect(), sentinels: info.sentinels.into_iter().collect(),
location: Some(info.location),
uri, uri,
} }
} }

View File

@ -39,7 +39,7 @@ impl Rocket<Orbit> {
Request::from_hyp(rocket, parts, connection).unwrap_or_else(|e| e) 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( let mut response = request.into_response(
stream, stream,
|rocket, request, data| Box::pin(rocket.preprocess(request, data)), |rocket, request, data| Box::pin(rocket.preprocess(request, data)),
@ -54,7 +54,7 @@ impl Rocket<Orbit> {
// TODO: Should upgrades be handled in dispatch? // TODO: Should upgrades be handled in dispatch?
response.inner().trace_info(); 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); let io_handler = response.make_io_handler(Rocket::extract_io_handler);
if let (Some((proto, handler)), Some(upgrade)) = (io_handler, upgrade) { if let (Some((proto, handler)), Some(upgrade)) = (io_handler, upgrade) {
let upgrade = upgrade.map_ok(IoStream::from).map_err(io::Error::other); 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"), 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 { if let Err(e) = handler.take().io(stream).await {
match e.kind() { match e.kind() {
io::ErrorKind::BrokenPipe => warn!("i/o handler closed"), 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::{Rocket, Request, Response, Orbit, Config};
use crate::fairing::{Fairing, Info, Kind}; use crate::fairing::{Fairing, Info, Kind};
use crate::http::{Header, uncased::UncasedStr}; use crate::http::{Header, uncased::UncasedStr};
use crate::shield::*; use crate::shield::{Frame, Hsts, NoSniff, Permission, Policy};
use crate::trace::*; use crate::trace::{Trace, TraceAll};
/// A [`Fairing`] that injects browser security and privacy headers into all /// A [`Fairing`] that injects browser security and privacy headers into all
/// outgoing responses. /// outgoing responses.
@ -195,7 +195,7 @@ impl Fairing for Shield {
self.force_hsts.store(true, Ordering::Release); 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(); self.policies.values().trace_all_info();
if force_hsts { if force_hsts {
@ -211,7 +211,7 @@ impl Fairing for Shield {
// the header is not already in the response. // the header is not already in the response.
for header in self.policies.values() { for header in self.policies.values() {
if response.headers().contains(header.name()) { 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(); 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 { macro_rules! declare_span_macro {
($($name:ident $level:ident),* $(,)?) => ( ($name:ident $level:ident) => (
$(declare_span_macro!([$] $name $level);)* declare_span_macro!([$] $name $level);
); );
([$d:tt] $name:ident $level:ident) => ( ([$d:tt] $name:ident $level:ident) => (
#[doc(hidden)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! $name { macro_rules! $name {
($n:literal $d ([ $d ($f:tt)* ])? => $in_scope:expr) => ({ (@[$d ($t:tt)+] => $in_scope:expr) => ({
$crate::tracing::span!($crate::tracing::Level::$level, $n $d (, $d ($f)* )?) $crate::tracing::span!($crate::tracing::Level::$level, $d ($t)+)
.in_scope(|| $in_scope); .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; 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)] #[doc(hidden)]
#[macro_export] #[macro_export]
macro_rules! event { 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)] #[doc(inline)]
pub use event as event; pub use event as event;
declare_macro!( reexport!(tracing::error);
error error, reexport!(tracing::warn);
info info, reexport!(tracing::info);
trace trace, reexport!(tracing::debug);
debug debug, reexport!(tracing::trace);
warn warn
);
declare_span_macro!( #[doc(hidden)] pub use tracing::error;
error_span ERROR, #[doc(hidden)] pub use tracing::warn;
warn_span WARN, #[doc(hidden)] pub use tracing::info;
info_span INFO, #[doc(hidden)] pub use tracing::debug;
trace_span TRACE, #[doc(hidden)] pub use tracing::trace;
debug_span DEBUG,
);

View File

@ -8,14 +8,18 @@ pub mod subscriber;
pub(crate) mod level; pub(crate) mod level;
#[doc(inline)]
pub use macros::*;
#[doc(inline)] #[doc(inline)]
pub use traceable::{Trace, TraceAll}; pub use traceable::{Trace, TraceAll};
#[doc(inline)] #[doc(inline)]
pub use macros::*; pub use tracing::{Level, level_filters::LevelFilter};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
#[non_exhaustive]
pub enum TraceFormat { pub enum TraceFormat {
#[serde(rename = "pretty")] #[serde(rename = "pretty")]
#[serde(alias = "PRETTY")] #[serde(alias = "PRETTY")]
@ -27,6 +31,9 @@ pub enum TraceFormat {
#[cfg_attr(nightly, doc(cfg(feature = "trace")))] #[cfg_attr(nightly, doc(cfg(feature = "trace")))]
pub fn init<'a, T: Into<Option<&'a crate::Config>>>(config: T) { pub fn init<'a, T: Into<Option<&'a crate::Config>>>(config: T) {
#[cfg(not(feature = "trace"))]
let _ = config;
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
crate::trace::subscriber::RocketDynFmt::init(config.into()) 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("-- ") 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(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 { 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" => { "liftoff" => {
let prefix = self.prefix(meta); let prefix = self.prefix(meta);
println!("{prefix}{}{} {}", self.emoji("🚀 "), 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()); &data["endpoint"].paint(style).primary().bold().underline());
}, },
"route" => println!("{}", Formatter(|f| { "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") { 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(()) 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()))?; write!(f, "{}", &data["uri.base"].paint(style.primary()))?;
if let Some(name) = data.get("name") { 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(()) Ok(())

View File

@ -47,10 +47,15 @@ impl Trace for Figment {
fn trace(&self, level: Level) { fn trace(&self, level: Level) {
for param in Config::PARAMETERS { for param in Config::PARAMETERS {
if let Some(source) = self.find_metadata(param) { if let Some(source) = self.find_metadata(param) {
if param.contains("secret") {
continue;
}
event! { level, "figment", event! { level, "figment",
param, param,
%source.name, %source.name,
source.source = source.source.as_ref().map(display), 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.base = %self.uri.base(),
uri.unmounted = %self.uri.unmounted(), uri.unmounted = %self.uri.unmounted(),
format = self.format.as_ref().map(display), 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", event! { Level::DEBUG, "sentinels",
@ -165,6 +173,9 @@ impl Trace for Catcher {
}), }),
rank = self.rank, rank = self.rank,
uri.base = %self.base(), 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 { impl Trace for figment::Error {
fn trace(&self, _: Level) { fn trace(&self, _: Level) {
for e in self.clone() { for e in self.clone() {
let span = tracing::error_span! { span_error!("config",
"config",
key = (!e.path.is_empty()).then_some(&e.path).and_then(|path| { key = (!e.path.is_empty()).then_some(&e.path).and_then(|path| {
let (profile, metadata) = (e.profile.as_ref()?, e.metadata.as_ref()?); let (profile, metadata) = (e.profile.as_ref()?, e.metadata.as_ref()?);
Some(metadata.interpolate(profile, path)) Some(metadata.interpolate(profile, path))
}), }),
source.name = e.metadata.as_ref().map(|m| &*m.name), 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), source.source = e.metadata.as_ref().and_then(|m| m.source.as_ref()).map(display)
}; => e.kind.trace_error());
span.in_scope(|| e.kind.trace_error());
} }
} }
} }
@ -298,7 +306,7 @@ impl Trace for ErrorKind {
e.trace(level); e.trace(level);
} else { } else {
event!(level, "error::bind", event!(level, "error::bind",
?error, reason = %error,
endpoint = endpoint.as_ref().map(display), endpoint = endpoint.as_ref().map(display),
"binding to network interface failed" "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")] #[get("/two")]
fn two_states(_one: &State<u32>, _two: &State<String>) {} fn two_states(_one: &State<u32>, _two: &State<String>) {}

View File

@ -19,6 +19,7 @@ port = 8000
workers = 1 workers = 1
keep_alive = 0 keep_alive = 0
log_level = "info" log_level = "info"
log_format = "pretty"
[release] [release]
address = "127.0.0.1" address = "127.0.0.1"
@ -26,6 +27,7 @@ port = 8000
workers = 12 workers = 12
keep_alive = 5 keep_alive = 5
log_level = "error" log_level = "error"
log_format = "compact"
# NOTE: Don't (!) use this key! Generate your own and keep it private! # NOTE: Don't (!) use this key! Generate your own and keep it private!
# e.g. via `head -c64 /dev/urandom | base64` # e.g. via `head -c64 /dev/urandom | base64`
secret_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk=" 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) { async fn test_config(profile: &str) {
let provider = Config::figment().select(profile); let provider = Config::figment().select(profile);
@ -8,12 +9,14 @@ async fn test_config(profile: &str) {
"debug" => { "debug" => {
assert_eq!(config.workers, 1); assert_eq!(config.workers, 1);
assert_eq!(config.keep_alive, 0); 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" => { "release" => {
assert_eq!(config.workers, 12); assert_eq!(config.workers, 12);
assert_eq!(config.keep_alive, 5); 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()); assert!(!config.secret_key.is_zero());
} }
_ => { _ => {

View File

@ -76,7 +76,7 @@ fn rocket() -> _ {
.attach(AdHoc::on_request("PUT Rewriter", |req, _| { .attach(AdHoc::on_request("PUT Rewriter", |req, _| {
Box::pin(async move { Box::pin(async move {
if req.uri().path() == "/" { if req.uri().path() == "/" {
info_span!("PUT rewriter" => { span_info!("PUT rewriter" => {
req.trace_info(); req.trace_info();
info!("changing method to `PUT`"); info!("changing method to `PUT`");
req.set_method(Method::Put); req.set_method(Method::Put);

View File

@ -6,4 +6,4 @@ edition = "2021"
publish = false publish = false
[dependencies] [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) 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`. // Note: without the `..` in `opt..`, we'd need to pass `opt.emoji`, `opt.name`.
// //
// Try visiting: // 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/?name=Rocketeer&lang=en&emoji
// http://127.0.0.1:8000/?lang=ru&emoji&name=Rocketeer // http://127.0.0.1:8000/?lang=ru&emoji&name=Rocketeer
#[get("/?<lang>&<opt..>")] #[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(); let mut greeting = String::new();
if opt.emoji { if opt.emoji {
greeting.push_str("👋 "); greeting.push_str("👋 ");

View File

@ -159,7 +159,7 @@ fn not_found(request: &Request<'_>) -> content::RawHtml<String> {
/******************************* `Either` Responder ***************************/ /******************************* `Either` Responder ***************************/
use rocket::Either; use rocket::either::Either;
use rocket::response::content::{RawJson, RawMsgPack}; use rocket::response::content::{RawJson, RawMsgPack};
use rocket::http::uncased::AsUncased; 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> { pub async fn try_launch(self, config: Config) -> Result<Rocket<Ignite>, Error> {
use rocket::http::Method::*; 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"); 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>) { async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
let Some(tls_addr) = rocket.endpoints().find_map(|e| e.tls()?.tcp()) else { 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\ warn!("Main instance is not being served over TLS/TCP.\n\
Redirector refusing to start."); Redirector refusing to start.");
}); });
@ -95,7 +95,7 @@ impl Fairing for Redirector {
let shutdown = rocket.shutdown(); let shutdown = rocket.shutdown();
rocket::tokio::spawn(async move { rocket::tokio::spawn(async move {
if let Err(e) = this.try_launch(config).await { 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(); e.trace_error();
info!("shutting down main instance"); info!("shutting down main instance");
}); });

View File

@ -13,6 +13,7 @@ procspawn = "1"
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"
ipc-channel = "0.18" ipc-channel = "0.18"
rustls-pemfile = "2.1" rustls-pemfile = "2.1"
inventory = "0.3.15"
[dependencies.nix] [dependencies.nix]
version = "0.28" 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 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}; use crate::{Result, Error, Server};
@ -26,7 +26,7 @@ impl Client {
.connect_timeout(Duration::from_secs(5)) .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())? { let uri = match Uri::parse_any(url).map_err(|e| e.into_owned())? {
Uri::Origin(uri) => { Uri::Origin(uri) => {
let proto = if server.tls { "https" } else { "http" }; let proto = if server.tls { "https" } else { "http" };
@ -45,7 +45,16 @@ impl Client {
uri => return Err(Error::InvalidUri(uri.into_owned())), 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}; mod runner;
use std::process::ExitCode; mod servers;
use std::time::Duration; mod config;
use rocket::tokio::net::TcpListener; pub mod prelude {
use rocket::yansi::Paint; pub use rocket::*;
use rocket::{get, routes, Build, Rocket, State}; pub use rocket::fairing::*;
use rocket::listener::{unix::UnixListener, Endpoint}; pub use rocket::response::stream::*;
use rocket::tls::TlsListener;
use reqwest::{tls::TlsInfo, Identity}; pub use testbench::{Error, Result, *};
pub use crate::register;
use testbench::*; pub use crate::config::*;
static DEFAULT_CONFIG: &str = r#"
[default]
address = "tcp:127.0.0.1"
workers = 2
port = 0
cli_colors = false
secret_key = "itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg="
[default.shutdown]
grace = 1
mercy = 1
"#;
static TLS_CONFIG: &str = r#"
[default.tls]
certs = "{ROCKET}/examples/tls/private/rsa_sha256_cert.pem"
key = "{ROCKET}/examples/tls/private/rsa_sha256_key.pem"
"#;
trait RocketExt {
fn default() -> Self;
fn tls_default() -> Self;
fn reconfigure_with_toml(self, toml: &str) -> Self;
} }
impl RocketExt for Rocket<Build> { pub use runner::Test;
fn default() -> Self {
rocket::build().reconfigure_with_toml(DEFAULT_CONFIG)
}
fn tls_default() -> Self { fn main() -> std::process::ExitCode {
rocket::build() runner::run()
.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);