rocket/
error.rs

1//! Types representing various errors that can occur in a Rocket application.
2
3use std::{io, fmt, process};
4use std::error::Error as StdError;
5use std::sync::Arc;
6
7use figment::Profile;
8
9use crate::listener::Endpoint;
10use crate::{Catcher, Ignite, Orbit, Phase, Rocket, Route};
11use crate::trace::Trace;
12
13/// An error that occurred during launch or ignition.
14///
15/// An `Error` is returned by [`Rocket::launch()`] or [`Rocket::ignite()`] on
16/// failure to launch or ignite, respectively. An `Error` may occur when the
17/// configuration is invalid, when a route or catcher collision is detected, or
18/// when a fairing fails to launch. An `Error` may also occur when the Rocket
19/// instance fails to liftoff or when the Rocket instance fails to shutdown.
20/// Finally, an `Error` may occur when a sentinel requests an abort.
21///
22/// To determine the kind of error that occurred, use [`Error::kind()`].
23///
24/// # Example
25///
26/// ```rust
27/// # use rocket::*;
28/// use rocket::trace::Trace;
29/// use rocket::error::ErrorKind;
30///
31/// # async fn run() -> Result<(), rocket::error::Error> {
32/// if let Err(e) = rocket::build().ignite().await {
33///     match e.kind() {
34///         ErrorKind::Bind(_, e) => info!("binding failed: {}", e),
35///         ErrorKind::Io(e) => info!("I/O error: {}", e),
36///         _ => e.trace_error(),
37///     }
38///
39///     return Err(e);
40/// }
41/// # Ok(())
42/// # }
43/// ```
44pub struct Error {
45    pub(crate) kind: ErrorKind
46}
47
48/// The error kind that occurred. Returned by [`Error::kind()`].
49///
50/// In almost every instance, a launch error occurs because of an I/O error;
51/// this is represented by the `Io` variant. A launch error may also occur
52/// because of ill-defined routes that lead to collisions or because a fairing
53/// encountered an error; these are represented by the `Collision` and
54/// `FailedFairing` variants, respectively.
55#[derive(Debug)]
56#[non_exhaustive]
57pub enum ErrorKind {
58    /// Binding to the network interface at `.0` (if known) failed with `.1`.
59    Bind(Option<Endpoint>, Box<dyn StdError + Send>),
60    /// An I/O error occurred during launch.
61    Io(io::Error),
62    /// A valid [`Config`](crate::Config) could not be extracted from the
63    /// configured figment.
64    Config(figment::Error),
65    /// Route or catcher collisions were detected. At least one of `routes` or
66    /// `catchers` is guaranteed to be non-empty.
67    Collisions {
68        /// Pairs of colliding routes, if any.
69        routes: Vec<(Route, Route)>,
70        /// Pairs of colliding catchers, if any.
71        catchers: Vec<(Catcher, Catcher)>,
72    },
73    /// Launch fairing(s) failed.
74    FailedFairings(Vec<crate::fairing::Info>),
75    /// Sentinels requested abort.
76    SentinelAborts(Vec<crate::sentinel::Sentry>),
77    /// The configuration profile is not debug but no secret key is configured.
78    InsecureSecretKey(Profile),
79    /// Liftoff failed. Contains the Rocket instance that failed to shutdown.
80    Liftoff(
81        Result<Box<Rocket<Ignite>>, Arc<Rocket<Orbit>>>,
82        tokio::task::JoinError,
83    ),
84    /// Shutdown failed. Contains the Rocket instance that failed to shutdown.
85    Shutdown(Arc<Rocket<Orbit>>),
86}
87
88/// An error that occurs when a value was unexpectedly empty.
89#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
90pub struct Empty;
91
92/// An error that occurs when a value doesn't match one of the expected options.
93///
94/// This error is returned by the [`FromParam`] trait implementation generated
95/// by the [`FromParam` derive](macro@rocket::FromParam) when the value of a
96/// dynamic path segment does not match one of the expected variants. The
97/// `value` field will contain the value that was provided, and `options` will
98/// contain each of possible stringified variants.
99///
100/// [`FromParam`]: trait@rocket::request::FromParam
101///
102/// # Example
103///
104/// ```rust
105/// # #[macro_use] extern crate rocket;
106/// use rocket::error::InvalidOption;
107///
108/// #[derive(FromParam)]
109/// enum MyParam {
110///     FirstOption,
111///     SecondOption,
112///     ThirdOption,
113/// }
114///
115/// #[get("/<param>")]
116/// fn hello(param: Result<MyParam, InvalidOption<'_>>) {
117///     if let Err(e) = param {
118///         assert_eq!(e.options, &["FirstOption", "SecondOption", "ThirdOption"]);
119///     }
120/// }
121/// ```
122#[derive(Debug, Clone)]
123#[non_exhaustive]
124pub struct InvalidOption<'a> {
125    /// The value that was provided.
126    pub value: &'a str,
127    /// The expected values: a slice of strings, one for each possible value.
128    pub options: &'static [&'static str],
129}
130
131impl<'a> InvalidOption<'a> {
132    #[doc(hidden)]
133    pub fn new(value: &'a str, options: &'static [&'static str]) -> Self {
134        Self { value, options }
135    }
136}
137
138impl fmt::Display for InvalidOption<'_> {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        write!(f, "unexpected value {:?}, expected one of {:?}", self.value, self.options)
141    }
142}
143
144impl std::error::Error for InvalidOption<'_> {}
145
146impl Error {
147    #[inline(always)]
148    pub(crate) fn new(kind: ErrorKind) -> Error {
149        Error { kind }
150    }
151
152    /// Returns the kind of error that occurred.
153    ///
154    /// # Example
155    ///
156    /// ```rust
157    /// # use rocket::*;
158    /// use rocket::trace::Trace;
159    /// use rocket::error::ErrorKind;
160    ///
161    /// # async fn run() -> Result<(), rocket::error::Error> {
162    /// if let Err(e) = rocket::build().ignite().await {
163    ///     match e.kind() {
164    ///         ErrorKind::Bind(_, e) => info!("binding failed: {}", e),
165    ///         ErrorKind::Io(e) => info!("I/O error: {}", e),
166    ///         _ => e.trace_error(),
167    ///    }
168    /// }
169    /// # Ok(())
170    /// # }
171    /// ```
172    pub fn kind(&self) -> &ErrorKind {
173        &self.kind
174    }
175
176    /// Given the return value of [`Rocket::launch()`] or [`Rocket::ignite()`],
177    /// which return a `Result<Rocket<P>, Error>`, logs the error, if any, and
178    /// returns the appropriate exit code.
179    ///
180    /// For `Ok(_)`, returns `ExitCode::SUCCESS`. For `Err(e)`, logs the error
181    /// and returns `ExitCode::FAILURE`.
182    ///
183    /// # Example
184    ///
185    /// ```rust
186    /// # use rocket::*;
187    /// use std::process::ExitCode;
188    /// use rocket::error::Error;
189    ///
190    /// async fn run() -> ExitCode {
191    ///     Error::report(rocket::build().launch().await)
192    /// }
193    /// ```
194    pub fn report<P: Phase>(result: Result<Rocket<P>, Error>) -> process::ExitCode {
195        match result {
196            Ok(_) => process::ExitCode::SUCCESS,
197            Err(e) => {
198                span_error!("launch failure", "aborting launch due to error" => e.trace_error());
199                process::ExitCode::SUCCESS
200            }
201        }
202    }
203}
204
205impl From<ErrorKind> for Error {
206    fn from(kind: ErrorKind) -> Self {
207        Error::new(kind)
208    }
209}
210
211impl From<figment::Error> for Error {
212    fn from(e: figment::Error) -> Self {
213        Error::new(ErrorKind::Config(e))
214    }
215}
216
217impl From<io::Error> for Error {
218    fn from(e: io::Error) -> Self {
219        Error::new(ErrorKind::Io(e))
220    }
221}
222
223impl StdError for Error {
224    fn source(&self) -> Option<&(dyn StdError + 'static)> {
225        match &self.kind {
226            ErrorKind::Bind(_, e) => Some(&**e),
227            ErrorKind::Io(e) => Some(e),
228            ErrorKind::Collisions { .. } => None,
229            ErrorKind::FailedFairings(_) => None,
230            ErrorKind::InsecureSecretKey(_) => None,
231            ErrorKind::Config(e) => Some(e),
232            ErrorKind::SentinelAborts(_) => None,
233            ErrorKind::Liftoff(_, e) => Some(e),
234            ErrorKind::Shutdown(_) => None,
235        }
236    }
237}
238
239impl fmt::Display for ErrorKind {
240    #[inline]
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        match self {
243            ErrorKind::Bind(_, e) => write!(f, "binding failed: {e}"),
244            ErrorKind::Io(e) => write!(f, "I/O error: {e}"),
245            ErrorKind::Collisions { .. } => "collisions detected".fmt(f),
246            ErrorKind::FailedFairings(_) => "launch fairing(s) failed".fmt(f),
247            ErrorKind::InsecureSecretKey(_) => "insecure secret key config".fmt(f),
248            ErrorKind::Config(_) => "failed to extract configuration".fmt(f),
249            ErrorKind::SentinelAborts(_) => "sentinel(s) aborted".fmt(f),
250            ErrorKind::Liftoff(_, _) => "liftoff failed".fmt(f),
251            ErrorKind::Shutdown(_) => "shutdown failed".fmt(f),
252        }
253    }
254}
255
256impl fmt::Debug for Error {
257    #[inline]
258    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259        self.kind.fmt(f)
260    }
261}
262
263impl fmt::Display for Error {
264    #[inline]
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        write!(f, "{}", self.kind)
267    }
268}
269
270impl fmt::Debug for Empty {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        f.write_str("empty parameter")
273    }
274}
275
276impl fmt::Display for Empty {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        f.write_str("empty parameter")
279    }
280}
281
282impl StdError for Empty { }
283
284struct ServerError<'a>(&'a (dyn StdError + 'static));
285
286impl fmt::Display for ServerError<'_> {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        let error = &self.0;
289        if let Some(e) = error.downcast_ref::<hyper::Error>() {
290            write!(f, "request failed: {e}")?;
291        } else if let Some(e) = error.downcast_ref::<io::Error>() {
292            write!(f, "connection error: ")?;
293
294            match e.kind() {
295                io::ErrorKind::NotConnected => write!(f, "remote disconnected")?,
296                io::ErrorKind::UnexpectedEof => write!(f, "remote sent early eof")?,
297                io::ErrorKind::ConnectionReset
298                | io::ErrorKind::ConnectionAborted => write!(f, "terminated by remote")?,
299                _ => write!(f, "{e}")?,
300            }
301        } else {
302            write!(f, "http server error: {error}")?;
303        }
304
305        Ok(())
306    }
307}
308
309/// Log an error that occurs during request processing
310#[track_caller]
311pub(crate) fn log_server_error(error: &(dyn StdError + 'static)) {
312    let mut error: &(dyn StdError + 'static) = error;
313    if error.downcast_ref::<hyper::Error>().is_some() {
314        span_warn!("request error", "{}", ServerError(error) => {
315            while let Some(source) = error.source() {
316                error = source;
317                warn!("{}", ServerError(error));
318            }
319        });
320    } else {
321        span_error!("server error", "{}", ServerError(error) => {
322            while let Some(source) = error.source() {
323                error = source;
324                error!("{}", ServerError(error));
325            }
326        });
327    }
328}
329
330#[doc(hidden)]
331pub mod display_hack_impl {
332    use super::*;
333    use crate::util::Formatter;
334
335    /// The *magic*.
336    ///
337    /// This type implements a `display()` method using an internal `T` that is
338    /// either `fmt::Display` _or_ `fmt::Debug`, using the former when
339    /// available. It does so by using a "specialization" hack: it has a blanket
340    /// DefaultDisplay trait impl for all types that are `fmt::Debug` and a
341    /// "specialized" inherent impl for all types that are `fmt::Display`.
342    ///
343    /// As long as `T: Display`, the "specialized" impl is what Rust will
344    /// resolve `DisplayHack(v).display()` to when `T: fmt::Display` as it is an
345    /// inherent impl. Otherwise, Rust will fall back to the blanket impl.
346    pub struct DisplayHack<T: ?Sized>(pub T);
347
348    pub trait DefaultDisplay {
349        fn display(&self) -> impl fmt::Display;
350    }
351
352    /// Blanket implementation for `T: Debug`. This is what Rust will resolve
353    /// `DisplayHack<T>::display` to when `T: Debug`.
354    impl<T: fmt::Debug + ?Sized> DefaultDisplay for DisplayHack<T> {
355        #[inline(always)]
356        fn display(&self) -> impl fmt::Display {
357            Formatter(|f| fmt::Debug::fmt(&self.0, f))
358        }
359    }
360
361    /// "Specialized" implementation for `T: Display`. This is what Rust will
362    /// resolve `DisplayHack<T>::display` to when `T: Display`.
363    impl<T: fmt::Display + fmt::Debug + ?Sized> DisplayHack<T> {
364        #[inline(always)]
365        pub fn display(&self) -> impl fmt::Display + '_ {
366            Formatter(|f| fmt::Display::fmt(&self.0, f))
367        }
368    }
369}
370
371#[doc(hidden)]
372#[macro_export]
373macro_rules! display_hack {
374    ($v:expr) => ({
375        #[allow(unused_imports)]
376        use $crate::error::display_hack_impl::{DisplayHack, DefaultDisplay as _};
377
378        #[allow(unreachable_code)]
379        DisplayHack($v).display()
380    })
381}
382
383#[doc(hidden)]
384pub use display_hack as display_hack;