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