use rocket::{*, either::Either, error::ErrorKind::SentinelAborts}; #[get("/two")] fn two_states(_one: &State, _two: &State) {} #[post("/one", data = "")] fn one_state<'r>(_three: &'r State, 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) -> bool { rocket.state::().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); 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) -> bool { true } } #[get("/")] fn route() -> MyThing { 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); #[get("/")] fn inner() -> MyThing> { 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, 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); impl Sentinel for Block { fn abort(_: &Rocket) -> bool { false } } #[get("/")] fn blocked() -> Block { todo!() } Client::debug_with(routes![blocked]).expect("no sentinel errors"); #[get("/a")] fn inner_b() -> Either>, Block> { todo!() } #[get("/b")] fn inner_b2() -> Either>, Block> { todo!() } Client::debug_with(routes![inner_b, inner_b2]).expect("no sentinel errors"); #[get("/")] fn half_b() -> Either, Block> { 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>, Result> > { 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>, Result, Inner> > { 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; #[get("/")] fn half_e<'r>() -> Either>, 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 { 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 { 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) -> bool { true } } impl AsRef 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> { futures::task::Poll::Ready(Ok(())) } } #[get("/text")] fn text<'r>() -> TextStream![TextSentinel<'r>] { TextStream!(yield TextSentinel("hi");) } #[get("/")] fn byte(a: &str) -> ByteStream![TextSentinel<'_>] { ByteStream!(yield TextSentinel(a);) } #[get("/<_a>/")] 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)); }