mirror of https://github.com/rwf2/Rocket.git
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:
parent
45264de8c9
commit
926e06ef3c
|
@ -1,5 +1,3 @@
|
|||
use std::collections::hash_set::HashSet;
|
||||
|
||||
use criterion::{criterion_group, Criterion};
|
||||
|
||||
use rocket::{route, config, Request, Data, Route, Config};
|
||||
|
@ -80,12 +78,12 @@ fn generate_matching_requests<'c>(client: &'c Client, routes: &[Route]) -> Vec<L
|
|||
fn client(routes: Vec<Route>) -> Client {
|
||||
let config = Config {
|
||||
profile: Config::RELEASE_PROFILE,
|
||||
// log_level: rocket::config::LogLevel::Off,
|
||||
log_level: None,
|
||||
cli_colors: config::CliColors::Never,
|
||||
shutdown: config::ShutdownConfig {
|
||||
ctrlc: false,
|
||||
#[cfg(unix)]
|
||||
signals: HashSet::new(),
|
||||
signals: std::collections::hash_set::HashSet::new(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
|
|
|
@ -21,9 +21,9 @@ workspace = true
|
|||
deadpool_postgres = ["deadpool-postgres", "deadpool"]
|
||||
deadpool_redis = ["deadpool-redis", "deadpool"]
|
||||
# sqlx features
|
||||
sqlx_mysql = ["sqlx", "sqlx/mysql"]
|
||||
sqlx_postgres = ["sqlx", "sqlx/postgres"]
|
||||
sqlx_sqlite = ["sqlx", "sqlx/sqlite"]
|
||||
sqlx_mysql = ["sqlx", "sqlx/mysql", "log"]
|
||||
sqlx_postgres = ["sqlx", "sqlx/postgres", "log"]
|
||||
sqlx_sqlite = ["sqlx", "sqlx/sqlite", "log"]
|
||||
sqlx_macros = ["sqlx/macros"]
|
||||
# diesel features
|
||||
diesel_postgres = ["diesel-async/postgres", "diesel-async/deadpool", "diesel", "deadpool_09"]
|
||||
|
@ -86,6 +86,11 @@ default-features = false
|
|||
features = ["runtime-tokio-rustls"]
|
||||
optional = true
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dev-dependencies.rocket]
|
||||
path = "../../../core/lib"
|
||||
default-features = false
|
||||
|
|
|
@ -269,7 +269,7 @@ mod deadpool_old {
|
|||
mod sqlx {
|
||||
use sqlx::ConnectOptions;
|
||||
use super::{Duration, Error, Config, Figment};
|
||||
// use rocket::config::LogLevel;
|
||||
use rocket::tracing::level_filters::LevelFilter;
|
||||
|
||||
type Options<D> = <<D as sqlx::Database>::Connection as sqlx::Connection>::Options;
|
||||
|
||||
|
@ -301,12 +301,21 @@ mod sqlx {
|
|||
specialize(&mut opts, &config);
|
||||
|
||||
opts = opts.disable_statement_logging();
|
||||
// if let Ok(level) = figment.extract_inner::<LogLevel>(rocket::Config::LOG_LEVEL) {
|
||||
// if !matches!(level, LogLevel::Normal | LogLevel::Off) {
|
||||
// opts = opts.log_statements(level.into())
|
||||
// .log_slow_statements(level.into(), Duration::default());
|
||||
// }
|
||||
// }
|
||||
if let Ok(value) = figment.find_value(rocket::Config::LOG_LEVEL) {
|
||||
if let Some(level) = value.as_str().and_then(|v| v.parse().ok()) {
|
||||
let log_level = match level {
|
||||
LevelFilter::OFF => log::LevelFilter::Off,
|
||||
LevelFilter::ERROR => log::LevelFilter::Error,
|
||||
LevelFilter::WARN => log::LevelFilter::Warn,
|
||||
LevelFilter::INFO => log::LevelFilter::Info,
|
||||
LevelFilter::DEBUG => log::LevelFilter::Debug,
|
||||
LevelFilter::TRACE => log::LevelFilter::Trace,
|
||||
};
|
||||
|
||||
opts = opts.log_statements(log_level)
|
||||
.log_slow_statements(log_level, Duration::default());
|
||||
}
|
||||
}
|
||||
|
||||
sqlx::pool::PoolOptions::new()
|
||||
.max_connections(config.max_connections as u32)
|
||||
|
|
|
@ -42,20 +42,20 @@ impl Engine for Environment<'static> {
|
|||
Some(env)
|
||||
}
|
||||
|
||||
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> {
|
||||
let Ok(template) = self.get_template(name) else {
|
||||
error!("Minijinja template '{name}' was not found.");
|
||||
fn render<C: Serialize>(&self, template: &str, context: C) -> Option<String> {
|
||||
let Ok(templ) = self.get_template(template) else {
|
||||
error!(template, "requested template does not exist");
|
||||
return None;
|
||||
};
|
||||
|
||||
match template.render(context) {
|
||||
match templ.render(context) {
|
||||
Ok(result) => Some(result),
|
||||
Err(e) => {
|
||||
error_span!("Error rendering Minijinja template '{name}': {e}" => {
|
||||
let mut error = &e as &dyn std::error::Error;
|
||||
while let Some(source) = error.source() {
|
||||
error_!("caused by: {source}");
|
||||
error = source;
|
||||
span_error!("templating", template, "failed to render Minijinja template" => {
|
||||
let mut error = Some(&e as &dyn std::error::Error);
|
||||
while let Some(err) = error {
|
||||
error!("{err}");
|
||||
error = err.source();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ impl Engine for Tera {
|
|||
|
||||
// Finally try to tell Tera about all of the templates.
|
||||
if let Err(e) = tera.add_template_files(files) {
|
||||
error_span!("Tera templating initialization failed" => {
|
||||
span_error!("templating", "Tera templating initialization failed" => {
|
||||
let mut error = Some(&e as &dyn Error);
|
||||
while let Some(err) = error {
|
||||
error!("{err}");
|
||||
|
@ -48,7 +48,7 @@ impl Engine for Tera {
|
|||
match Tera::render(self, template, &tera_ctx) {
|
||||
Ok(string) => Some(string),
|
||||
Err(e) => {
|
||||
error_span!("failed to render Tera template {name}" [template] => {
|
||||
span_error!("templating", template, "failed to render Tera template" => {
|
||||
let mut error = Some(&e as &dyn Error);
|
||||
while let Some(err) = error {
|
||||
error!("{err}");
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use rocket::{Rocket, Build, Orbit};
|
||||
use rocket::fairing::{self, Fairing, Info, Kind};
|
||||
use rocket::figment::{Source, value::magic::RelativePathBuf};
|
||||
use rocket::trace::Trace;
|
||||
|
||||
use crate::context::{Callback, Context, ContextManager};
|
||||
use crate::template::DEFAULT_TEMPLATE_DIR;
|
||||
|
@ -40,7 +41,7 @@ impl Fairing for TemplateFairing {
|
|||
Ok(dir) => dir,
|
||||
Err(e) if e.missing() => DEFAULT_TEMPLATE_DIR.into(),
|
||||
Err(e) => {
|
||||
rocket::config::pretty_print_error(e);
|
||||
e.trace_error();
|
||||
return Err(rocket);
|
||||
}
|
||||
};
|
||||
|
@ -57,7 +58,7 @@ impl Fairing for TemplateFairing {
|
|||
let cm = rocket.state::<ContextManager>()
|
||||
.expect("Template ContextManager registered in on_ignite");
|
||||
|
||||
info_span!("templating" => {
|
||||
span_info!("templating" => {
|
||||
info!(directory = %Source::from(&*cm.context().root));
|
||||
info!(engines = ?Engines::ENABLED_EXTENSIONS);
|
||||
});
|
||||
|
|
|
@ -248,7 +248,7 @@ impl Template {
|
|||
})?;
|
||||
|
||||
let value = self.value.map_err(|e| {
|
||||
error_span!("template context failed to serialize" => e.trace_error());
|
||||
span_error!("templating", "template context failed to serialize" => e.trace_error());
|
||||
Status::InternalServerError
|
||||
})?;
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
|
|||
let config = match Config::from(database, &rocket) {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
error_span!("database configuration error" [database] => e.trace_error());
|
||||
span_error!("database configuration error", database => e.trace_error());
|
||||
return Err(rocket);
|
||||
}
|
||||
};
|
||||
|
@ -82,7 +82,7 @@ impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
|
|||
_marker: PhantomData,
|
||||
})),
|
||||
Err(Error::Config(e)) => {
|
||||
error_span!("database configuration error" [database] => e.trace_error());
|
||||
span_error!("database configuration error", database => e.trace_error());
|
||||
Err(rocket)
|
||||
}
|
||||
Err(Error::Pool(reason)) => {
|
||||
|
|
|
@ -80,9 +80,10 @@ pub fn _catch(
|
|||
}
|
||||
|
||||
#_catcher::StaticInfo {
|
||||
name: stringify!(#user_catcher_fn_name),
|
||||
name: ::core::stringify!(#user_catcher_fn_name),
|
||||
code: #status_code,
|
||||
handler: monomorphized_function,
|
||||
location: (::core::file!(), ::core::line!(), ::core::column!()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,8 +67,7 @@ impl EntryAttr for Launch {
|
|||
// Always infer the type as `Rocket<Build>`.
|
||||
if let syn::ReturnType::Type(_, ref mut ty) = &mut f.sig.output {
|
||||
if let syn::Type::Infer(_) = &mut **ty {
|
||||
let new = quote_spanned!(ty.span() => ::rocket::Rocket<::rocket::Build>);
|
||||
*ty = syn::parse2(new).expect("path is type");
|
||||
*ty = syn::parse_quote_spanned!(ty.span() => ::rocket::Rocket<::rocket::Build>);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,8 +104,9 @@ impl EntryAttr for Launch {
|
|||
}
|
||||
|
||||
let (vis, mut sig) = (&f.vis, f.sig.clone());
|
||||
sig.ident = syn::Ident::new("main", sig.ident.span());
|
||||
sig.output = syn::parse_quote!(-> #_ExitCode);
|
||||
sig.ident = syn::Ident::new("main", f.sig.ident.span());
|
||||
let ret_ty = _ExitCode.respanned(ty.span());
|
||||
sig.output = syn::parse_quote_spanned!(ty.span() => -> #ret_ty);
|
||||
sig.asyncness = None;
|
||||
|
||||
Ok(quote_spanned!(block.span() =>
|
||||
|
|
|
@ -105,9 +105,10 @@ fn query_decls(route: &Route) -> Option<TokenStream> {
|
|||
)*
|
||||
|
||||
if !__e.is_empty() {
|
||||
::rocket::info_span!("query string failed to match route declaration" => {
|
||||
for _err in __e { ::rocket::info!("{_err}"); }
|
||||
});
|
||||
::rocket::trace::span_info!("codegen",
|
||||
"query string failed to match route declaration" =>
|
||||
{ for _err in __e { ::rocket::trace::info!("{_err}"); } }
|
||||
);
|
||||
|
||||
return #Outcome::Forward((#__data, #Status::UnprocessableEntity));
|
||||
}
|
||||
|
@ -120,18 +121,25 @@ fn query_decls(route: &Route) -> Option<TokenStream> {
|
|||
fn request_guard_decl(guard: &Guard) -> TokenStream {
|
||||
let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty);
|
||||
define_spanned_export!(ty.span() =>
|
||||
__req, __data, _request, FromRequest, Outcome
|
||||
__req, __data, _request, display_hack, FromRequest, Outcome
|
||||
);
|
||||
|
||||
quote_spanned! { ty.span() =>
|
||||
let #ident: #ty = match <#ty as #FromRequest>::from_request(#__req).await {
|
||||
#Outcome::Success(__v) => __v,
|
||||
#Outcome::Forward(__e) => {
|
||||
::rocket::info!(type_name = stringify!(#ty), "guard forwarding");
|
||||
::rocket::trace::info!(name: "forward", parameter = stringify!(#ident),
|
||||
type_name = stringify!(#ty), status = __e.code,
|
||||
"request guard forwarding");
|
||||
|
||||
return #Outcome::Forward((#__data, __e));
|
||||
},
|
||||
#[allow(unreachable_code)]
|
||||
#Outcome::Error((__c, __e)) => {
|
||||
::rocket::info!(type_name = stringify!(#ty), "guard failed: {__e:?}");
|
||||
::rocket::trace::info!(name: "failure", parameter = stringify!(#ident),
|
||||
type_name = stringify!(#ty), reason = %#display_hack!(__e),
|
||||
"request guard failed");
|
||||
|
||||
return #Outcome::Error(__c);
|
||||
}
|
||||
};
|
||||
|
@ -142,14 +150,14 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
|
|||
let (i, name, ty) = (guard.index, &guard.name, &guard.ty);
|
||||
define_spanned_export!(ty.span() =>
|
||||
__req, __data, _None, _Some, _Ok, _Err,
|
||||
Outcome, FromSegments, FromParam, Status
|
||||
Outcome, FromSegments, FromParam, Status, display_hack
|
||||
);
|
||||
|
||||
// Returned when a dynamic parameter fails to parse.
|
||||
let parse_error = quote!({
|
||||
::rocket::info!(name: "forward",
|
||||
reason = %__error, parameter = #name, "type" = stringify!(#ty),
|
||||
"parameter forwarding");
|
||||
::rocket::trace::info!(name: "forward", parameter = #name,
|
||||
type_name = stringify!(#ty), reason = %#display_hack!(__error),
|
||||
"path guard forwarding");
|
||||
|
||||
#Outcome::Forward((#__data, #Status::UnprocessableEntity))
|
||||
});
|
||||
|
@ -161,10 +169,11 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
|
|||
match #__req.routed_segment(#i) {
|
||||
#_Some(__s) => match <#ty as #FromParam>::from_param(__s) {
|
||||
#_Ok(__v) => __v,
|
||||
#[allow(unreachable_code)]
|
||||
#_Err(__error) => return #parse_error,
|
||||
},
|
||||
#_None => {
|
||||
::rocket::error!(
|
||||
::rocket::trace::error!(
|
||||
"Internal invariant broken: dyn param {} not found.\n\
|
||||
Please report this to the Rocket issue tracker.\n\
|
||||
https://github.com/rwf2/Rocket/issues", #i);
|
||||
|
@ -176,6 +185,7 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
|
|||
true => quote_spanned! { ty.span() =>
|
||||
match <#ty as #FromSegments>::from_segments(#__req.routed_segments(#i..)) {
|
||||
#_Ok(__v) => __v,
|
||||
#[allow(unreachable_code)]
|
||||
#_Err(__error) => return #parse_error,
|
||||
}
|
||||
},
|
||||
|
@ -187,17 +197,24 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
|
|||
|
||||
fn data_guard_decl(guard: &Guard) -> TokenStream {
|
||||
let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty);
|
||||
define_spanned_export!(ty.span() => __req, __data, FromData, Outcome);
|
||||
define_spanned_export!(ty.span() => __req, __data, display_hack, FromData, Outcome);
|
||||
|
||||
quote_spanned! { ty.span() =>
|
||||
let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await {
|
||||
#Outcome::Success(__d) => __d,
|
||||
#Outcome::Forward((__d, __e)) => {
|
||||
::rocket::info!(type_name = stringify!(#ty), "data guard forwarding");
|
||||
::rocket::trace::info!(name: "forward", parameter = stringify!(#ident),
|
||||
type_name = stringify!(#ty), status = __e.code,
|
||||
"data guard forwarding");
|
||||
|
||||
return #Outcome::Forward((__d, __e));
|
||||
}
|
||||
#[allow(unreachable_code)]
|
||||
#Outcome::Error((__c, __e)) => {
|
||||
::rocket::info!(type_name = stringify!(#ty), "data guard failed: {__e:?}");
|
||||
::rocket::trace::info!(name: "failure", parameter = stringify!(#ident),
|
||||
type_name = stringify!(#ty), reason = %#display_hack!(__e),
|
||||
"data guard failed");
|
||||
|
||||
return #Outcome::Error(__c);
|
||||
}
|
||||
};
|
||||
|
@ -232,7 +249,7 @@ fn internal_uri_macro_decl(route: &Route) -> TokenStream {
|
|||
/// Rocket generated URI macro.
|
||||
macro_rules! #inner_macro_name {
|
||||
($($token:tt)*) => {{
|
||||
rocket::rocket_internal_uri!(#route_uri, (#(#uri_args),*), $($token)*)
|
||||
::rocket::rocket_internal_uri!(#route_uri, (#(#uri_args),*), $($token)*)
|
||||
}};
|
||||
}
|
||||
|
||||
|
@ -385,6 +402,7 @@ fn codegen_route(route: Route) -> Result<TokenStream> {
|
|||
format: #format,
|
||||
rank: #rank,
|
||||
sentinels: #sentinels,
|
||||
location: (::core::file!(), ::core::line!(), ::core::column!()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ define_exported_paths! {
|
|||
_Vec => ::std::vec::Vec,
|
||||
_Cow => ::std::borrow::Cow,
|
||||
_ExitCode => ::std::process::ExitCode,
|
||||
display_hack => ::rocket::error::display_hack,
|
||||
BorrowMut => ::std::borrow::BorrowMut,
|
||||
Outcome => ::rocket::outcome::Outcome,
|
||||
FromForm => ::rocket::form::FromForm,
|
||||
|
|
|
@ -1328,7 +1328,7 @@ pub fn catchers(input: TokenStream) -> TokenStream {
|
|||
/// assert_eq!(bob2.to_string(), "/person/Bob%20Smith");
|
||||
///
|
||||
/// #[get("/person/<age>")]
|
||||
/// fn ok(age: Result<u8, &str>) { }
|
||||
/// fn ok(age: Result<u8, std::num::ParseIntError>) { }
|
||||
///
|
||||
/// let kid1 = uri!(ok(age = 10));
|
||||
/// let kid2 = uri!(ok(12));
|
||||
|
|
|
@ -709,3 +709,19 @@ fn test_vec_in_query() {
|
|||
uri!(h(v = &[1, 2, 3][..])) => "/?v=%01%02%03",
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_either() {
|
||||
use rocket::either::{Either, Left, Right};
|
||||
|
||||
#[get("/<_foo>")]
|
||||
fn f(_foo: Either<usize, &str>) { }
|
||||
|
||||
assert_uri_eq! {
|
||||
uri!(f(Left::<usize, &str>(123))) => "/123",
|
||||
uri!(f(_foo = Left::<usize, &str>(710))) => "/710",
|
||||
|
||||
uri!(f(Right::<usize, &str>("hello world"))) => "/hello%20world",
|
||||
uri!(f(_foo = Right::<usize, &str>("bye?"))) => "/bye%3F",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,14 +114,6 @@ error[E0728]: `await` is only allowed inside `async` functions and blocks
|
|||
73 | let _ = rocket::build().launch().await;
|
||||
| ^^^^^ only allowed inside `async` functions and blocks
|
||||
|
||||
error[E0728]: `await` is only allowed inside `async` functions and blocks
|
||||
--> tests/ui-fail-nightly/async-entry.rs:73:42
|
||||
|
|
||||
72 | fn rocket() -> _ {
|
||||
| ----------- this is not `async`
|
||||
73 | let _ = rocket::build().launch().await;
|
||||
| ^^^^^ only allowed inside `async` functions and blocks
|
||||
|
||||
error[E0277]: `main` has invalid return type `Rocket<Build>`
|
||||
--> tests/ui-fail-nightly/async-entry.rs:94:20
|
||||
|
|
||||
|
|
|
@ -5,14 +5,14 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
|
|||
| ^ the trait `FromParam<'_>` is not implemented for `Q`
|
||||
|
|
||||
= help: the following other types implement trait `FromParam<'a>`:
|
||||
&'a str
|
||||
IpAddr
|
||||
Ipv4Addr
|
||||
Ipv6Addr
|
||||
NonZero<i128>
|
||||
NonZero<i16>
|
||||
NonZero<i32>
|
||||
NonZero<i64>
|
||||
<&'a str as FromParam<'a>>
|
||||
<IpAddr as FromParam<'a>>
|
||||
<Ipv4Addr as FromParam<'a>>
|
||||
<Ipv6Addr as FromParam<'a>>
|
||||
<NonZero<i128> as FromParam<'a>>
|
||||
<NonZero<i16> as FromParam<'a>>
|
||||
<NonZero<i32> as FromParam<'a>>
|
||||
<NonZero<i64> as FromParam<'a>>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Q: FromSegments<'_>` is not satisfied
|
||||
|
@ -104,14 +104,14 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
|
|||
| ^ the trait `FromParam<'_>` is not implemented for `Q`
|
||||
|
|
||||
= help: the following other types implement trait `FromParam<'a>`:
|
||||
&'a str
|
||||
IpAddr
|
||||
Ipv4Addr
|
||||
Ipv6Addr
|
||||
NonZero<i128>
|
||||
NonZero<i16>
|
||||
NonZero<i32>
|
||||
NonZero<i64>
|
||||
<&'a str as FromParam<'a>>
|
||||
<IpAddr as FromParam<'a>>
|
||||
<Ipv4Addr as FromParam<'a>>
|
||||
<Ipv6Addr as FromParam<'a>>
|
||||
<NonZero<i128> as FromParam<'a>>
|
||||
<NonZero<i16> as FromParam<'a>>
|
||||
<NonZero<i32> as FromParam<'a>>
|
||||
<NonZero<i64> as FromParam<'a>>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Q: FromRequest<'_>` is not satisfied
|
||||
|
@ -138,14 +138,14 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
|
|||
| ^ the trait `FromParam<'_>` is not implemented for `Q`
|
||||
|
|
||||
= help: the following other types implement trait `FromParam<'a>`:
|
||||
&'a str
|
||||
IpAddr
|
||||
Ipv4Addr
|
||||
Ipv6Addr
|
||||
NonZero<i128>
|
||||
NonZero<i16>
|
||||
NonZero<i32>
|
||||
NonZero<i64>
|
||||
<&'a str as FromParam<'a>>
|
||||
<IpAddr as FromParam<'a>>
|
||||
<Ipv4Addr as FromParam<'a>>
|
||||
<Ipv6Addr as FromParam<'a>>
|
||||
<NonZero<i128> as FromParam<'a>>
|
||||
<NonZero<i16> as FromParam<'a>>
|
||||
<NonZero<i32> as FromParam<'a>>
|
||||
<NonZero<i64> as FromParam<'a>>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
|
||||
|
@ -155,12 +155,12 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
|
|||
| ^ the trait `FromParam<'_>` is not implemented for `Q`
|
||||
|
|
||||
= help: the following other types implement trait `FromParam<'a>`:
|
||||
&'a str
|
||||
IpAddr
|
||||
Ipv4Addr
|
||||
Ipv6Addr
|
||||
NonZero<i128>
|
||||
NonZero<i16>
|
||||
NonZero<i32>
|
||||
NonZero<i64>
|
||||
<&'a str as FromParam<'a>>
|
||||
<IpAddr as FromParam<'a>>
|
||||
<Ipv4Addr as FromParam<'a>>
|
||||
<Ipv6Addr as FromParam<'a>>
|
||||
<NonZero<i128> as FromParam<'a>>
|
||||
<NonZero<i16> as FromParam<'a>>
|
||||
<NonZero<i32> as FromParam<'a>>
|
||||
<NonZero<i64> as FromParam<'a>>
|
||||
and $N others
|
||||
|
|
|
@ -86,7 +86,7 @@ error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Path, _>`
|
|||
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut &'a std::path::Path>>
|
||||
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>>
|
||||
and $N others
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:24:18
|
||||
|
@ -190,7 +190,7 @@ error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>
|
|||
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut &'a std::path::Path>>
|
||||
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>>
|
||||
and $N others
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:41:29
|
||||
|
@ -229,7 +229,7 @@ error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>
|
|||
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut &'a std::path::Path>>
|
||||
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>>
|
||||
and $N others
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:68:25
|
||||
|
@ -298,7 +298,7 @@ error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>
|
|||
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut &'a std::path::Path>>
|
||||
<&'a std::path::Path as FromUriParam<rocket::http::uri::fmt::Path, &'x mut PathBuf>>
|
||||
and $N others
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:15:15
|
||||
|
@ -450,7 +450,7 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:49:17
|
||||
|
@ -462,7 +462,7 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, i64>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:51:22
|
||||
|
@ -474,7 +474,7 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:58:25
|
||||
|
@ -487,7 +487,7 @@ error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::fmt::Path, s
|
|||
<i32 as FromUriParam<P, &'x mut i32>>
|
||||
<i32 as FromUriParam<P, i32>>
|
||||
= note: required for `std::option::Option<i32>` to implement `FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>`
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query, &str>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:60:19
|
||||
|
@ -499,7 +499,7 @@ error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query
|
|||
<isize as FromUriParam<P, &'x isize>>
|
||||
<isize as FromUriParam<P, &'x mut isize>>
|
||||
<isize as FromUriParam<P, isize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query, &str>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:62:24
|
||||
|
@ -511,7 +511,7 @@ error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query
|
|||
<isize as FromUriParam<P, &'x isize>>
|
||||
<isize as FromUriParam<P, &'x mut isize>>
|
||||
<isize as FromUriParam<P, isize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:79:40
|
||||
|
@ -523,7 +523,7 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:80:33
|
||||
|
@ -535,7 +535,7 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:83:25
|
||||
|
@ -547,7 +547,7 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> tests/ui-fail-nightly/typed-uri-bad-type.rs:84:25
|
||||
|
@ -559,4 +559,4 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
|
|
@ -10,7 +10,7 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied
|
|||
<Box<str> as Responder<'r, 'static>>
|
||||
<Box<[u8]> as Responder<'r, 'static>>
|
||||
<Box<T> as Responder<'r, 'o>>
|
||||
<rocket::Either<T, E> as Responder<'r, 'o>>
|
||||
<rocket::either::Either<T, E> as Responder<'r, 'o>>
|
||||
<Cow<'o, R> as Responder<'r, 'o>>
|
||||
<rocket::tokio::fs::File as Responder<'r, 'static>>
|
||||
<EventStream<S> as Responder<'r, 'r>>
|
||||
|
@ -29,7 +29,7 @@ error[E0277]: the trait bound `bool: Responder<'_, '_>` is not satisfied
|
|||
<Box<str> as Responder<'r, 'static>>
|
||||
<Box<[u8]> as Responder<'r, 'static>>
|
||||
<Box<T> as Responder<'r, 'o>>
|
||||
<rocket::Either<T, E> as Responder<'r, 'o>>
|
||||
<rocket::either::Either<T, E> as Responder<'r, 'o>>
|
||||
<Cow<'o, R> as Responder<'r, 'o>>
|
||||
<rocket::tokio::fs::File as Responder<'r, 'static>>
|
||||
<EventStream<S> as Responder<'r, 'r>>
|
||||
|
@ -62,7 +62,7 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied
|
|||
<Box<str> as Responder<'r, 'static>>
|
||||
<Box<[u8]> as Responder<'r, 'static>>
|
||||
<Box<T> as Responder<'r, 'o>>
|
||||
<rocket::Either<T, E> as Responder<'r, 'o>>
|
||||
<rocket::either::Either<T, E> as Responder<'r, 'o>>
|
||||
<Cow<'o, R> as Responder<'r, 'o>>
|
||||
<rocket::tokio::fs::File as Responder<'r, 'static>>
|
||||
<EventStream<S> as Responder<'r, 'r>>
|
||||
|
@ -81,7 +81,7 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied
|
|||
<Box<str> as Responder<'r, 'static>>
|
||||
<Box<[u8]> as Responder<'r, 'static>>
|
||||
<Box<T> as Responder<'r, 'o>>
|
||||
<rocket::Either<T, E> as Responder<'r, 'o>>
|
||||
<rocket::either::Either<T, E> as Responder<'r, 'o>>
|
||||
<Cow<'o, R> as Responder<'r, 'o>>
|
||||
<rocket::tokio::fs::File as Responder<'r, 'static>>
|
||||
<EventStream<S> as Responder<'r, 'r>>
|
||||
|
|
|
@ -137,9 +137,9 @@ error[E0277]: the trait bound `Unknown: FromForm<'r>` is not satisfied
|
|||
<HashMap<K, V> as FromForm<'v>>
|
||||
<BTreeMap<K, V> as FromForm<'v>>
|
||||
<Arc<T> as FromForm<'v>>
|
||||
<Vec<T> as FromForm<'v>>
|
||||
<form::from_form::_::proxy::Range<T> as FromForm<'r>>
|
||||
<form::from_form::_::proxy::RangeFrom<T> as FromForm<'r>>
|
||||
<form::from_form::_::proxy::RangeTo<T> as FromForm<'r>>
|
||||
and $N others
|
||||
= note: required for `Unknown` to implement `FromForm<'r>`
|
||||
|
||||
|
@ -196,9 +196,9 @@ error[E0277]: the trait bound `Foo<usize>: FromForm<'r>` is not satisfied
|
|||
<HashMap<K, V> as FromForm<'v>>
|
||||
<BTreeMap<K, V> as FromForm<'v>>
|
||||
<Arc<T> as FromForm<'v>>
|
||||
<Vec<T> as FromForm<'v>>
|
||||
<form::from_form::_::proxy::Range<T> as FromForm<'r>>
|
||||
<form::from_form::_::proxy::RangeFrom<T> as FromForm<'r>>
|
||||
<form::from_form::_::proxy::RangeTo<T> as FromForm<'r>>
|
||||
and $N others
|
||||
= note: required for `Foo<usize>` to implement `FromForm<'r>`
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ error[E0277]: the trait bound `u8: Responder<'_, '_>` is not satisfied
|
|||
<Box<str> as Responder<'r, 'static>>
|
||||
<Box<[u8]> as Responder<'r, 'static>>
|
||||
<Box<T> as Responder<'r, 'o>>
|
||||
<rocket::Either<T, E> as Responder<'r, 'o>>
|
||||
<rocket::either::Either<T, E> as Responder<'r, 'o>>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Header<'_>: From<u8>` is not satisfied
|
||||
|
@ -52,7 +52,7 @@ error[E0277]: the trait bound `u8: Responder<'_, '_>` is not satisfied
|
|||
<Box<str> as Responder<'r, 'static>>
|
||||
<Box<[u8]> as Responder<'r, 'static>>
|
||||
<Box<T> as Responder<'r, 'o>>
|
||||
<rocket::Either<T, E> as Responder<'r, 'o>>
|
||||
<rocket::either::Either<T, E> as Responder<'r, 'o>>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Header<'_>: From<u8>` is not satisfied
|
||||
|
@ -117,7 +117,7 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied
|
|||
<Box<str> as Responder<'r, 'static>>
|
||||
<Box<[u8]> as Responder<'r, 'static>>
|
||||
<Box<T> as Responder<'r, 'o>>
|
||||
<rocket::Either<T, E> as Responder<'r, 'o>>
|
||||
<rocket::either::Either<T, E> as Responder<'r, 'o>>
|
||||
and $N others
|
||||
note: required by a bound in `route::handler::<impl Outcome<Response<'o>, Status, (rocket::Data<'o>, Status)>>::from`
|
||||
--> $WORKSPACE/core/lib/src/route/handler.rs
|
||||
|
|
|
@ -5,14 +5,14 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
|
|||
| ^ the trait `FromParam<'_>` is not implemented for `Q`
|
||||
|
|
||||
= help: the following other types implement trait `FromParam<'a>`:
|
||||
bool
|
||||
isize
|
||||
i8
|
||||
i16
|
||||
i32
|
||||
i64
|
||||
i128
|
||||
usize
|
||||
<bool as FromParam<'a>>
|
||||
<isize as FromParam<'a>>
|
||||
<i8 as FromParam<'a>>
|
||||
<i16 as FromParam<'a>>
|
||||
<i32 as FromParam<'a>>
|
||||
<i64 as FromParam<'a>>
|
||||
<i128 as FromParam<'a>>
|
||||
<usize as FromParam<'a>>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Q: FromSegments<'_>` is not satisfied
|
||||
|
@ -104,14 +104,14 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
|
|||
| ^ the trait `FromParam<'_>` is not implemented for `Q`
|
||||
|
|
||||
= help: the following other types implement trait `FromParam<'a>`:
|
||||
bool
|
||||
isize
|
||||
i8
|
||||
i16
|
||||
i32
|
||||
i64
|
||||
i128
|
||||
usize
|
||||
<bool as FromParam<'a>>
|
||||
<isize as FromParam<'a>>
|
||||
<i8 as FromParam<'a>>
|
||||
<i16 as FromParam<'a>>
|
||||
<i32 as FromParam<'a>>
|
||||
<i64 as FromParam<'a>>
|
||||
<i128 as FromParam<'a>>
|
||||
<usize as FromParam<'a>>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Q: FromRequest<'_>` is not satisfied
|
||||
|
@ -138,14 +138,14 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
|
|||
| ^ the trait `FromParam<'_>` is not implemented for `Q`
|
||||
|
|
||||
= help: the following other types implement trait `FromParam<'a>`:
|
||||
bool
|
||||
isize
|
||||
i8
|
||||
i16
|
||||
i32
|
||||
i64
|
||||
i128
|
||||
usize
|
||||
<bool as FromParam<'a>>
|
||||
<isize as FromParam<'a>>
|
||||
<i8 as FromParam<'a>>
|
||||
<i16 as FromParam<'a>>
|
||||
<i32 as FromParam<'a>>
|
||||
<i64 as FromParam<'a>>
|
||||
<i128 as FromParam<'a>>
|
||||
<usize as FromParam<'a>>
|
||||
and $N others
|
||||
|
||||
error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
|
||||
|
@ -155,12 +155,12 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied
|
|||
| ^ the trait `FromParam<'_>` is not implemented for `Q`
|
||||
|
|
||||
= help: the following other types implement trait `FromParam<'a>`:
|
||||
bool
|
||||
isize
|
||||
i8
|
||||
i16
|
||||
i32
|
||||
i64
|
||||
i128
|
||||
usize
|
||||
<bool as FromParam<'a>>
|
||||
<isize as FromParam<'a>>
|
||||
<i8 as FromParam<'a>>
|
||||
<i16 as FromParam<'a>>
|
||||
<i32 as FromParam<'a>>
|
||||
<i64 as FromParam<'a>>
|
||||
<i128 as FromParam<'a>>
|
||||
<usize as FromParam<'a>>
|
||||
and $N others
|
||||
|
|
|
@ -86,7 +86,7 @@ error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Path, _>`
|
|||
<i8 as FromUriParam<P, i8>>
|
||||
<i8 as FromUriParam<P, &'x i8>>
|
||||
and $N others
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:24:18
|
||||
|
@ -190,7 +190,7 @@ error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>
|
|||
<i8 as FromUriParam<P, i8>>
|
||||
<i8 as FromUriParam<P, &'x i8>>
|
||||
and $N others
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:41:29
|
||||
|
@ -229,7 +229,7 @@ error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>
|
|||
<i8 as FromUriParam<P, i8>>
|
||||
<i8 as FromUriParam<P, &'x i8>>
|
||||
and $N others
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:68:25
|
||||
|
@ -298,7 +298,7 @@ error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>
|
|||
<i8 as FromUriParam<P, i8>>
|
||||
<i8 as FromUriParam<P, &'x i8>>
|
||||
and $N others
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:15:15
|
||||
|
@ -450,7 +450,7 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:49:17
|
||||
|
@ -462,7 +462,7 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, i64>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:51:22
|
||||
|
@ -474,7 +474,7 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:58:25
|
||||
|
@ -487,7 +487,7 @@ error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::fmt::Path, s
|
|||
<i32 as FromUriParam<P, &'x i32>>
|
||||
<i32 as FromUriParam<P, &'x mut i32>>
|
||||
= note: required for `std::option::Option<i32>` to implement `FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>`
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query, &str>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:60:19
|
||||
|
@ -499,7 +499,7 @@ error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query
|
|||
<isize as FromUriParam<P, isize>>
|
||||
<isize as FromUriParam<P, &'x isize>>
|
||||
<isize as FromUriParam<P, &'x mut isize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query, &str>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:62:24
|
||||
|
@ -511,7 +511,7 @@ error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query
|
|||
<isize as FromUriParam<P, isize>>
|
||||
<isize as FromUriParam<P, &'x isize>>
|
||||
<isize as FromUriParam<P, &'x mut isize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:79:40
|
||||
|
@ -523,7 +523,7 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:80:33
|
||||
|
@ -535,7 +535,7 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:83:25
|
||||
|
@ -547,7 +547,7 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> tests/ui-fail-stable/typed-uri-bad-type.rs:84:25
|
||||
|
@ -559,4 +559,4 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path,
|
|||
<usize as FromUriParam<P, usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
= note: this error originates in the macro `rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
= note: this error originates in the macro `::rocket::rocket_internal_uri` which comes from the expansion of the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use either::Either;
|
||||
|
||||
use crate::uri::fmt::UriDisplay;
|
||||
use crate::uri::fmt::{self, Part};
|
||||
|
@ -61,7 +63,7 @@ use crate::uri::fmt::{self, Part};
|
|||
///
|
||||
/// * `String`, `i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `u8`, `u16`,
|
||||
/// `u32`, `u64`, `u128`, `usize`, `f32`, `f64`, `bool`, `IpAddr`,
|
||||
/// `Ipv4Addr`, `Ipv6Addr`, `&str`, `Cow<str>`
|
||||
/// `Ipv4Addr`, `Ipv6Addr`, `&str`, `Cow<str>`, `Either<A, B>`
|
||||
///
|
||||
/// The following types have _identity_ implementations _only in [`Path`]_:
|
||||
///
|
||||
|
@ -375,7 +377,9 @@ impl<A, T: FromUriParam<fmt::Path, A>> FromUriParam<fmt::Path, A> for Option<T>
|
|||
}
|
||||
|
||||
/// A no cost conversion allowing `T` to be used in place of an `Result<T, E>`.
|
||||
impl<A, E, T: FromUriParam<fmt::Path, A>> FromUriParam<fmt::Path, A> for Result<T, E> {
|
||||
impl<A, E, T> FromUriParam<fmt::Path, A> for Result<T, E>
|
||||
where T: FromUriParam<fmt::Path, A>
|
||||
{
|
||||
type Target = T::Target;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -384,6 +388,19 @@ impl<A, E, T: FromUriParam<fmt::Path, A>> FromUriParam<fmt::Path, A> for Result<
|
|||
}
|
||||
}
|
||||
|
||||
impl<P: Part, A, B, T, U> FromUriParam<P, Either<A, B>> for Either<T, U>
|
||||
where T: FromUriParam<P, A>, U: FromUriParam<P, B>
|
||||
{
|
||||
type Target = Either<T::Target, U::Target>;
|
||||
|
||||
fn from_uri_param(param: Either<A, B>) -> Self::Target {
|
||||
match param {
|
||||
Either::Left(a) => Either::Left(T::from_uri_param(a)),
|
||||
Either::Right(b) => Either::Right(U::from_uri_param(b)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, T: FromUriParam<fmt::Query, A>> FromUriParam<fmt::Query, Option<A>> for Option<T> {
|
||||
type Target = Option<T::Target>;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::collections::{BTreeMap, HashMap};
|
|||
use std::{fmt, path};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use either::Either;
|
||||
use time::{macros::format_description, format_description::FormatItem};
|
||||
|
||||
use crate::RawStr;
|
||||
|
@ -421,6 +422,17 @@ impl<P: Part, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &T {
|
|||
}
|
||||
}
|
||||
|
||||
/// Defers to `T` or `U` in `Either<T, U>`.
|
||||
impl<P: Part, T: UriDisplay<P>, U: UriDisplay<P>> UriDisplay<P> for Either<T, U> {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
||||
match self {
|
||||
Either::Left(t) => UriDisplay::fmt(t, f),
|
||||
Either::Right(u) => UriDisplay::fmt(u, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defers to the `UriDisplay<P>` implementation for `T`.
|
||||
impl<P: Part, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &mut T {
|
||||
#[inline(always)]
|
||||
|
|
|
@ -185,7 +185,7 @@ type TestData<'a> = (
|
|||
fn fuzz((route_a, route_b, req): TestData<'_>) {
|
||||
let rocket = rocket::custom(rocket::Config {
|
||||
workers: 2,
|
||||
// log_level: rocket::log::LogLevel::Off,
|
||||
log_level: None,
|
||||
cli_colors: rocket::config::CliColors::Never,
|
||||
..rocket::Config::debug_default()
|
||||
});
|
||||
|
|
|
@ -127,6 +127,9 @@ pub struct Catcher {
|
|||
///
|
||||
/// This is -(number of nonempty segments in base).
|
||||
pub(crate) rank: isize,
|
||||
|
||||
/// The catcher's file, line, and column location.
|
||||
pub(crate) location: Option<(&'static str, u32, u32)>,
|
||||
}
|
||||
|
||||
// The rank is computed as -(number of nonempty segments in base) => catchers
|
||||
|
@ -185,7 +188,8 @@ impl Catcher {
|
|||
base: uri::Origin::root().clone(),
|
||||
handler: Box::new(handler),
|
||||
rank: rank(uri::Origin::root().path()),
|
||||
code
|
||||
code,
|
||||
location: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,6 +332,8 @@ pub struct StaticInfo {
|
|||
pub code: Option<u16>,
|
||||
/// The catcher's handler, i.e, the annotated function.
|
||||
pub handler: for<'r> fn(Status, &'r Request<'_>) -> BoxFuture<'r>,
|
||||
/// The file, line, and column where the catcher was defined.
|
||||
pub location: (&'static str, u32, u32),
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -336,6 +342,7 @@ impl From<StaticInfo> for Catcher {
|
|||
fn from(info: StaticInfo) -> Catcher {
|
||||
let mut catcher = Catcher::new(info.code, info.handler);
|
||||
catcher.name = Some(info.name.into());
|
||||
catcher.location = Some(info.location);
|
||||
catcher
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,13 @@ use figment::{Figment, Profile, Provider, Metadata, error::Result};
|
|||
use figment::providers::{Serialized, Env, Toml, Format};
|
||||
use figment::value::{Map, Dict, magic::RelativePathBuf};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::Level;
|
||||
|
||||
#[cfg(feature = "secrets")]
|
||||
use crate::config::SecretKey;
|
||||
use crate::config::{ShutdownConfig, Ident, CliColors};
|
||||
use crate::config::{ShutdownConfig, Level, TraceFormat, Ident, CliColors};
|
||||
use crate::request::{self, Request, FromRequest};
|
||||
use crate::http::uncased::Uncased;
|
||||
use crate::data::Limits;
|
||||
use crate::trace::{Trace, TraceFormat};
|
||||
|
||||
/// Rocket server configuration.
|
||||
///
|
||||
|
@ -288,7 +286,7 @@ impl Config {
|
|||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If extraction fails, prints an error message indicating the error and
|
||||
/// If extraction fails, logs an error message indicating the error and
|
||||
/// panics. For a version that doesn't panic, use [`Config::try_from()`].
|
||||
///
|
||||
/// # Example
|
||||
|
@ -306,7 +304,12 @@ impl Config {
|
|||
/// let config = Config::from(figment);
|
||||
/// ```
|
||||
pub fn from<T: Provider>(provider: T) -> Self {
|
||||
Self::try_from(provider).unwrap_or_else(bail_with_config_error)
|
||||
use crate::trace::Trace;
|
||||
|
||||
Self::try_from(provider).unwrap_or_else(|e| {
|
||||
e.trace_error();
|
||||
panic!("aborting due to configuration error(s)")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,15 +436,3 @@ impl<'r> FromRequest<'r> for &'r Config {
|
|||
request::Outcome::Success(req.rocket().config())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn bail_with_config_error<T>(error: figment::Error) -> T {
|
||||
pretty_print_error(error);
|
||||
panic!("aborting due to configuration error(s)")
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
// FIXME: Remove this function.
|
||||
pub fn pretty_print_error(error: figment::Error) {
|
||||
error.trace_error()
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ pub use ident::Ident;
|
|||
pub use config::Config;
|
||||
pub use cli_colors::CliColors;
|
||||
|
||||
// pub use crate::log::LogLevel;
|
||||
pub use crate::trace::{TraceFormat, Level};
|
||||
pub use crate::shutdown::ShutdownConfig;
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
|
@ -139,6 +139,3 @@ pub use crate::shutdown::Sig;
|
|||
|
||||
#[cfg(feature = "secrets")]
|
||||
pub use secret_key::SecretKey;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use config::{pretty_print_error, bail_with_config_error};
|
||||
|
|
|
@ -7,8 +7,8 @@ use std::sync::Arc;
|
|||
use figment::Profile;
|
||||
|
||||
use crate::listener::Endpoint;
|
||||
use crate::trace::Trace;
|
||||
use crate::{Ignite, Orbit, Phase, Rocket};
|
||||
use crate::trace::Trace;
|
||||
|
||||
/// An error that occurs during launch.
|
||||
///
|
||||
|
@ -84,7 +84,7 @@ impl Error {
|
|||
match result {
|
||||
Ok(_) => process::ExitCode::SUCCESS,
|
||||
Err(e) => {
|
||||
error_span!("aborting launch due to error" => e.trace_error());
|
||||
span_error!("error", "aborting launch due to error" => e.trace_error());
|
||||
process::ExitCode::SUCCESS
|
||||
}
|
||||
}
|
||||
|
@ -200,14 +200,14 @@ impl fmt::Display for ServerError<'_> {
|
|||
pub(crate) fn log_server_error(error: &(dyn StdError + 'static)) {
|
||||
let mut error: &(dyn StdError + 'static) = error;
|
||||
if error.downcast_ref::<hyper::Error>().is_some() {
|
||||
warn_span!("minor server error" ["{}", ServerError(error)] => {
|
||||
span_warn!("request error", "{}", ServerError(error) => {
|
||||
while let Some(source) = error.source() {
|
||||
error = source;
|
||||
warn!("{}", ServerError(error));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
error_span!("server error" ["{}", ServerError(error)] => {
|
||||
span_error!("server error", "{}", ServerError(error) => {
|
||||
while let Some(source) = error.source() {
|
||||
error = source;
|
||||
error!("{}", ServerError(error));
|
||||
|
@ -215,3 +215,59 @@ pub(crate) fn log_server_error(error: &(dyn StdError + 'static)) {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod display_hack_impl {
|
||||
use super::*;
|
||||
use crate::util::Formatter;
|
||||
|
||||
/// The *magic*.
|
||||
///
|
||||
/// This type implements a `display()` method using an internal `T` that is
|
||||
/// either `fmt::Display` _or_ `fmt::Debug`, using the former when
|
||||
/// available. It does so by using a "specialization" hack: it has a blanket
|
||||
/// DefaultDisplay trait impl for all types that are `fmt::Debug` and a
|
||||
/// "specialized" inherent impl for all types that are `fmt::Display`.
|
||||
///
|
||||
/// As long as `T: Display`, the "specialized" impl is what Rust will
|
||||
/// resolve `DisplayHack(v).display()` to when `T: fmt::Display` as it is an
|
||||
/// inherent impl. Otherwise, Rust will fall back to the blanket impl.
|
||||
pub struct DisplayHack<T: ?Sized>(pub T);
|
||||
|
||||
pub trait DefaultDisplay {
|
||||
fn display(&self) -> impl fmt::Display;
|
||||
}
|
||||
|
||||
/// Blanket implementation for `T: Debug`. This is what Rust will resolve
|
||||
/// `DisplayHack<T>::display` to when `T: Debug`.
|
||||
impl<T: fmt::Debug + ?Sized> DefaultDisplay for DisplayHack<T> {
|
||||
#[inline(always)]
|
||||
fn display(&self) -> impl fmt::Display {
|
||||
Formatter(|f| fmt::Debug::fmt(&self.0, f))
|
||||
}
|
||||
}
|
||||
|
||||
/// "Specialized" implementation for `T: Display`. This is what Rust will
|
||||
/// resolve `DisplayHack<T>::display` to when `T: Display`.
|
||||
impl<T: fmt::Display + fmt::Debug + ?Sized> DisplayHack<T> {
|
||||
#[inline(always)]
|
||||
pub fn display(&self) -> impl fmt::Display + '_ {
|
||||
Formatter(|f| fmt::Display::fmt(&self.0, f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! display_hack {
|
||||
($v:expr) => ({
|
||||
#[allow(unused_imports)]
|
||||
use $crate::error::display_hack_impl::{DisplayHack, DefaultDisplay as _};
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
DisplayHack($v).display()
|
||||
})
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use display_hack as display_hack;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use futures::future::{Future, BoxFuture, FutureExt};
|
||||
use parking_lot::Mutex;
|
||||
use futures::future::{Future, BoxFuture, FutureExt};
|
||||
|
||||
use crate::route::RouteUri;
|
||||
use crate::{Rocket, Request, Response, Data, Build, Orbit};
|
||||
use crate::fairing::{Fairing, Kind, Info, Result};
|
||||
use crate::route::RouteUri;
|
||||
use crate::trace::Trace;
|
||||
|
||||
/// A ad-hoc fairing that can be created from a function or closure.
|
||||
///
|
||||
|
@ -235,7 +236,7 @@ impl AdHoc {
|
|||
let app_config = match rocket.figment().extract::<T>() {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
crate::config::pretty_print_error(e);
|
||||
e.trace_error();
|
||||
return Err(rocket);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -51,16 +51,6 @@ impl Fairings {
|
|||
iter!(self, self.active().collect::<HashSet<_>>().into_iter())
|
||||
.map(|v| v.1)
|
||||
.collect()
|
||||
// .into_iter()
|
||||
// .map(|i| )
|
||||
// if !active_fairings.is_empty() {
|
||||
// tracing::info_span!("fairings").in_scope(|| {
|
||||
// for (_, fairing) in iter!(self, active_fairings.into_iter()) {
|
||||
// let (name, kind) = (fairing.info().name, fairing.info().kind);
|
||||
// info!(name: "fairing", name, %kind)
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn add(&mut self, fairing: Box<dyn Fairing>) {
|
||||
|
|
|
@ -123,6 +123,7 @@ pub use tokio;
|
|||
pub use figment;
|
||||
pub use time;
|
||||
pub use tracing;
|
||||
pub use either;
|
||||
|
||||
#[macro_use]
|
||||
pub mod trace;
|
||||
|
@ -164,8 +165,6 @@ mod router;
|
|||
mod phase;
|
||||
mod erased;
|
||||
|
||||
#[doc(hidden)] pub use either::Either;
|
||||
|
||||
#[doc(inline)] pub use rocket_codegen::*;
|
||||
|
||||
#[doc(inline)] pub use crate::response::Response;
|
||||
|
@ -254,6 +253,11 @@ pub fn async_test<R>(fut: impl std::future::Future<Output = R>) -> R {
|
|||
/// WARNING: This is unstable! Do not use this method outside of Rocket!
|
||||
#[doc(hidden)]
|
||||
pub fn async_main<R>(fut: impl std::future::Future<Output = R> + Send) -> R {
|
||||
fn bail<T, E: crate::trace::Trace>(e: E) -> T {
|
||||
e.trace_error();
|
||||
panic!("aborting due to error")
|
||||
}
|
||||
|
||||
// FIXME: We need to run `fut` to get the user's `Figment` to properly set
|
||||
// up the async env, but we need the async env to run `fut`. So we're stuck.
|
||||
// Tokio doesn't let us take the state from one async env and migrate it to
|
||||
|
@ -263,8 +267,6 @@ pub fn async_main<R>(fut: impl std::future::Future<Output = R> + Send) -> R {
|
|||
// values won't reflect swaps of `Rocket` in attach fairings with different
|
||||
// config values, or values from non-Rocket configs. See tokio-rs/tokio#3329
|
||||
// for a necessary resolution in `tokio`.
|
||||
use config::bail_with_config_error as bail;
|
||||
|
||||
let fig = Config::figment();
|
||||
let workers = fig.extract_inner(Config::WORKERS).unwrap_or_else(bail);
|
||||
let max_blocking = fig.extract_inner(Config::MAX_BLOCKING).unwrap_or_else(bail);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use futures::future::{FutureExt, Future};
|
||||
|
||||
use crate::{route, catcher, Rocket, Orbit, Request, Response, Data};
|
||||
use crate::trace::Trace;
|
||||
use crate::util::Formatter;
|
||||
use crate::data::IoHandler;
|
||||
use crate::http::{Method, Status, Header};
|
||||
use crate::outcome::Outcome;
|
||||
use crate::form::Form;
|
||||
use crate::{route, catcher, Rocket, Orbit, Request, Response, Data};
|
||||
|
||||
// A token returned to force the execution of one method before another.
|
||||
pub(crate) struct RequestToken;
|
||||
|
@ -199,6 +199,7 @@ impl Rocket<Orbit> {
|
|||
let mut status = Status::NotFound;
|
||||
for route in self.router.route(request) {
|
||||
// Retrieve and set the requests parameters.
|
||||
route.trace_info();
|
||||
request.set_route(route);
|
||||
|
||||
let name = route.name.as_deref();
|
||||
|
@ -207,7 +208,6 @@ impl Rocket<Orbit> {
|
|||
|
||||
// Check if the request processing completed (Some) or if the
|
||||
// request needs to be forwarded. If it does, continue the loop
|
||||
route.trace_info();
|
||||
outcome.trace_info();
|
||||
match outcome {
|
||||
o@Outcome::Success(_) | o@Outcome::Error(_) => return o,
|
||||
|
@ -215,9 +215,7 @@ impl Rocket<Orbit> {
|
|||
}
|
||||
}
|
||||
|
||||
let outcome = Outcome::Forward((data, status));
|
||||
outcome.trace_info();
|
||||
outcome
|
||||
Outcome::Forward((data, status))
|
||||
}
|
||||
|
||||
// Invokes the catcher for `status`. Returns the response on success.
|
||||
|
|
|
@ -138,7 +138,7 @@ macro_rules! pub_client_impl {
|
|||
use crate::config;
|
||||
|
||||
let figment = rocket.figment().clone()
|
||||
// .merge((config::Config::LOG_LEVEL, config::LogLevel::Debug))
|
||||
.merge((config::Config::LOG_LEVEL, "debug"))
|
||||
.select(config::Config::DEBUG_PROFILE);
|
||||
|
||||
Self::tracked(rocket.reconfigure(figment)) $(.$suffix)?
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::str::FromStr;
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::error::Empty;
|
||||
use crate::either::Either;
|
||||
use crate::http::uri::{Segments, error::PathError, fmt::Path};
|
||||
|
||||
/// Trait to convert a dynamic path segment string to a concrete value.
|
||||
|
@ -40,29 +41,56 @@ use crate::http::uri::{Segments, error::PathError, fmt::Path};
|
|||
///
|
||||
/// Sometimes, a forward is not desired, and instead, we simply want to know
|
||||
/// that the dynamic path segment could not be parsed into some desired type
|
||||
/// `T`. In these cases, types of `Option<T>` or `Result<T, T::Error>` can be
|
||||
/// used. These types implement `FromParam` themselves. Their implementations
|
||||
/// always return successfully, so they never forward. They can be used to
|
||||
/// determine if the `FromParam` call failed and to retrieve the error value
|
||||
/// from the failed `from_param` call.
|
||||
/// `T`. In these cases, types of `Option<T>`, `Result<T, T::Error>`, or
|
||||
/// `Either<A, B>` can be used, which implement `FromParam` themselves.
|
||||
///
|
||||
/// For instance, imagine you've asked for an `<id>` as a `usize`. To determine
|
||||
/// when the `<id>` was not a valid `usize` and retrieve the string that failed
|
||||
/// to parse, you can use a `Result<usize, &str>` type for the `<id>` parameter
|
||||
/// as follows:
|
||||
/// * **`Option<T>`** _where_ **`T: FromParam`**
|
||||
///
|
||||
/// Always returns successfully.
|
||||
///
|
||||
/// If the conversion to `T` fails, `None` is returned. If the conversion
|
||||
/// succeeds, `Some(value)` is returned.
|
||||
///
|
||||
/// * **`Result<T, T::Error>`** _where_ **`T: FromParam`**
|
||||
///
|
||||
/// Always returns successfully.
|
||||
///
|
||||
/// If the conversion to `T` fails, `Err(error)` is returned. If the
|
||||
/// conversion succeeds, `Ok(value)` is returned.
|
||||
///
|
||||
/// * **`Either<A, B>`** _where_ **`A: FromParam`** _and_ **`B: FromParam`**
|
||||
///
|
||||
/// Fails only when both `A::from_param` and `B::from_param` fail. If one
|
||||
/// of the two succeeds, the successful value is returned in
|
||||
/// `Either::Left(A)` or `Either::Right(B)` variant, respectively. If both
|
||||
/// fail, the error values from both calls are returned in a tuple in the
|
||||
/// `Err` variant.
|
||||
///
|
||||
/// `Either<A, B>` is particularly useful with a `B` type of `&str`, allowing
|
||||
/// you to retrieve the invalid path segment. Because `&str`'s implementation of
|
||||
/// `FromParam` always succeeds, the `Right` variant of the `Either` will always
|
||||
/// contain the path segment in case of failure.
|
||||
///
|
||||
/// For instance, consider the following route and handler:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::either::{Either, Left, Right};
|
||||
///
|
||||
/// #[get("/<id>")]
|
||||
/// fn hello(id: Result<usize, &str>) -> String {
|
||||
/// fn hello(id: Either<usize, &str>) -> String {
|
||||
/// match id {
|
||||
/// Ok(id_num) => format!("usize: {}", id_num),
|
||||
/// Err(string) => format!("Not a usize: {}", string)
|
||||
/// Left(id_num) => format!("usize: {}", id_num),
|
||||
/// Right(string) => format!("Not a usize: {}", string)
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() { }
|
||||
/// ```
|
||||
///
|
||||
/// In the above example, if the dynamic path segment cannot be parsed into a
|
||||
/// `usize`, the raw path segment is returned in the `Right` variant of the
|
||||
/// `Either<usize, &str>` value.
|
||||
///
|
||||
/// # Provided Implementations
|
||||
///
|
||||
/// Rocket implements `FromParam` for several standard library types. Their
|
||||
|
@ -219,11 +247,11 @@ impl<'a> FromParam<'a> for String {
|
|||
macro_rules! impl_with_fromstr {
|
||||
($($T:ty),+) => ($(
|
||||
impl<'a> FromParam<'a> for $T {
|
||||
type Error = &'a str;
|
||||
type Error = <$T as FromStr>::Err;
|
||||
|
||||
#[inline(always)]
|
||||
fn from_param(param: &'a str) -> Result<Self, Self::Error> {
|
||||
<$T as FromStr>::from_str(param).map_err(|_| param)
|
||||
<$T as FromStr>::from_str(param)
|
||||
}
|
||||
}
|
||||
)+)
|
||||
|
@ -361,3 +389,23 @@ impl<'r, T: FromSegments<'r>> FromSegments<'r> for Option<T> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements `FromParam` for `Either<A, B>`, where `A` and `B` both implement
|
||||
/// `FromParam`. If `A::from_param` returns `Ok(a)`, `Either::Left(a)` is
|
||||
/// returned. If `B::from_param` returns `Ok(b)`, `Either::Right(b)` is
|
||||
/// returned. If both `A::from_param` and `B::from_param` return `Err(a)` and
|
||||
/// `Err(b)`, respectively, then `Err((a, b))` is returned.
|
||||
impl<'v, A: FromParam<'v>, B: FromParam<'v>> FromParam<'v> for Either<A, B> {
|
||||
type Error = (A::Error, B::Error);
|
||||
|
||||
#[inline(always)]
|
||||
fn from_param(param: &'v str) -> Result<Self, Self::Error> {
|
||||
match A::from_param(param) {
|
||||
Ok(a) => Ok(Either::Left(a)),
|
||||
Err(a) => match B::from_param(param) {
|
||||
Ok(b) => Ok(Either::Right(b)),
|
||||
Err(b) => Err((a, b)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -484,7 +484,8 @@ impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Option<R> {
|
|||
match self {
|
||||
Some(r) => r.respond_to(req),
|
||||
None => {
|
||||
debug!("{} responder returned `None`", std::any::type_name::<Self>());
|
||||
let type_name = std::any::type_name::<Self>();
|
||||
debug!(type_name, "`Option` responder returned `None`");
|
||||
Err(Status::NotFound)
|
||||
},
|
||||
}
|
||||
|
@ -506,13 +507,13 @@ impl<'r, 'o: 'r, 't: 'o, 'e: 'o, T, E> Responder<'r, 'o> for Result<T, E>
|
|||
|
||||
/// Responds with the wrapped `Responder` in `self`, whether it is `Left` or
|
||||
/// `Right`.
|
||||
impl<'r, 'o: 'r, 't: 'o, 'e: 'o, T, E> Responder<'r, 'o> for crate::Either<T, E>
|
||||
impl<'r, 'o: 'r, 't: 'o, 'e: 'o, T, E> Responder<'r, 'o> for either::Either<T, E>
|
||||
where T: Responder<'r, 't>, E: Responder<'r, 'e>
|
||||
{
|
||||
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
|
||||
match self {
|
||||
crate::Either::Left(r) => r.respond_to(req),
|
||||
crate::Either::Right(r) => r.respond_to(req),
|
||||
either::Either::Left(r) => r.respond_to(req),
|
||||
either::Either::Right(r) => r.respond_to(req),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -185,16 +185,9 @@ impl Rocket<Build> {
|
|||
/// ```
|
||||
#[must_use]
|
||||
pub fn custom<T: Provider>(provider: T) -> Self {
|
||||
// We initialize the logger here so that logging from fairings and so on
|
||||
// are visible; we use the final config to set a max log-level in ignite
|
||||
crate::trace::init(None);
|
||||
|
||||
let rocket: Rocket<Build> = Rocket(Building {
|
||||
figment: Figment::from(provider),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
rocket.attach(Shield::default())
|
||||
Rocket::<Build>(Building::default())
|
||||
.reconfigure(provider)
|
||||
.attach(Shield::default())
|
||||
}
|
||||
|
||||
/// Overrides the current configuration provider with `provider`.
|
||||
|
@ -237,7 +230,12 @@ impl Rocket<Build> {
|
|||
/// ```
|
||||
#[must_use]
|
||||
pub fn reconfigure<T: Provider>(mut self, provider: T) -> Self {
|
||||
// We initialize the logger here so that logging from fairings and so on
|
||||
// are visible; we use the final config to set a max log-level in ignite
|
||||
self.figment = Figment::from(provider);
|
||||
crate::trace::init(Config::try_from(&self.figment).ok().as_ref());
|
||||
span_trace!("reconfigure" => self.figment().trace_trace());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -566,14 +564,14 @@ impl Rocket<Build> {
|
|||
// Log everything we know: config, routes, catchers, fairings.
|
||||
// TODO: Store/print managed state type names?
|
||||
let fairings = self.fairings.unique_set();
|
||||
info_span!("config" [profile = %self.figment().profile()] => {
|
||||
span_info!("config", profile = %self.figment().profile() => {
|
||||
config.trace_info();
|
||||
self.figment().trace_debug();
|
||||
});
|
||||
|
||||
info_span!("routes" [count = self.routes.len()] => self.routes().trace_all_info());
|
||||
info_span!("catchers" [count = self.catchers.len()] => self.catchers().trace_all_info());
|
||||
info_span!("fairings" [count = fairings.len()] => fairings.trace_all_info());
|
||||
span_info!("routes", count = self.routes.len() => self.routes().trace_all_info());
|
||||
span_info!("catchers", count = self.catchers.len() => self.catchers().trace_all_info());
|
||||
span_info!("fairings", count = fairings.len() => fairings.trace_all_info());
|
||||
|
||||
// Ignite the rocket.
|
||||
let rocket: Rocket<Ignite> = Rocket(Igniting {
|
||||
|
@ -592,19 +590,6 @@ impl Rocket<Build> {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "items", skip_all, fields(kind = kind))]
|
||||
fn log_items<T, I, B, O>(kind: &str, items: I, base: B, origin: O)
|
||||
where T: fmt::Display + Copy, I: Iterator<Item = T>,
|
||||
B: Fn(&T) -> &Origin<'_>, O: Fn(&T) -> &Origin<'_>
|
||||
{
|
||||
let mut items: Vec<_> = items.collect();
|
||||
items.sort_by_key(|i| origin(i).path().as_str().chars().count());
|
||||
items.sort_by_key(|i| origin(i).path().segments().count());
|
||||
items.sort_by_key(|i| base(i).path().as_str().chars().count());
|
||||
items.sort_by_key(|i| base(i).path().segments().count());
|
||||
items.iter().for_each(|item| info!(name: "item", %item));
|
||||
}
|
||||
|
||||
impl Rocket<Ignite> {
|
||||
/// Returns the finalized, active configuration. This is guaranteed to
|
||||
/// remain stable through ignition and into orbit.
|
||||
|
|
|
@ -176,6 +176,8 @@ pub struct Route {
|
|||
pub format: Option<MediaType>,
|
||||
/// The discovered sentinels.
|
||||
pub(crate) sentinels: Vec<Sentry>,
|
||||
/// The file, line, and column where the route was defined, if known.
|
||||
pub(crate) location: Option<(&'static str, u32, u32)>,
|
||||
}
|
||||
|
||||
impl Route {
|
||||
|
@ -250,6 +252,7 @@ impl Route {
|
|||
format: None,
|
||||
sentinels: Vec::new(),
|
||||
handler: Box::new(handler),
|
||||
location: None,
|
||||
rank, uri, method,
|
||||
}
|
||||
}
|
||||
|
@ -371,6 +374,8 @@ pub struct StaticInfo {
|
|||
/// Route-derived sentinels, if any.
|
||||
/// This isn't `&'static [SentryInfo]` because `type_name()` isn't `const`.
|
||||
pub sentinels: Vec<Sentry>,
|
||||
/// The file, line, and column where the route was defined.
|
||||
pub location: (&'static str, u32, u32),
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
|
@ -386,6 +391,7 @@ impl From<StaticInfo> for Route {
|
|||
rank: info.rank.unwrap_or_else(|| uri.default_rank()),
|
||||
format: info.format,
|
||||
sentinels: info.sentinels.into_iter().collect(),
|
||||
location: Some(info.location),
|
||||
uri,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ impl Rocket<Orbit> {
|
|||
Request::from_hyp(rocket, parts, connection).unwrap_or_else(|e| e)
|
||||
});
|
||||
|
||||
debug_span!("request headers" => request.inner().headers().iter().trace_all_debug());
|
||||
span_debug!("request headers" => request.inner().headers().iter().trace_all_debug());
|
||||
let mut response = request.into_response(
|
||||
stream,
|
||||
|rocket, request, data| Box::pin(rocket.preprocess(request, data)),
|
||||
|
@ -54,7 +54,7 @@ impl Rocket<Orbit> {
|
|||
|
||||
// TODO: Should upgrades be handled in dispatch?
|
||||
response.inner().trace_info();
|
||||
debug_span!("response headers" => response.inner().headers().iter().trace_all_debug());
|
||||
span_debug!("response headers" => response.inner().headers().iter().trace_all_debug());
|
||||
let io_handler = response.make_io_handler(Rocket::extract_io_handler);
|
||||
if let (Some((proto, handler)), Some(upgrade)) = (io_handler, upgrade) {
|
||||
let upgrade = upgrade.map_ok(IoStream::from).map_err(io::Error::other);
|
||||
|
@ -92,7 +92,7 @@ async fn io_handler_task<S>(proto: String, stream: S, mut handler: ErasedIoHandl
|
|||
Err(e) => return warn!(error = %e, "i/o upgrade failed"),
|
||||
};
|
||||
|
||||
info!("i/o upgrade succeeded");
|
||||
debug!("i/o upgrade succeeded");
|
||||
if let Err(e) = handler.take().io(stream).await {
|
||||
match e.kind() {
|
||||
io::ErrorKind::BrokenPipe => warn!("i/o handler closed"),
|
||||
|
|
|
@ -4,8 +4,8 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
|||
use crate::{Rocket, Request, Response, Orbit, Config};
|
||||
use crate::fairing::{Fairing, Info, Kind};
|
||||
use crate::http::{Header, uncased::UncasedStr};
|
||||
use crate::shield::*;
|
||||
use crate::trace::*;
|
||||
use crate::shield::{Frame, Hsts, NoSniff, Permission, Policy};
|
||||
use crate::trace::{Trace, TraceAll};
|
||||
|
||||
/// A [`Fairing`] that injects browser security and privacy headers into all
|
||||
/// outgoing responses.
|
||||
|
@ -195,7 +195,7 @@ impl Fairing for Shield {
|
|||
self.force_hsts.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
info_span!("shield" [policies = self.policies.len()] => {
|
||||
span_info!("shield", policies = self.policies.len() => {
|
||||
self.policies.values().trace_all_info();
|
||||
|
||||
if force_hsts {
|
||||
|
@ -211,7 +211,7 @@ impl Fairing for Shield {
|
|||
// the header is not already in the response.
|
||||
for header in self.policies.values() {
|
||||
if response.headers().contains(header.name()) {
|
||||
warn_span!("shield refusing to overwrite existing response header" => {
|
||||
span_warn!("shield", "shield refusing to overwrite existing response header" => {
|
||||
header.trace_warn();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,56 +1,32 @@
|
|||
macro_rules! declare_macro {
|
||||
($($name:ident $level:ident),* $(,)?) => (
|
||||
$(declare_macro!([$] $name $level);)*
|
||||
);
|
||||
|
||||
([$d:tt] $name:ident $level:ident) => (
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! $name {
|
||||
($d ($t:tt)*) => ($crate::tracing::$level!($d ($t)*));
|
||||
}
|
||||
|
||||
// pub use $name as $name;
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! declare_span_macro {
|
||||
($($name:ident $level:ident),* $(,)?) => (
|
||||
$(declare_span_macro!([$] $name $level);)*
|
||||
($name:ident $level:ident) => (
|
||||
declare_span_macro!([$] $name $level);
|
||||
);
|
||||
|
||||
([$d:tt] $name:ident $level:ident) => (
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! $name {
|
||||
($n:literal $d ([ $d ($f:tt)* ])? => $in_scope:expr) => ({
|
||||
$crate::tracing::span!($crate::tracing::Level::$level, $n $d (, $d ($f)* )?)
|
||||
(@[$d ($t:tt)+] => $in_scope:expr) => ({
|
||||
$crate::tracing::span!($crate::tracing::Level::$level, $d ($t)+)
|
||||
.in_scope(|| $in_scope);
|
||||
})
|
||||
});
|
||||
|
||||
(@[$d ($t:tt)+] $token:tt $d ($rest:tt)*) => ({
|
||||
$crate::trace::$name!(@[$d ($t)+ $token] $d ($rest)*);
|
||||
});
|
||||
|
||||
// base case
|
||||
($t:tt $d ($rest:tt)*) => ({
|
||||
$crate::trace::$name!(@[$t] $d ($rest)*);
|
||||
});
|
||||
}
|
||||
|
||||
#[doc(inline)]
|
||||
#[doc(hidden)]
|
||||
pub use $name as $name;
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! span {
|
||||
($level:expr, $($args:tt)*) => {{
|
||||
match $level {
|
||||
$crate::tracing::Level::ERROR =>
|
||||
$crate::tracing::span!($crate::tracing::Level::ERROR, $($args)*),
|
||||
$crate::tracing::Level::WARN =>
|
||||
$crate::tracing::span!($crate::tracing::Level::WARN, $($args)*),
|
||||
$crate::tracing::Level::INFO =>
|
||||
$crate::tracing::span!($crate::tracing::Level::INFO, $($args)*),
|
||||
$crate::tracing::Level::DEBUG =>
|
||||
$crate::tracing::span!($crate::tracing::Level::DEBUG, $($args)*),
|
||||
$crate::tracing::Level::TRACE =>
|
||||
$crate::tracing::span!($crate::tracing::Level::TRACE, $($args)*),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! event {
|
||||
|
@ -69,21 +45,64 @@ macro_rules! event {
|
|||
}};
|
||||
}
|
||||
|
||||
// Re-exports the macro at $path with the name $name. The point is to allow
|
||||
// a `#[macro_use] extern crate rocket` to also automatically import the
|
||||
// relevant tracing macros.
|
||||
macro_rules! reexport {
|
||||
($path:ident::$name:ident) => (
|
||||
reexport!([$] $path::$name);
|
||||
);
|
||||
|
||||
([ $d:tt ] $path:ident::$name:ident) => {
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! $name {
|
||||
($d ($f:tt)*) => {
|
||||
$crate::$path::$name!($d ($f)*)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! span {
|
||||
($level:expr, $($args:tt)*) => {{
|
||||
match $level {
|
||||
$crate::tracing::Level::ERROR =>
|
||||
$crate::tracing::span!($crate::tracing::Level::ERROR, $($args)*),
|
||||
$crate::tracing::Level::WARN =>
|
||||
$crate::tracing::span!($crate::tracing::Level::WARN, $($args)*),
|
||||
$crate::tracing::Level::INFO =>
|
||||
$crate::tracing::span!($crate::tracing::Level::INFO, $($args)*),
|
||||
$crate::tracing::Level::DEBUG =>
|
||||
$crate::tracing::span!($crate::tracing::Level::DEBUG, $($args)*),
|
||||
$crate::tracing::Level::TRACE =>
|
||||
$crate::tracing::span!($crate::tracing::Level::TRACE, $($args)*),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[doc(inline)]
|
||||
pub use span as span;
|
||||
|
||||
declare_span_macro!(span_error ERROR);
|
||||
declare_span_macro!(span_warn WARN);
|
||||
declare_span_macro!(span_info INFO);
|
||||
declare_span_macro!(span_debug DEBUG);
|
||||
declare_span_macro!(span_trace TRACE);
|
||||
|
||||
#[doc(inline)]
|
||||
pub use event as event;
|
||||
|
||||
declare_macro!(
|
||||
error error,
|
||||
info info,
|
||||
trace trace,
|
||||
debug debug,
|
||||
warn warn
|
||||
);
|
||||
reexport!(tracing::error);
|
||||
reexport!(tracing::warn);
|
||||
reexport!(tracing::info);
|
||||
reexport!(tracing::debug);
|
||||
reexport!(tracing::trace);
|
||||
|
||||
declare_span_macro!(
|
||||
error_span ERROR,
|
||||
warn_span WARN,
|
||||
info_span INFO,
|
||||
trace_span TRACE,
|
||||
debug_span DEBUG,
|
||||
);
|
||||
#[doc(hidden)] pub use tracing::error;
|
||||
#[doc(hidden)] pub use tracing::warn;
|
||||
#[doc(hidden)] pub use tracing::info;
|
||||
#[doc(hidden)] pub use tracing::debug;
|
||||
#[doc(hidden)] pub use tracing::trace;
|
||||
|
|
|
@ -8,14 +8,18 @@ pub mod subscriber;
|
|||
|
||||
pub(crate) mod level;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use macros::*;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use traceable::{Trace, TraceAll};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use macros::*;
|
||||
pub use tracing::{Level, level_filters::LevelFilter};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
#[non_exhaustive]
|
||||
pub enum TraceFormat {
|
||||
#[serde(rename = "pretty")]
|
||||
#[serde(alias = "PRETTY")]
|
||||
|
@ -27,6 +31,9 @@ pub enum TraceFormat {
|
|||
|
||||
#[cfg_attr(nightly, doc(cfg(feature = "trace")))]
|
||||
pub fn init<'a, T: Into<Option<&'a crate::Config>>>(config: T) {
|
||||
#[cfg(not(feature = "trace"))]
|
||||
let _ = config;
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
crate::trace::subscriber::RocketDynFmt::init(config.into())
|
||||
}
|
||||
|
|
|
@ -28,9 +28,9 @@ impl RocketFmt<Pretty> {
|
|||
MARKER.get(self.state().depth as usize).copied().unwrap_or("-- ")
|
||||
}
|
||||
|
||||
fn emoji(&self, emoji: &'static str) -> Painted<&'static str> {
|
||||
fn emoji(&self, _emoji: &'static str) -> Painted<&'static str> {
|
||||
#[cfg(windows)] { "".paint(self.style).mask() }
|
||||
#[cfg(not(windows))] { emoji.paint(self.style).mask() }
|
||||
#[cfg(not(windows))] { _emoji.paint(self.style).mask() }
|
||||
}
|
||||
|
||||
fn prefix<'a>(&self, meta: &'a Metadata<'_>) -> impl fmt::Display + 'a {
|
||||
|
@ -79,7 +79,7 @@ impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
|
|||
"liftoff" => {
|
||||
let prefix = self.prefix(meta);
|
||||
println!("{prefix}{}{} {}", self.emoji("🚀 "),
|
||||
"Rocket has launched from".paint(style).primary().bold(),
|
||||
"Rocket has launched on".paint(style).primary().bold(),
|
||||
&data["endpoint"].paint(style).primary().bold().underline());
|
||||
},
|
||||
"route" => println!("{}", Formatter(|f| {
|
||||
|
@ -98,7 +98,13 @@ impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
|
|||
)?;
|
||||
|
||||
if let Some(name) = data.get("name") {
|
||||
write!(f, " ({})", name.paint(style.bold().bright()))?;
|
||||
write!(f, " ({}", name.paint(style.bold().bright()))?;
|
||||
|
||||
if let Some(location) = data.get("location") {
|
||||
write!(f, " {}", location.paint(style.dim()))?;
|
||||
}
|
||||
|
||||
write!(f, ")")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -113,7 +119,13 @@ impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for RocketFmt<Pretty> {
|
|||
|
||||
write!(f, "{}", &data["uri.base"].paint(style.primary()))?;
|
||||
if let Some(name) = data.get("name") {
|
||||
write!(f, " ({})", name.paint(style.bold().bright()))?;
|
||||
write!(f, " ({}", name.paint(style.bold().bright()))?;
|
||||
|
||||
if let Some(location) = data.get("location") {
|
||||
write!(f, " {}", location.paint(style.dim()))?;
|
||||
}
|
||||
|
||||
write!(f, ")")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -47,10 +47,15 @@ impl Trace for Figment {
|
|||
fn trace(&self, level: Level) {
|
||||
for param in Config::PARAMETERS {
|
||||
if let Some(source) = self.find_metadata(param) {
|
||||
if param.contains("secret") {
|
||||
continue;
|
||||
}
|
||||
|
||||
event! { level, "figment",
|
||||
param,
|
||||
%source.name,
|
||||
source.source = source.source.as_ref().map(display),
|
||||
value = self.find_value(param).ok().map(debug),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -142,6 +147,9 @@ impl Trace for Route {
|
|||
uri.base = %self.uri.base(),
|
||||
uri.unmounted = %self.uri.unmounted(),
|
||||
format = self.format.as_ref().map(display),
|
||||
location = self.location.as_ref()
|
||||
.map(|(file, line, _)| Formatter(move |f| write!(f, "{file}:{line}")))
|
||||
.map(display),
|
||||
}
|
||||
|
||||
event! { Level::DEBUG, "sentinels",
|
||||
|
@ -165,6 +173,9 @@ impl Trace for Catcher {
|
|||
}),
|
||||
rank = self.rank,
|
||||
uri.base = %self.base(),
|
||||
location = self.location.as_ref()
|
||||
.map(|(file, line, _)| Formatter(move |f| write!(f, "{file}:{line}")))
|
||||
.map(display),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,17 +216,14 @@ impl Trace for figment::error::Kind {
|
|||
impl Trace for figment::Error {
|
||||
fn trace(&self, _: Level) {
|
||||
for e in self.clone() {
|
||||
let span = tracing::error_span! {
|
||||
"config",
|
||||
span_error!("config",
|
||||
key = (!e.path.is_empty()).then_some(&e.path).and_then(|path| {
|
||||
let (profile, metadata) = (e.profile.as_ref()?, e.metadata.as_ref()?);
|
||||
Some(metadata.interpolate(profile, path))
|
||||
}),
|
||||
source.name = e.metadata.as_ref().map(|m| &*m.name),
|
||||
source.source = e.metadata.as_ref().and_then(|m| m.source.as_ref()).map(display),
|
||||
};
|
||||
|
||||
span.in_scope(|| e.kind.trace_error());
|
||||
source.source = e.metadata.as_ref().and_then(|m| m.source.as_ref()).map(display)
|
||||
=> e.kind.trace_error());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -298,7 +306,7 @@ impl Trace for ErrorKind {
|
|||
e.trace(level);
|
||||
} else {
|
||||
event!(level, "error::bind",
|
||||
?error,
|
||||
reason = %error,
|
||||
endpoint = endpoint.as_ref().map(display),
|
||||
"binding to network interface failed"
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use rocket::{*, error::ErrorKind::SentinelAborts};
|
||||
use rocket::{*, either::Either, error::ErrorKind::SentinelAborts};
|
||||
|
||||
#[get("/two")]
|
||||
fn two_states(_one: &State<u32>, _two: &State<String>) {}
|
||||
|
|
|
@ -19,6 +19,7 @@ port = 8000
|
|||
workers = 1
|
||||
keep_alive = 0
|
||||
log_level = "info"
|
||||
log_format = "pretty"
|
||||
|
||||
[release]
|
||||
address = "127.0.0.1"
|
||||
|
@ -26,6 +27,7 @@ port = 8000
|
|||
workers = 12
|
||||
keep_alive = 5
|
||||
log_level = "error"
|
||||
log_format = "compact"
|
||||
# NOTE: Don't (!) use this key! Generate your own and keep it private!
|
||||
# e.g. via `head -c64 /dev/urandom | base64`
|
||||
secret_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use rocket::config::{Config, /* LogLevel */};
|
||||
use rocket::config::Config;
|
||||
use rocket::trace::{Level, TraceFormat};
|
||||
|
||||
async fn test_config(profile: &str) {
|
||||
let provider = Config::figment().select(profile);
|
||||
|
@ -8,12 +9,14 @@ async fn test_config(profile: &str) {
|
|||
"debug" => {
|
||||
assert_eq!(config.workers, 1);
|
||||
assert_eq!(config.keep_alive, 0);
|
||||
// assert_eq!(config.log_level, LogLevel::Normal);
|
||||
assert_eq!(config.log_level, Some(Level::INFO));
|
||||
assert_eq!(config.log_format, TraceFormat::Pretty);
|
||||
}
|
||||
"release" => {
|
||||
assert_eq!(config.workers, 12);
|
||||
assert_eq!(config.keep_alive, 5);
|
||||
// assert_eq!(config.log_level, LogLevel::Critical);
|
||||
assert_eq!(config.log_level, Some(Level::ERROR));
|
||||
assert_eq!(config.log_format, TraceFormat::Compact);
|
||||
assert!(!config.secret_key.is_zero());
|
||||
}
|
||||
_ => {
|
||||
|
|
|
@ -76,7 +76,7 @@ fn rocket() -> _ {
|
|||
.attach(AdHoc::on_request("PUT Rewriter", |req, _| {
|
||||
Box::pin(async move {
|
||||
if req.uri().path() == "/" {
|
||||
info_span!("PUT rewriter" => {
|
||||
span_info!("PUT rewriter" => {
|
||||
req.trace_info();
|
||||
info!("changing method to `PUT`");
|
||||
req.set_method(Method::Put);
|
||||
|
|
|
@ -6,4 +6,4 @@ edition = "2021"
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
rocket = { path = "../../core/lib", features = ["secrets"] }
|
||||
rocket = { path = "../../core/lib" }
|
||||
|
|
|
@ -38,9 +38,6 @@ fn wave(name: &str, age: u8) -> String {
|
|||
format!("👋 Hello, {} year old named {}!", age, name)
|
||||
}
|
||||
|
||||
#[get("/<a>/<b>")]
|
||||
fn f(a: usize, b: usize) { }
|
||||
|
||||
// Note: without the `..` in `opt..`, we'd need to pass `opt.emoji`, `opt.name`.
|
||||
//
|
||||
// Try visiting:
|
||||
|
@ -54,7 +51,7 @@ fn f(a: usize, b: usize) { }
|
|||
// http://127.0.0.1:8000/?name=Rocketeer&lang=en&emoji
|
||||
// http://127.0.0.1:8000/?lang=ru&emoji&name=Rocketeer
|
||||
#[get("/?<lang>&<opt..>")]
|
||||
async fn hello(lang: Option<Lang>, opt: Options<'_>) -> String {
|
||||
fn hello(lang: Option<Lang>, opt: Options<'_>) -> String {
|
||||
let mut greeting = String::new();
|
||||
if opt.emoji {
|
||||
greeting.push_str("👋 ");
|
||||
|
|
|
@ -159,7 +159,7 @@ fn not_found(request: &Request<'_>) -> content::RawHtml<String> {
|
|||
|
||||
/******************************* `Either` Responder ***************************/
|
||||
|
||||
use rocket::Either;
|
||||
use rocket::either::Either;
|
||||
use rocket::response::content::{RawJson, RawMsgPack};
|
||||
use rocket::http::uncased::AsUncased;
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ impl Redirector {
|
|||
pub async fn try_launch(self, config: Config) -> Result<Rocket<Ignite>, Error> {
|
||||
use rocket::http::Method::*;
|
||||
|
||||
rocket::info_span!("HTTP -> HTTPS Redirector" => {
|
||||
rocket::span_info!("HTTP -> HTTPS Redirector" => {
|
||||
info!(from = self.0, to = config.tls_addr.port(), "redirecting");
|
||||
});
|
||||
|
||||
|
@ -75,7 +75,7 @@ impl Fairing for Redirector {
|
|||
|
||||
async fn on_liftoff(&self, rocket: &Rocket<Orbit>) {
|
||||
let Some(tls_addr) = rocket.endpoints().find_map(|e| e.tls()?.tcp()) else {
|
||||
rocket::warn_span!("HTTP -> HTTPS Redirector" => {
|
||||
rocket::span_warn!("HTTP -> HTTPS Redirector" => {
|
||||
warn!("Main instance is not being served over TLS/TCP.\n\
|
||||
Redirector refusing to start.");
|
||||
});
|
||||
|
@ -95,7 +95,7 @@ impl Fairing for Redirector {
|
|||
let shutdown = rocket.shutdown();
|
||||
rocket::tokio::spawn(async move {
|
||||
if let Err(e) = this.try_launch(config).await {
|
||||
error_span!("failed to start HTTP -> HTTPS redirector" => {
|
||||
span_error!("HTTP -> HTTPS Redirector", "failed to start" => {
|
||||
e.trace_error();
|
||||
info!("shutting down main instance");
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ procspawn = "1"
|
|||
pretty_assertions = "1.4.0"
|
||||
ipc-channel = "0.18"
|
||||
rustls-pemfile = "2.1"
|
||||
inventory = "0.3.15"
|
||||
|
||||
[dependencies.nix]
|
||||
version = "0.28"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::time::Duration;
|
||||
use std::{str::FromStr, time::Duration};
|
||||
|
||||
use reqwest::blocking::{ClientBuilder, RequestBuilder};
|
||||
use rocket::http::{ext::IntoOwned, uri::{Absolute, Uri}};
|
||||
use rocket::http::{ext::IntoOwned, uri::{Absolute, Uri}, Method};
|
||||
|
||||
use crate::{Result, Error, Server};
|
||||
|
||||
|
@ -26,7 +26,7 @@ impl Client {
|
|||
.connect_timeout(Duration::from_secs(5))
|
||||
}
|
||||
|
||||
pub fn get(&self, server: &Server, url: &str) -> Result<RequestBuilder> {
|
||||
pub fn request(&self, server: &Server, method: Method, url: &str) -> Result<RequestBuilder> {
|
||||
let uri = match Uri::parse_any(url).map_err(|e| e.into_owned())? {
|
||||
Uri::Origin(uri) => {
|
||||
let proto = if server.tls { "https" } else { "http" };
|
||||
|
@ -45,7 +45,16 @@ impl Client {
|
|||
uri => return Err(Error::InvalidUri(uri.into_owned())),
|
||||
};
|
||||
|
||||
Ok(self.client.get(uri.to_string()))
|
||||
let method = reqwest::Method::from_str(method.as_str()).unwrap();
|
||||
Ok(self.client.request(method, uri.to_string()))
|
||||
}
|
||||
|
||||
pub fn get(&self, server: &Server, url: &str) -> Result<RequestBuilder> {
|
||||
self.request(server, Method::Get, url)
|
||||
}
|
||||
|
||||
pub fn post(&self, server: &Server, url: &str) -> Result<RequestBuilder> {
|
||||
self.request(server, Method::Post, url)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
|
@ -1,480 +1,19 @@
|
|||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::process::ExitCode;
|
||||
use std::time::Duration;
|
||||
mod runner;
|
||||
mod servers;
|
||||
mod config;
|
||||
|
||||
use rocket::tokio::net::TcpListener;
|
||||
use rocket::yansi::Paint;
|
||||
use rocket::{get, routes, Build, Rocket, State};
|
||||
use rocket::listener::{unix::UnixListener, Endpoint};
|
||||
use rocket::tls::TlsListener;
|
||||
pub mod prelude {
|
||||
pub use rocket::*;
|
||||
pub use rocket::fairing::*;
|
||||
pub use rocket::response::stream::*;
|
||||
|
||||
use reqwest::{tls::TlsInfo, Identity};
|
||||
|
||||
use testbench::*;
|
||||
|
||||
static DEFAULT_CONFIG: &str = r#"
|
||||
[default]
|
||||
address = "tcp:127.0.0.1"
|
||||
workers = 2
|
||||
port = 0
|
||||
cli_colors = false
|
||||
secret_key = "itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg="
|
||||
|
||||
[default.shutdown]
|
||||
grace = 1
|
||||
mercy = 1
|
||||
"#;
|
||||
|
||||
static TLS_CONFIG: &str = r#"
|
||||
[default.tls]
|
||||
certs = "{ROCKET}/examples/tls/private/rsa_sha256_cert.pem"
|
||||
key = "{ROCKET}/examples/tls/private/rsa_sha256_key.pem"
|
||||
"#;
|
||||
|
||||
trait RocketExt {
|
||||
fn default() -> Self;
|
||||
fn tls_default() -> Self;
|
||||
fn reconfigure_with_toml(self, toml: &str) -> Self;
|
||||
pub use testbench::{Error, Result, *};
|
||||
pub use crate::register;
|
||||
pub use crate::config::*;
|
||||
}
|
||||
|
||||
impl RocketExt for Rocket<Build> {
|
||||
fn default() -> Self {
|
||||
rocket::build().reconfigure_with_toml(DEFAULT_CONFIG)
|
||||
}
|
||||
pub use runner::Test;
|
||||
|
||||
fn tls_default() -> Self {
|
||||
rocket::build()
|
||||
.reconfigure_with_toml(DEFAULT_CONFIG)
|
||||
.reconfigure_with_toml(TLS_CONFIG)
|
||||
}
|
||||
|
||||
fn reconfigure_with_toml(self, toml: &str) -> Self {
|
||||
use rocket::figment::{Figment, providers::{Format, Toml}};
|
||||
|
||||
let toml = toml.replace("{ROCKET}", rocket::fs::relative!("../"));
|
||||
let config = Figment::from(self.figment())
|
||||
.merge(Toml::string(&toml).nested());
|
||||
|
||||
self.reconfigure(config)
|
||||
}
|
||||
fn main() -> std::process::ExitCode {
|
||||
runner::run()
|
||||
}
|
||||
|
||||
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())
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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;
|
|
@ -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));
|
|
@ -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);
|
|
@ -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);
|
|
@ -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())
|
||||
// }
|
||||
// }
|
|
@ -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);
|
Loading…
Reference in New Issue