pub trait Sentinel {
// Required method
fn abort(rocket: &Rocket<Ignite>) -> bool;
}Expand description
An automatic last line of defense against launching an invalid Rocket.
A sentinel, automatically run on ignition, can trigger
a launch abort should an instance fail to meet arbitrary conditions. Every
type that appears in a mounted route’s type signature is eligible to be
a sentinel. Of these, those that implement Sentinel have their
abort() method invoked automatically, immediately
after ignition, once for each unique type. Sentinels inspect the finalized
instance of Rocket and can trigger a launch abort by returning true.
§Built-In Sentinels
The State<T> type is a sentinel that triggers an abort if the finalized
Rocket instance is not managing state for type T. Doing so prevents
run-time failures of the State request guard.
§Example
As an example, consider the following simple application:
#[get("/<id>")]
fn index(id: usize, state: &State<String>) -> Response {
/* ... */
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![index])
}
At ignition time, effected by the #[launch] attribute here, Rocket probes
all types in all mounted routes for Sentinel implementations. In this
example, the types are: usize, State<String>, and Response. Those that
implement Sentinel are queried for an abort trigger via their
Sentinel::abort() method. In this example, the sentinel types are
State and potentially Response, if it implements
Sentinel. If abort() returns true, launch is aborted with a
corresponding error.
In this example, launch will be aborted because state of type String is
not being managed. To correct the error and allow launching to proceed
nominally, a value of type String must be managed:
#[launch]
fn rocket() -> _ {
rocket::build()
.mount("/", routes![index])
.manage(String::from("my managed string"))
}
§Embedded Sentinels
Embedded types – type parameters of already eligible types – are also eligible to be sentinels. Consider the following route:
#[get("/")]
fn f(guard: Option<&State<String>>) -> Either<Foo, Inner<Bar>> {
unimplemented!()
}The directly eligible sentinel types, guard and responders, are:
Option<&State<String>>Either<Foo, Inner<Bar>>
In addition, all embedded types are also eligible. These are:
&State<String>State<String>StringFooInner<Bar>Bar
A type, whether embedded or not, is queried if it is a Sentinel and none
of its parent types are sentinels. Said a different way, if every directly
eligible type is viewed as the root of an acyclic graph with edges between a
type and its type parameters, the first Sentinel in breadth-first order
is queried:
1. Option<&State<String>> Either<Foo, Inner<Bar>>
| / \
2. &State<String> Foo Inner<Bar>
| |
3. State<String> Bar
|
4. StringIn each graph above, types are queried from top to bottom, level 1 to 4.
Querying continues down paths where the parents were not sentinels. For
example, if Option is a sentinel but Either is not, then querying stops
for the left subgraph (Option) but continues for the right subgraph
Either.
§Limitations
Because Rocket must know which Sentinel implementation to query based on
its written type, generally only explicitly written, resolved, concrete
types are eligible to be sentinels. A typical application will only work
with such types, but there are several common cases to be aware of.
§impl Trait
Occasionally an existential impl Trait may find its way into return types:
use rocket::response::Responder;
#[get("/")]
fn f<'r>() -> Either<impl Responder<'r, 'static>, AnotherSentinel> {
/* ... */
}Note: Rocket actively discourages using impl Trait in route
signatures. In addition to impeding sentinel discovery, doing so decreases
the ability to gleam a handler’s functionality based on its type signature.
The return type of the route f depends on its implementation. At present,
it is not possible to name the underlying concrete type of an impl Trait
at compile-time and thus not possible to determine if it implements
Sentinel. As such, existentials are not eligible to be sentinels.
That being said, this limitation only applies per embedding: types
embedded inside of an impl Trait are eligible. As such, in the example
above, the named AnotherSentinel type continues to be eligible.
When possible, prefer to name all types:
#[get("/")]
fn f() -> Either<AbortingSentinel, AnotherSentinel> {
/* ... */
}§Aliases
Embedded sentinels made opaque by a type alias will fail to be considered;
the aliased type itself is considered. In the example below, only
Result<Foo, Bar> will be considered, while the embedded Foo and Bar
will not.
type SomeAlias = Result<Foo, Bar>;
#[get("/")]
fn f() -> SomeAlias {
/* ... */
}Note, however, that Option<T> and Debug<T> are
a sentinels if T: Sentinel, and Result<T, E> and Either<T, E> are
sentinels if both T: Sentinel, E: Sentinel. Thus, for these specific
cases, a type alias will “consider” embeddings. Nevertheless, prefer to
write concrete types when possible.
§Type Macros
It is impossible to determine, a priori, what a type macro will expand to. As such, Rocket is unable to determine which sentinels, if any, a type macro references, and thus no sentinels are discovered from type macros.
Even approximations are impossible. For example, consider the following:
macro_rules! MyType {
(State<'_, u32>) => (&'_ rocket::Config)
}
#[get("/")]
fn f(guard: MyType![State<'_, u32>]) {
/* ... */
}While the MyType![State<'_, u32>] type appears to contain a State
sentinel, the macro actually expands to &'_ rocket::Config, which is not
the State sentinel.
Because Rocket knows the exact syntax expected by type macros that it exports, such as the typed stream macros, discovery in these macros works as expected. You should prefer not to use type macros aside from those exported by Rocket, or if necessary, restrict your use to those that always expand to types without sentinels.
§Custom Sentinels
Any type can implement Sentinel, and the implementation can arbitrarily
inspect an ignited instance of Rocket. For illustration, consider the
following implementation of Sentinel for a custom Responder which
requires:
- state for a type
Tto be managed - a catcher for status code
400at base/
use rocket::{Rocket, Ignite, Sentinel};
impl Sentinel for MyResponder {
fn abort(rocket: &Rocket<Ignite>) -> bool {
if rocket.state::<T>().is_none() {
return true;
}
if !rocket.catchers().any(|c| c.code == Some(400) && c.base == "/") {
return true;
}
false
}
}If a MyResponder is returned by any mounted route, its abort() method
will be invoked. If the required conditions aren’t met, signaled by
returning true from abort(), Rocket aborts launch.
Required Methods§
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.
Implementations on Foreign Types§
Implementors§
impl<T> Sentinel for Debug<T>
A sentinel that never aborts. The Responder impl for Debug will never be
called, so it’s okay to not abort for failing T: Sentinel.