rocket_community/sentinel.rs
1use std::any::TypeId;
2use std::fmt;
3
4use crate::{Ignite, Rocket};
5
6/// An automatic last line of defense against launching an invalid [`Rocket`].
7///
8/// A sentinel, automatically run on [`ignition`](Rocket::ignite()), can trigger
9/// a launch abort should an instance fail to meet arbitrary conditions. Every
10/// type that appears in a **mounted** route's type signature is eligible to be
11/// a sentinel. Of these, those that implement `Sentinel` have their
12/// [`abort()`](Sentinel::abort()) method invoked automatically, immediately
13/// after ignition, once for each unique type. Sentinels inspect the finalized
14/// instance of `Rocket` and can trigger a launch abort by returning `true`.
15///
16/// # Built-In Sentinels
17///
18/// The [`State<T>`] type is a sentinel that triggers an abort if the finalized
19/// `Rocket` instance is not managing state for type `T`. Doing so prevents
20/// run-time failures of the `State` request guard.
21///
22/// [`State<T>`]: crate::State
23/// [`State`]: crate::State
24///
25/// ## Example
26///
27/// As an example, consider the following simple application:
28///
29/// ```rust
30/// extern crate rocket_community as rocket;
31///
32/// # use rocket::*;
33/// # type Response = ();
34/// #[get("/<id>")]
35/// fn index(id: usize, state: &State<String>) -> Response {
36/// /* ... */
37/// }
38///
39/// #[launch]
40/// fn rocket() -> _ {
41/// rocket::build().mount("/", routes![index])
42/// }
43///
44/// # use rocket::{Config, error::ErrorKind};
45/// # rocket::async_test(async {
46/// # let result = rocket().reconfigure(Config::debug_default()).ignite().await;
47/// # assert!(matches!(result.unwrap_err().kind(), ErrorKind::SentinelAborts(..)));
48/// # })
49/// ```
50///
51/// At ignition time, effected by the `#[launch]` attribute here, Rocket probes
52/// all types in all mounted routes for `Sentinel` implementations. In this
53/// example, the types are: `usize`, `State<String>`, and `Response`. Those that
54/// implement `Sentinel` are queried for an abort trigger via their
55/// [`Sentinel::abort()`] method. In this example, the sentinel types are
56/// [`State`] and _potentially_ `Response`, if it implements
57/// `Sentinel`. If `abort()` returns true, launch is aborted with a
58/// corresponding error.
59///
60/// In this example, launch will be aborted because state of type `String` is
61/// not being managed. To correct the error and allow launching to proceed
62/// nominally, a value of type `String` must be managed:
63///
64/// ```rust
65/// extern crate rocket_community as rocket;
66///
67/// # use rocket::*;
68/// # type Response = ();
69/// # #[get("/<id>")]
70/// # fn index(id: usize, state: &State<String>) -> Response {
71/// # /* ... */
72/// # }
73/// #
74/// #[launch]
75/// fn rocket() -> _ {
76/// rocket::build()
77/// .mount("/", routes![index])
78/// .manage(String::from("my managed string"))
79/// }
80///
81/// # use rocket::{Config, error::ErrorKind};
82/// # rocket::async_test(async {
83/// # rocket().reconfigure(Config::debug_default()).ignite().await.unwrap();
84/// # })
85/// ```
86///
87/// # Embedded Sentinels
88///
89/// Embedded types -- type parameters of already eligible types -- are also
90/// eligible to be sentinels. Consider the following route:
91///
92/// ```rust
93/// extern crate rocket_community as rocket;
94///
95/// # use rocket::*;
96/// # use either::Either;
97/// # type Inner<T> = Option<T>;
98/// # type Foo = ();
99/// # type Bar = ();
100/// #[get("/")]
101/// fn f(guard: Option<&State<String>>) -> Either<Foo, Inner<Bar>> {
102/// unimplemented!()
103/// }
104/// ```
105///
106/// The directly eligible sentinel types, guard and responders, are:
107///
108/// * `Option<&State<String>>`
109/// * `Either<Foo, Inner<Bar>>`
110///
111/// In addition, all embedded types are _also_ eligible. These are:
112///
113/// * `&State<String>`
114/// * `State<String>`
115/// * `String`
116/// * `Foo`
117/// * `Inner<Bar>`
118/// * `Bar`
119///
120/// A type, whether embedded or not, is queried if it is a `Sentinel` _and_ none
121/// of its parent types are sentinels. Said a different way, if every _directly_
122/// eligible type is viewed as the root of an acyclic graph with edges between a
123/// type and its type parameters, the _first_ `Sentinel` in breadth-first order
124/// is queried:
125///
126/// ```text
127/// 1. Option<&State<String>> Either<Foo, Inner<Bar>>
128/// | / \
129/// 2. &State<String> Foo Inner<Bar>
130/// | |
131/// 3. State<String> Bar
132/// |
133/// 4. String
134/// ```
135///
136/// In each graph above, types are queried from top to bottom, level 1 to 4.
137/// Querying continues down paths where the parents were _not_ sentinels. For
138/// example, if `Option` is a sentinel but `Either` is not, then querying stops
139/// for the left subgraph (`Option`) but continues for the right subgraph
140/// `Either`.
141///
142/// # Limitations
143///
144/// Because Rocket must know which `Sentinel` implementation to query based on
145/// its _written_ type, generally only explicitly written, resolved, concrete
146/// types are eligible to be sentinels. A typical application will only work
147/// with such types, but there are several common cases to be aware of.
148///
149/// ## `impl Trait`
150///
151/// Occasionally an existential `impl Trait` may find its way into return types:
152///
153/// ```rust
154/// extern crate rocket_community as rocket;
155///
156/// # use rocket::*;
157/// # use either::Either;
158/// use rocket::response::Responder;
159/// # type AnotherSentinel = ();
160///
161/// #[get("/")]
162/// fn f<'r>() -> Either<impl Responder<'r, 'static>, AnotherSentinel> {
163/// /* ... */
164/// # Either::Left(())
165/// }
166/// ```
167///
168/// **Note:** _Rocket actively discourages using `impl Trait` in route
169/// signatures. In addition to impeding sentinel discovery, doing so decreases
170/// the ability to glean a handler's functionality based on its type signature._
171///
172/// The return type of the route `f` depends on its implementation. At present,
173/// it is not possible to name the underlying concrete type of an `impl Trait`
174/// at compile-time and thus not possible to determine if it implements
175/// `Sentinel`. As such, existentials _are not_ eligible to be sentinels.
176///
177/// That being said, this limitation only applies _per embedding_: types
178/// embedded inside of an `impl Trait` _are_ eligible. As such, in the example
179/// above, the named `AnotherSentinel` type continues to be eligible.
180///
181/// When possible, prefer to name all types:
182///
183/// ```rust
184/// extern crate rocket_community as rocket;
185///
186/// # use rocket::*;
187/// # use either::Either;
188/// # type AbortingSentinel = ();
189/// # type AnotherSentinel = ();
190/// #[get("/")]
191/// fn f() -> Either<AbortingSentinel, AnotherSentinel> {
192/// /* ... */
193/// # unimplemented!()
194/// }
195/// ```
196///
197/// ## Aliases
198///
199/// _Embedded_ sentinels made opaque by a type alias will fail to be considered;
200/// the aliased type itself _is_ considered. In the example below, only
201/// `Result<Foo, Bar>` will be considered, while the embedded `Foo` and `Bar`
202/// will not.
203///
204/// ```rust
205/// extern crate rocket_community as rocket;
206///
207/// # use rocket::get;
208/// # type Foo = ();
209/// # type Bar = ();
210/// type SomeAlias = Result<Foo, Bar>;
211///
212/// #[get("/")]
213/// fn f() -> SomeAlias {
214/// /* ... */
215/// # unimplemented!()
216/// }
217/// ```
218///
219/// Note, however, that `Option<T>` and [`Debug<T>`](crate::response::Debug) are
220/// a sentinels if `T: Sentinel`, and `Result<T, E>` and `Either<T, E>` are
221/// sentinels if _both_ `T: Sentinel, E: Sentinel`. Thus, for these specific
222/// cases, a type alias _will_ "consider" embeddings. Nevertheless, prefer to
223/// write concrete types when possible.
224///
225/// ## Type Macros
226///
227/// It is impossible to determine, a priori, what a type macro will expand to.
228/// As such, Rocket is unable to determine which sentinels, if any, a type macro
229/// references, and thus no sentinels are discovered from type macros.
230///
231/// Even approximations are impossible. For example, consider the following:
232///
233/// ```rust
234/// extern crate rocket_community as rocket;
235///
236/// # use rocket::*;
237/// macro_rules! MyType {
238/// (State<'_, u32>) => (&'_ rocket::Config)
239/// }
240///
241/// #[get("/")]
242/// fn f(guard: MyType![State<'_, u32>]) {
243/// /* ... */
244/// }
245/// ```
246///
247/// While the `MyType![State<'_, u32>]` type _appears_ to contain a `State`
248/// sentinel, the macro actually expands to `&'_ rocket::Config`, which is _not_
249/// the `State` sentinel.
250///
251/// Because Rocket knows the exact syntax expected by type macros that it
252/// exports, such as the [typed stream] macros, discovery in these macros works
253/// as expected. You should prefer not to use type macros aside from those
254/// exported by Rocket, or if necessary, restrict your use to those that always
255/// expand to types without sentinels.
256///
257/// [typed stream]: crate::response::stream
258///
259/// # Custom Sentinels
260///
261/// Any type can implement `Sentinel`, and the implementation can arbitrarily
262/// inspect an ignited instance of `Rocket`. For illustration, consider the
263/// following implementation of `Sentinel` for a custom `Responder` which
264/// requires:
265///
266/// * state for a type `T` to be managed
267/// * a catcher for status code `400` at base `/`
268///
269/// ```rust
270/// extern crate rocket_community as rocket;
271///
272/// use rocket::{Rocket, Ignite, Sentinel};
273/// # struct MyResponder;
274/// # struct T;
275///
276/// impl Sentinel for MyResponder {
277/// fn abort(rocket: &Rocket<Ignite>) -> bool {
278/// if rocket.state::<T>().is_none() {
279/// return true;
280/// }
281///
282/// if !rocket.catchers().any(|c| c.code == Some(400) && c.base() == "/") {
283/// return true;
284/// }
285///
286/// false
287/// }
288/// }
289/// ```
290///
291/// If a `MyResponder` is returned by any mounted route, its `abort()` method
292/// will be invoked. If the required conditions aren't met, signaled by
293/// returning `true` from `abort()`, Rocket aborts launch.
294pub trait Sentinel {
295 /// Returns `true` if launch should be aborted and `false` otherwise.
296 fn abort(rocket: &Rocket<Ignite>) -> bool;
297}
298
299impl<T: Sentinel> Sentinel for Option<T> {
300 fn abort(rocket: &Rocket<Ignite>) -> bool {
301 T::abort(rocket)
302 }
303}
304
305// In the next impls, we want to run _both_ sentinels _without_ short
306// circuiting, for the logs. Ideally we could check if these are the same type
307// or not, but `TypeId` only works with `'static`, and adding those bounds to
308// `T` and `E` would reduce the types for which the implementations work, which
309// would mean more types that we miss in type applies. When the type _isn't_ an
310// alias, however, the existence of these implementations is strictly worse.
311
312impl<T: Sentinel, E: Sentinel> Sentinel for Result<T, E> {
313 fn abort(rocket: &Rocket<Ignite>) -> bool {
314 let left = T::abort(rocket);
315 let right = E::abort(rocket);
316 left || right
317 }
318}
319
320impl<T: Sentinel, E: Sentinel> Sentinel for either::Either<T, E> {
321 fn abort(rocket: &Rocket<Ignite>) -> bool {
322 let left = T::abort(rocket);
323 let right = E::abort(rocket);
324 left || right
325 }
326}
327
328/// A sentinel that never aborts. The `Responder` impl for `Debug` will never be
329/// called, so it's okay to not abort for failing `T: Sentinel`.
330impl<T> Sentinel for crate::response::Debug<T> {
331 fn abort(_: &Rocket<Ignite>) -> bool {
332 false
333 }
334}
335
336/// Information resolved at compile-time from eligible [`Sentinel`] types.
337///
338/// Returned as a result of the [`ignition`](Rocket::ignite()) method, this
339/// struct contains information about a resolved sentinel including the type ID
340/// and type name. It is made available via the [`ErrorKind::SentinelAborts`]
341/// variant of the [`ErrorKind`] enum.
342///
343/// [`ErrorKind`]: crate::error::ErrorKind
344/// [`ErrorKind::SentinelAborts`]: crate::error::ErrorKind::SentinelAborts
345///
346// The information resolved from a `T: ?Sentinel` by the `resolve!()` macro.
347#[derive(Clone, Copy)]
348pub struct Sentry {
349 /// The type ID of `T`.
350 #[doc(hidden)]
351 pub type_id: TypeId,
352 /// The type name `T` as a string.
353 #[doc(hidden)]
354 pub type_name: &'static str,
355 /// The type ID of type in which `T` is nested if not a top-level type.
356 #[doc(hidden)]
357 pub parent: Option<TypeId>,
358 /// The source (file, column, line) location of the resolved `T`.
359 #[doc(hidden)]
360 pub location: (&'static str, u32, u32),
361 /// The value of `<T as Sentinel>::SPECIALIZED` or the fallback.
362 ///
363 /// This is `true` when `T: Sentinel` and `false` when `T: !Sentinel`.
364 #[doc(hidden)]
365 pub specialized: bool,
366 /// The value of `<T as Sentinel>::abort` or the fallback.
367 #[doc(hidden)]
368 pub abort: fn(&Rocket<Ignite>) -> bool,
369}
370
371impl Sentry {
372 /// Returns the type ID of the resolved sentinel type.
373 ///
374 /// # Example
375 ///
376 /// ```rust
377 /// extern crate rocket_community as rocket;
378 ///
379 /// use rocket::Sentry;
380 ///
381 /// fn handle_error(sentry: &Sentry) {
382 /// let type_id = sentry.type_id();
383 /// }
384 /// ```
385 pub fn type_id(&self) -> TypeId {
386 self.type_id
387 }
388
389 /// Returns the type name of the resolved sentinel type.
390 ///
391 /// # Example
392 ///
393 /// ```rust
394 /// extern crate rocket_community as rocket;
395 ///
396 /// use rocket::Sentry;
397 ///
398 /// fn handle_error(sentry: &Sentry) {
399 /// let type_name = sentry.type_name();
400 /// println!("Type name: {}", type_name);
401 /// }
402 pub fn type_name(&self) -> &'static str {
403 self.type_name
404 }
405}
406
407/// Query `sentinels`, once for each unique `type_id`, returning an `Err` of all
408/// of the sentinels that triggered an abort or `Ok(())` if none did.
409pub(crate) fn query<'s>(
410 sentinels: impl Iterator<Item = &'s Sentry>,
411 rocket: &Rocket<Ignite>,
412) -> Result<(), Vec<Sentry>> {
413 use std::collections::{HashMap, VecDeque};
414
415 // Build a graph of the sentinels.
416 let mut roots: VecDeque<&'s Sentry> = VecDeque::new();
417 let mut map: HashMap<TypeId, VecDeque<&'s Sentry>> = HashMap::new();
418 for sentinel in sentinels {
419 match sentinel.parent {
420 Some(parent) => map.entry(parent).or_default().push_back(sentinel),
421 None => roots.push_back(sentinel),
422 }
423 }
424
425 // Traverse the graph in breadth-first order. If we find a specialized
426 // sentinel, query it (once for a unique type) and don't traverse its
427 // children. Otherwise, traverse its children. Record queried aborts.
428 let mut remaining = roots;
429 let mut visited: HashMap<TypeId, bool> = HashMap::new();
430 let mut aborted = vec![];
431 while let Some(sentinel) = remaining.pop_front() {
432 if sentinel.specialized {
433 if *visited
434 .entry(sentinel.type_id)
435 .or_insert_with(|| (sentinel.abort)(rocket))
436 {
437 aborted.push(sentinel);
438 }
439 } else if let Some(mut children) = map.remove(&sentinel.type_id) {
440 remaining.append(&mut children);
441 }
442 }
443
444 match aborted.is_empty() {
445 true => Ok(()),
446 false => Err(aborted.into_iter().cloned().collect()),
447 }
448}
449
450impl fmt::Debug for Sentry {
451 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452 f.debug_struct("Sentry")
453 .field("type_id", &self.type_id)
454 .field("type_name", &self.type_name)
455 .field("parent", &self.parent)
456 .field("location", &self.location)
457 .field("default", &self.specialized)
458 .finish()
459 }
460}
461
462/// Resolves a `T` to the specialized or fallback implementation of
463/// `Sentinel`, returning a `Sentry` struct with the resolved items.
464#[doc(hidden)]
465#[macro_export]
466macro_rules! resolve {
467 ($T:ty $(, $P:ty)?) => ({
468 #[allow(unused_imports)]
469 use $crate::sentinel::resolution::{Resolve, DefaultSentinel as _};
470
471 $crate::sentinel::Sentry {
472 type_id: std::any::TypeId::of::<$T>(),
473 type_name: std::any::type_name::<$T>(),
474 parent: None $(.or(Some(std::any::TypeId::of::<$P>())))?,
475 location: (std::file!(), std::line!(), std::column!()),
476 specialized: Resolve::<$T>::SPECIALIZED,
477 abort: Resolve::<$T>::abort,
478 }
479 })
480}
481
482pub use resolve;
483
484pub mod resolution {
485 use super::*;
486
487 /// The *magic*.
488 ///
489 /// `Resolve<T>::item` for `T: Sentinel` is `<T as Sentinel>::item`.
490 /// `Resolve<T>::item` for `T: !Sentinel` is `DefaultSentinel::item`.
491 ///
492 /// This _must_ be used as `Resolve::<T>:item` for resolution to work. This
493 /// is a fun, static dispatch hack for "specialization" that works because
494 /// Rust prefers inherent methods over blanket trait impl methods.
495 pub struct Resolve<T: ?Sized>(std::marker::PhantomData<T>);
496
497 /// Fallback trait "implementing" `Sentinel` for all types. This is what
498 /// Rust will resolve `Resolve<T>::item` to when `T: !Sentinel`.
499 pub trait DefaultSentinel {
500 const SPECIALIZED: bool = false;
501
502 fn abort(_: &Rocket<Ignite>) -> bool {
503 false
504 }
505 }
506
507 impl<T: ?Sized> DefaultSentinel for T {}
508
509 /// "Specialized" "implementation" of `Sentinel` for `T: Sentinel`. This is
510 /// what Rust will resolve `Resolve<T>::item` to when `T: Sentinel`.
511 impl<T: Sentinel + ?Sized> Resolve<T> {
512 pub const SPECIALIZED: bool = true;
513
514 pub fn abort(rocket: &Rocket<Ignite>) -> bool {
515 T::abort(rocket)
516 }
517 }
518}
519
520#[cfg(test)]
521mod test {
522 use std::any::TypeId;
523
524 struct NotASentinel;
525 struct YesASentinel;
526
527 impl super::Sentinel for YesASentinel {
528 fn abort(_: &crate::Rocket<crate::Ignite>) -> bool {
529 unimplemented!()
530 }
531 }
532
533 #[test]
534 fn check_can_determine() {
535 let not_a_sentinel = resolve!(NotASentinel);
536 assert!(not_a_sentinel.type_name.ends_with("NotASentinel"));
537 assert!(!not_a_sentinel.specialized);
538
539 let yes_a_sentinel = resolve!(YesASentinel);
540 assert!(yes_a_sentinel.type_name.ends_with("YesASentinel"));
541 assert!(yes_a_sentinel.specialized);
542 }
543
544 struct HasSentinel<T>(T);
545
546 #[test]
547 fn parent_works() {
548 let child = resolve!(YesASentinel, HasSentinel<YesASentinel>);
549 assert!(child.type_name.ends_with("YesASentinel"));
550 assert_eq!(
551 child.parent.unwrap(),
552 TypeId::of::<HasSentinel<YesASentinel>>()
553 );
554 assert!(child.specialized);
555
556 let not_a_direct_sentinel = resolve!(HasSentinel<YesASentinel>);
557 assert!(not_a_direct_sentinel.type_name.contains("HasSentinel"));
558 assert!(not_a_direct_sentinel.type_name.contains("YesASentinel"));
559 assert!(not_a_direct_sentinel.parent.is_none());
560 assert!(!not_a_direct_sentinel.specialized);
561 }
562}