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}