use rocket::{*, error::ErrorKind::SentinelAborts}; #[get("/two")] fn two_states(_one: &State, _two: &State) {} #[get("/one")] fn one_state(_three: &State) {} #[async_test] async fn state_sentinel_works() { let err = rocket::build() .configure(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() .configure(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() .configure(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() .configure(Config::debug_default()) .mount("/", routes![two_states]) .manage(String::new()) .manage(1 as u32) .ignite().await; assert!(result.is_ok()); let err = rocket::build() .configure(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() .configure(Config::debug_default()) .mount("/", routes![one_state]) .manage(1 as u8) .ignite().await; assert!(result.is_ok()); let err = rocket::build() .configure(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() .configure(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() .configure(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() .configure(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() .configure(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()); } #[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> { todo!() } } 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(&'static 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() -> TextStream![TextSentinel] { TextStream!(yield TextSentinel("hi");) } #[get("/bytes")] fn byte() -> ByteStream![TextSentinel] { ByteStream!(yield TextSentinel("hi");) } #[get("/reader")] fn reader() -> ReaderStream![TextSentinel] { ReaderStream!(yield TextSentinel("hi");) } macro_rules! UnknownStream { ($t:ty) => (ReaderStream![$t]) } #[get("/ignore")] fn ignore() -> UnknownStream![TextSentinel] { 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)); }