Rocket/core/lib/tests/sentinel.rs
Sergio Benitez 926e06ef3c 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.
2024-06-03 15:02:44 -07:00

340 lines
9.6 KiB
Rust

use rocket::{*, either::Either, error::ErrorKind::SentinelAborts};
#[get("/two")]
fn two_states(_one: &State<u32>, _two: &State<String>) {}
#[post("/one", data = "<s>")]
fn one_state<'r>(_three: &'r State<u8>, s: &'r str) -> &'r str { s }
#[async_test]
async fn state_sentinel_works() {
let err = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![two_states])
.ignite().await
.unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 2));
let err = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![two_states])
.manage(String::new())
.ignite().await
.unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
let err = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![two_states])
.manage(1 as u32)
.ignite().await
.unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
let result = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![two_states])
.manage(String::new())
.manage(1 as u32)
.ignite().await;
assert!(result.is_ok());
let err = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![one_state])
.ignite().await
.unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
let result = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![one_state])
.manage(1 as u8)
.ignite().await;
assert!(result.is_ok());
let err = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![one_state, two_states])
.ignite().await
.unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 3));
let err = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![one_state, two_states])
.manage(1 as u32)
.ignite().await
.unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 2));
let err = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![one_state, two_states])
.manage(1 as u8)
.ignite().await
.unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 2));
let err = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![one_state, two_states])
.manage(1 as u32)
.manage(1 as u8)
.ignite().await
.unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
let result = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![one_state, two_states])
.manage(1 as u32)
.manage(1 as u8)
.manage(String::new())
.ignite().await;
assert!(result.is_ok());
}
struct Data;
#[crate::async_trait]
impl<'r> data::FromData<'r> for Data {
type Error = Error;
async fn from_data(_: &'r Request<'_>, _: data::Data<'r>) -> data::Outcome<'r, Self> {
unimplemented!()
}
}
impl Sentinel for Data {
fn abort(rocket: &Rocket<Ignite>) -> bool {
rocket.state::<Data>().is_none()
}
}
#[post("/data", data = "<_data>")]
fn with_data(_data: Data) {}
#[async_test]
async fn data_sentinel_works() {
let err = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![with_data])
.ignite().await
.unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
let result = rocket::build()
.reconfigure(Config::debug_default())
.mount("/", routes![with_data])
.manage(Data)
.ignite().await;
assert!(result.is_ok());
}
#[test]
fn inner_sentinels_detected() {
use rocket::local::blocking::Client;
#[derive(Responder)]
struct MyThing<T>(T);
struct ResponderSentinel;
impl<'r, 'o: 'r> response::Responder<'r, 'o> for ResponderSentinel {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> {
unimplemented!()
}
}
impl Sentinel for ResponderSentinel {
fn abort(_: &Rocket<Ignite>) -> bool {
true
}
}
#[get("/")]
fn route() -> MyThing<ResponderSentinel> { todo!() }
let err = Client::debug_with(routes![route]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
#[derive(Responder)]
struct Inner<T>(T);
#[get("/")]
fn inner() -> MyThing<Inner<ResponderSentinel>> { todo!() }
let err = Client::debug_with(routes![inner]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
#[get("/")]
fn inner_either() -> Either<Inner<ResponderSentinel>, ResponderSentinel> { todo!() }
let err = Client::debug_with(routes![inner_either]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 2));
#[derive(Responder)]
struct Block<T>(T);
impl<T> Sentinel for Block<T> {
fn abort(_: &Rocket<Ignite>) -> bool {
false
}
}
#[get("/")]
fn blocked() -> Block<ResponderSentinel> { todo!() }
Client::debug_with(routes![blocked]).expect("no sentinel errors");
#[get("/a")]
fn inner_b() -> Either<Inner<Block<ResponderSentinel>>, Block<ResponderSentinel>> {
todo!()
}
#[get("/b")]
fn inner_b2() -> Either<Block<Inner<ResponderSentinel>>, Block<ResponderSentinel>> {
todo!()
}
Client::debug_with(routes![inner_b, inner_b2]).expect("no sentinel errors");
#[get("/")]
fn half_b() -> Either<Inner<ResponderSentinel>, Block<ResponderSentinel>> {
todo!()
}
let err = Client::debug_with(routes![half_b]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
use rocket::response::Responder;
#[get("/")]
fn half_c<'r>() -> Either<
Inner<impl Responder<'r, 'static>>,
Result<ResponderSentinel, Inner<ResponderSentinel>>
> {
Either::Left(Inner(()))
}
let err = Client::debug_with(routes![half_c]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 2));
#[get("/")]
fn half_d<'r>() -> Either<
Inner<impl Responder<'r, 'static>>,
Result<Block<ResponderSentinel>, Inner<ResponderSentinel>>
> {
Either::Left(Inner(()))
}
let err = Client::debug_with(routes![half_d]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
// The special `Result` implementation.
type MyResult = Result<ResponderSentinel, ResponderSentinel>;
#[get("/")]
fn half_e<'r>() -> Either<Inner<impl Responder<'r, 'static>>, MyResult> {
Either::Left(Inner(()))
}
let err = Client::debug_with(routes![half_e]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
// Another specialized sentinel.
#[get("/")] fn either_route() -> Either<ResponderSentinel, ResponderSentinel> { todo!() }
let err = Client::debug_with(routes![either_route]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
#[get("/")] fn either_route2() -> Either<ResponderSentinel, ()> { todo!() }
let err = Client::debug_with(routes![either_route2]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
#[get("/")] fn either_route3() -> Either<(), ResponderSentinel> { todo!() }
let err = Client::debug_with(routes![either_route3]).unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 1));
#[get("/")] fn either_route4() -> Either<(), ()> { todo!() }
Client::debug_with(routes![either_route4]).expect("no sentinel error");
}
#[async_test]
async fn known_macro_sentinel_works() {
use rocket::response::stream::{TextStream, ByteStream, ReaderStream};
use rocket::local::asynchronous::Client;
use rocket::tokio::io::AsyncRead;
#[derive(Responder)]
struct TextSentinel<'r>(&'r str);
impl Sentinel for TextSentinel<'_> {
fn abort(_: &Rocket<Ignite>) -> bool {
true
}
}
impl AsRef<str> for TextSentinel<'_> {
fn as_ref(&self) -> &str {
self.0
}
}
impl AsRef<[u8]> for TextSentinel<'_> {
fn as_ref(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl AsyncRead for TextSentinel<'_> {
fn poll_read(
self: std::pin::Pin<&mut Self>,
_: &mut futures::task::Context<'_>,
_: &mut tokio::io::ReadBuf<'_>,
) -> futures::task::Poll<std::io::Result<()>> {
futures::task::Poll::Ready(Ok(()))
}
}
#[get("/text")]
fn text<'r>() -> TextStream![TextSentinel<'r>] {
TextStream!(yield TextSentinel("hi");)
}
#[get("/<a>")]
fn byte(a: &str) -> ByteStream![TextSentinel<'_>] {
ByteStream!(yield TextSentinel(a);)
}
#[get("/<_a>/<b>")]
fn reader<'b>(_a: &str, b: &'b str) -> ReaderStream![TextSentinel<'b>] {
ReaderStream!(yield TextSentinel(b);)
}
macro_rules! UnknownStream {
($t:ty) => (ReaderStream![$t])
}
#[get("/ignore")]
fn ignore() -> UnknownStream![TextSentinel<'static>] {
ReaderStream!(yield TextSentinel("hi");)
}
let err = Client::debug_with(routes![text, byte, reader, ignore]).await.unwrap_err();
assert!(matches!(err.kind(), SentinelAborts(vec) if vec.len() == 3));
}