rocket_community/
error.rs

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