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().reconfigure(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().reconfigure(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 glean 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/// Information resolved at compile-time from eligible [`Sentinel`] types.
321///
322/// Returned as a result of the [`ignition`](Rocket::ignite()) method, this
323/// struct contains information about a resolved sentinel including the type ID
324/// and type name. It is made available via the [`ErrorKind::SentinelAborts`]
325/// variant of the [`ErrorKind`] enum.
326///
327/// [`ErrorKind`]: crate::error::ErrorKind
328/// [`ErrorKind::SentinelAborts`]: crate::error::ErrorKind::SentinelAborts
329///
330// The information resolved from a `T: ?Sentinel` by the `resolve!()` macro.
331#[derive(Clone, Copy)]
332pub struct Sentry {
333    /// The type ID of `T`.
334    #[doc(hidden)]
335    pub type_id: TypeId,
336    /// The type name `T` as a string.
337    #[doc(hidden)]
338    pub type_name: &'static str,
339    /// The type ID of type in which `T` is nested if not a top-level type.
340    #[doc(hidden)]
341    pub parent: Option<TypeId>,
342    /// The source (file, column, line) location of the resolved `T`.
343    #[doc(hidden)]
344    pub location: (&'static str, u32, u32),
345    /// The value of `<T as Sentinel>::SPECIALIZED` or the fallback.
346    ///
347    /// This is `true` when `T: Sentinel` and `false` when `T: !Sentinel`.
348    #[doc(hidden)]
349    pub specialized: bool,
350    /// The value of `<T as Sentinel>::abort` or the fallback.
351    #[doc(hidden)]
352    pub abort: fn(&Rocket<Ignite>) -> bool,
353}
354
355impl Sentry {
356    /// Returns the type ID of the resolved sentinel type.
357    ///
358    /// # Example
359    ///
360    /// ```rust
361    /// use rocket::Sentry;
362    ///
363    /// fn handle_error(sentry: &Sentry) {
364    ///     let type_id = sentry.type_id();
365    /// }
366    /// ```
367    pub fn type_id(&self) -> TypeId {
368        self.type_id
369    }
370
371    /// Returns the type name of the resolved sentinel type.
372    ///
373    /// # Example
374    ///
375    /// ```rust
376    /// use rocket::Sentry;
377    ///
378    /// fn handle_error(sentry: &Sentry) {
379    ///     let type_name = sentry.type_name();
380    ///     println!("Type name: {}", type_name);
381    /// }
382    pub fn type_name(&self) -> &'static str {
383        self.type_name
384    }
385}
386
387/// Query `sentinels`, once for each unique `type_id`, returning an `Err` of all
388/// of the sentinels that triggered an abort or `Ok(())` if none did.
389pub(crate) fn query<'s>(
390    sentinels: impl Iterator<Item = &'s Sentry>,
391    rocket: &Rocket<Ignite>,
392) -> Result<(), Vec<Sentry>> {
393    use std::collections::{HashMap, VecDeque};
394
395    // Build a graph of the sentinels.
396    let mut roots: VecDeque<&'s Sentry> = VecDeque::new();
397    let mut map: HashMap<TypeId, VecDeque<&'s Sentry>> = HashMap::new();
398    for sentinel in sentinels {
399        match sentinel.parent {
400            Some(parent) => map.entry(parent).or_default().push_back(sentinel),
401            None => roots.push_back(sentinel),
402        }
403    }
404
405    // Traverse the graph in breadth-first order. If we find a specialized
406    // sentinel, query it (once for a unique type) and don't traverse its
407    // children. Otherwise, traverse its children. Record queried aborts.
408    let mut remaining = roots;
409    let mut visited: HashMap<TypeId, bool> = HashMap::new();
410    let mut aborted = vec![];
411    while let Some(sentinel) = remaining.pop_front() {
412        if sentinel.specialized {
413            if *visited.entry(sentinel.type_id).or_insert_with(|| (sentinel.abort)(rocket)) {
414                aborted.push(sentinel);
415            }
416        } else if let Some(mut children) = map.remove(&sentinel.type_id) {
417            remaining.append(&mut children);
418        }
419    }
420
421    match aborted.is_empty() {
422        true => Ok(()),
423        false => Err(aborted.into_iter().cloned().collect())
424    }
425}
426
427impl fmt::Debug for Sentry {
428    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429        f.debug_struct("Sentry")
430            .field("type_id", &self.type_id)
431            .field("type_name", &self.type_name)
432            .field("parent", &self.parent)
433            .field("location", &self.location)
434            .field("default", &self.specialized)
435            .finish()
436    }
437}
438
439/// Resolves a `T` to the specialized or fallback implementation of
440/// `Sentinel`, returning a `Sentry` struct with the resolved items.
441#[doc(hidden)]
442#[macro_export]
443macro_rules! resolve {
444    ($T:ty $(, $P:ty)?) => ({
445        #[allow(unused_imports)]
446        use $crate::sentinel::resolution::{Resolve, DefaultSentinel as _};
447
448        $crate::sentinel::Sentry {
449            type_id: std::any::TypeId::of::<$T>(),
450            type_name: std::any::type_name::<$T>(),
451            parent: None $(.or(Some(std::any::TypeId::of::<$P>())))?,
452            location: (std::file!(), std::line!(), std::column!()),
453            specialized: Resolve::<$T>::SPECIALIZED,
454            abort: Resolve::<$T>::abort,
455        }
456    })
457}
458
459pub use resolve;
460
461pub mod resolution {
462    use super::*;
463
464    /// The *magic*.
465    ///
466    /// `Resolve<T>::item` for `T: Sentinel` is `<T as Sentinel>::item`.
467    /// `Resolve<T>::item` for `T: !Sentinel` is `DefaultSentinel::item`.
468    ///
469    /// This _must_ be used as `Resolve::<T>:item` for resolution to work. This
470    /// is a fun, static dispatch hack for "specialization" that works because
471    /// Rust prefers inherent methods over blanket trait impl methods.
472    pub struct Resolve<T: ?Sized>(std::marker::PhantomData<T>);
473
474    /// Fallback trait "implementing" `Sentinel` for all types. This is what
475    /// Rust will resolve `Resolve<T>::item` to when `T: !Sentinel`.
476    pub trait DefaultSentinel {
477        const SPECIALIZED: bool = false;
478
479        fn abort(_: &Rocket<Ignite>) -> bool { false }
480    }
481
482    impl<T: ?Sized> DefaultSentinel for T {}
483
484    /// "Specialized" "implementation" of `Sentinel` for `T: Sentinel`. This is
485    /// what Rust will resolve `Resolve<T>::item` to when `T: Sentinel`.
486    impl<T: Sentinel + ?Sized> Resolve<T> {
487        pub const SPECIALIZED: bool = true;
488
489        pub fn abort(rocket: &Rocket<Ignite>) -> bool {
490            T::abort(rocket)
491        }
492    }
493}
494
495#[cfg(test)]
496mod test {
497    use std::any::TypeId;
498
499    struct NotASentinel;
500    struct YesASentinel;
501
502    impl super::Sentinel for YesASentinel {
503        fn abort(_: &crate::Rocket<crate::Ignite>) -> bool {
504            unimplemented!()
505        }
506    }
507
508    #[test]
509    fn check_can_determine() {
510        let not_a_sentinel = resolve!(NotASentinel);
511        assert!(not_a_sentinel.type_name.ends_with("NotASentinel"));
512        assert!(!not_a_sentinel.specialized);
513
514        let yes_a_sentinel = resolve!(YesASentinel);
515        assert!(yes_a_sentinel.type_name.ends_with("YesASentinel"));
516        assert!(yes_a_sentinel.specialized);
517    }
518
519    struct HasSentinel<T>(T);
520
521    #[test]
522    fn parent_works() {
523        let child = resolve!(YesASentinel, HasSentinel<YesASentinel>);
524        assert!(child.type_name.ends_with("YesASentinel"));
525        assert_eq!(child.parent.unwrap(), TypeId::of::<HasSentinel<YesASentinel>>());
526        assert!(child.specialized);
527
528        let not_a_direct_sentinel = resolve!(HasSentinel<YesASentinel>);
529        assert!(not_a_direct_sentinel.type_name.contains("HasSentinel"));
530        assert!(not_a_direct_sentinel.type_name.contains("YesASentinel"));
531        assert!(not_a_direct_sentinel.parent.is_none());
532        assert!(!not_a_direct_sentinel.specialized);
533    }
534}