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;