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}