Skip to main content

salvo_core/
error.rs

1//! Error types and handling for Salvo.
2//!
3//! This module provides the core error type [`Error`] used throughout Salvo,
4//! along with conversions from common error types and rendering support.
5//!
6//! # Error Type
7//!
8//! The [`Error`] enum wraps various error types that can occur during request
9//! handling:
10//!
11//! - HTTP parsing errors
12//! - I/O errors
13//! - JSON serialization errors
14//! - HTTP status errors
15//! - Custom errors via [`BoxedError`]
16//!
17//! # Error Handling in Handlers
18//!
19//! Handlers can return `Result<T, Error>` or `Result<T, impl Writer>`:
20//!
21//! ```ignore
22//! use salvo_core::prelude::*;
23//!
24//! #[handler]
25//! async fn example() -> Result<&'static str, salvo_core::Error> {
26//!     // Errors are automatically converted and rendered
27//!     Ok("Success")
28//! }
29//! ```
30//!
31//! # Custom Error Types
32//!
33//! Custom error types can be converted to [`Error`] using the [`other`](Error::other)
34//! method or by implementing `From`:
35//!
36//! ```ignore
37//! use salvo_core::Error;
38//!
39//! let custom_error = Error::other(MyCustomError::new("something failed"));
40//! ```
41//!
42//! # Integration with anyhow and eyre
43//!
44//! With the `anyhow` or `eyre` features enabled, errors from those crates
45//! are automatically convertible to [`Error`] and will render as 500 Internal
46//! Server Error responses.
47
48use std::convert::Infallible;
49use std::error::Error as StdError;
50use std::fmt::{self, Display, Formatter};
51use std::io::Error as IoError;
52
53use crate::http::{ParseError, StatusError};
54use crate::{Response, Scribe};
55
56/// A boxed error type for dynamic error handling.
57///
58/// This type alias provides a convenient way to work with any error type
59/// that implements `std::error::Error + Send + Sync`.
60pub type BoxedError = Box<dyn StdError + Send + Sync>;
61
62/// The main error type used throughout Salvo.
63///
64/// This enum encompasses all error types that can occur during request processing,
65/// from low-level I/O errors to HTTP-specific errors.
66///
67/// # Rendering
68///
69/// `Error` implements [`Scribe`], which means it can be rendered directly to a
70/// response. Most error variants render as a 500 Internal Server Error, except
71/// for `HttpStatus` which uses the status code from the contained [`StatusError`].
72///
73/// # Error Conversion
74///
75/// Common error types automatically convert to `Error`:
76///
77/// ```ignore
78/// use salvo_core::Error;
79///
80/// // I/O errors
81/// let io_err: Error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found").into();
82///
83/// // JSON errors
84/// let json_err: Error = serde_json::from_str::<()>("invalid").unwrap_err().into();
85///
86/// // Custom errors
87/// let custom: Error = Error::other(MyError);
88/// ```
89///
90/// # Feature-Gated Variants
91///
92/// Some variants are only available with specific features:
93/// - `quinn`: HTTP/3 error variants
94/// - `anyhow`: `anyhow::Error` support
95/// - `eyre`: `eyre::Report` support
96#[derive(Debug)]
97#[non_exhaustive]
98pub enum Error {
99    /// Error from the hyper HTTP library.
100    ///
101    /// These errors typically occur during low-level HTTP processing.
102    Hyper(hyper::Error),
103    /// HTTP parsing error.
104    ///
105    /// Occurs when request data cannot be parsed correctly.
106    HttpParse(ParseError),
107    /// HTTP status error with an associated status code.
108    ///
109    /// Unlike other variants, this error uses its contained status code
110    /// when rendering to a response.
111    HttpStatus(StatusError),
112    /// Standard I/O error.
113    ///
114    /// Common for file operations and network issues.
115    Io(IoError),
116    /// JSON serialization/deserialization error.
117    SerdeJson(serde_json::Error),
118    /// Invalid URI error.
119    ///
120    /// Occurs when a URI cannot be parsed.
121    InvalidUri(http::uri::InvalidUri),
122    /// HTTP/3 connection error (requires `quinn` feature).
123    #[cfg(feature = "quinn")]
124    #[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
125    H3Connection(salvo_http3::error::ConnectionError),
126    /// HTTP/3 stream error (requires `quinn` feature).
127    #[cfg(feature = "quinn")]
128    #[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
129    H3Stream(salvo_http3::error::StreamError),
130    /// HTTP/3 datagram send error (requires `quinn` feature).
131    #[cfg(feature = "quinn")]
132    #[cfg_attr(docsrs, doc(cfg(feature = "quinn")))]
133    H3SendDatagram(h3_datagram::datagram_handler::SendDatagramError),
134    /// Error from the anyhow crate (requires `anyhow` feature).
135    #[cfg(feature = "anyhow")]
136    #[cfg_attr(docsrs, doc(cfg(feature = "anyhow")))]
137    Anyhow(anyhow::Error),
138    /// Error from the eyre crate (requires `eyre` feature).
139    #[cfg(feature = "eyre")]
140    #[cfg_attr(docsrs, doc(cfg(feature = "eyre")))]
141    Eyre(eyre::Report),
142    /// Any other error type wrapped as a boxed trait object.
143    ///
144    /// Use [`Error::other`] to create this variant from any error type.
145    Other(BoxedError),
146}
147
148impl Error {
149    /// Creates an `Error` from any error type.
150    ///
151    /// This is useful for wrapping custom error types that don't have
152    /// a dedicated variant in the `Error` enum.
153    ///
154    /// # Example
155    ///
156    /// ```ignore
157    /// use salvo_core::Error;
158    ///
159    /// #[derive(Debug)]
160    /// struct MyError(String);
161    ///
162    /// impl std::fmt::Display for MyError {
163    ///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164    ///         write!(f, "{}", self.0)
165    ///     }
166    /// }
167    ///
168    /// impl std::error::Error for MyError {}
169    ///
170    /// let error = Error::other(MyError("something went wrong".into()));
171    /// ```
172    #[inline]
173    pub fn other(error: impl Into<BoxedError>) -> Self {
174        Self::Other(error.into())
175    }
176}
177impl Display for Error {
178    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
179        match self {
180            Self::Hyper(e) => Display::fmt(e, f),
181            Self::HttpParse(e) => Display::fmt(e, f),
182            Self::HttpStatus(e) => Display::fmt(e, f),
183            Self::Io(e) => Display::fmt(e, f),
184            Self::SerdeJson(e) => Display::fmt(e, f),
185            Self::InvalidUri(e) => Display::fmt(e, f),
186            #[cfg(feature = "quinn")]
187            Self::H3Connection(e) => Display::fmt(e, f),
188            #[cfg(feature = "quinn")]
189            Self::H3Stream(e) => Display::fmt(e, f),
190            #[cfg(feature = "quinn")]
191            Self::H3SendDatagram(e) => Display::fmt(e, f),
192            #[cfg(feature = "anyhow")]
193            Self::Anyhow(e) => Display::fmt(e, f),
194            #[cfg(feature = "eyre")]
195            Self::Eyre(e) => Display::fmt(e, f),
196            Self::Other(e) => Display::fmt(e, f),
197        }
198    }
199}
200
201impl StdError for Error {}
202
203impl From<Infallible> for Error {
204    #[inline]
205    fn from(infallible: Infallible) -> Self {
206        match infallible {}
207    }
208}
209impl From<hyper::Error> for Error {
210    #[inline]
211    fn from(e: hyper::Error) -> Self {
212        Self::Hyper(e)
213    }
214}
215impl From<ParseError> for Error {
216    #[inline]
217    fn from(d: ParseError) -> Self {
218        Self::HttpParse(d)
219    }
220}
221impl From<StatusError> for Error {
222    #[inline]
223    fn from(e: StatusError) -> Self {
224        Self::HttpStatus(e)
225    }
226}
227impl From<IoError> for Error {
228    #[inline]
229    fn from(e: IoError) -> Self {
230        Self::Io(e)
231    }
232}
233impl From<http::uri::InvalidUri> for Error {
234    #[inline]
235    fn from(e: http::uri::InvalidUri) -> Self {
236        Self::InvalidUri(e)
237    }
238}
239impl From<serde_json::Error> for Error {
240    #[inline]
241    fn from(e: serde_json::Error) -> Self {
242        Self::SerdeJson(e)
243    }
244}
245cfg_feature! {
246    #![feature = "quinn"]
247    impl From<salvo_http3::error::ConnectionError> for Error {
248        #[inline]
249        fn from(e: salvo_http3::error::ConnectionError) -> Self {
250            Self::H3Connection(e)
251        }
252    }
253    impl From<salvo_http3::error::StreamError> for Error {
254        #[inline]
255        fn from(e: salvo_http3::error::StreamError) -> Self {
256            Self::H3Stream(e)
257        }
258    }
259    impl From<h3_datagram::datagram_handler::SendDatagramError> for Error {
260        #[inline]
261        fn from(e: h3_datagram::datagram_handler::SendDatagramError) -> Self {
262            Self::H3SendDatagram(e)
263        }
264    }
265}
266cfg_feature! {
267    #![feature = "anyhow"]
268    impl From<anyhow::Error> for Error {
269        #[inline]
270        fn from(e: anyhow::Error) -> Self {
271            Self::Anyhow(e)
272        }
273    }
274}
275cfg_feature! {
276    #![feature = "eyre"]
277    impl From<eyre::Report> for Error {
278        #[inline]
279        fn from(e: eyre::Report) -> Self {
280            Self::Eyre(e)
281        }
282    }
283}
284
285impl From<BoxedError> for Error {
286    #[inline]
287    fn from(e: BoxedError) -> Self {
288        Self::Other(e)
289    }
290}
291
292impl Scribe for Error {
293    fn render(self, res: &mut Response) {
294        let status_error = match self {
295            Self::HttpStatus(e) => e,
296            _ => StatusError::internal_server_error().cause(self),
297        };
298        res.render(status_error);
299    }
300}
301cfg_feature! {
302    #![feature = "anyhow"]
303    impl Scribe for anyhow::Error {
304        #[inline]
305        fn render(self, res: &mut Response) {
306            tracing::error!(error = ?self, "anyhow error occurred");
307            res.render(StatusError::internal_server_error().origin(self));
308        }
309    }
310}
311cfg_feature! {
312    #![feature = "eyre"]
313    impl Scribe for eyre::Report {
314        #[inline]
315        fn render(self, res: &mut Response) {
316            tracing::error!(error = ?self, "eyre error occurred");
317            res.render(StatusError::internal_server_error().cause(self));
318        }
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    use std::str::FromStr;
325
326    use super::*;
327    use crate::http::*;
328    use crate::{Depot, Writer};
329
330    #[tokio::test]
331    #[cfg(feature = "anyhow")]
332    async fn test_anyhow() {
333        let mut req = Request::default();
334        let mut res = Response::default();
335        let mut depot = Depot::new();
336        let e: anyhow::Error = anyhow::anyhow!("detail message");
337        e.write(&mut req, &mut depot, &mut res).await;
338        assert_eq!(res.status_code, Some(StatusCode::INTERNAL_SERVER_ERROR));
339    }
340
341    #[tokio::test]
342    #[cfg(feature = "eyre")]
343    async fn test_eyre() {
344        let mut req = Request::default();
345        let mut res = Response::default();
346        let mut depot = Depot::new();
347        let e: eyre::Report = eyre::Report::msg("detail message");
348        e.write(&mut req, &mut depot, &mut res).await;
349        assert_eq!(res.status_code, Some(StatusCode::INTERNAL_SERVER_ERROR));
350    }
351
352    #[tokio::test]
353    async fn test_error() {
354        let mut req = Request::default();
355        let mut res = Response::default();
356        let mut depot = Depot::new();
357
358        let e = Error::Other(Box::new(std::io::Error::new(
359            std::io::ErrorKind::Other,
360            "detail message",
361        )));
362        e.write(&mut req, &mut depot, &mut res).await;
363        assert_eq!(res.status_code, Some(StatusCode::INTERNAL_SERVER_ERROR));
364    }
365
366    #[test]
367    fn test_error_from() {
368        use std::io;
369
370        let err: Error = io::Error::new(io::ErrorKind::Other, "oh no!").into();
371        assert!(matches!(err, Error::Io(_)));
372
373        let err: Error = ParseError::ParseFromStr.into();
374        assert!(matches!(err, Error::HttpParse(_)));
375
376        let err: Error = StatusError::bad_request().into();
377        assert!(matches!(err, Error::HttpStatus(_)));
378
379        let err: Error = serde_json::from_str::<serde_json::Value>("{")
380            .unwrap_err()
381            .into();
382        assert!(matches!(err, Error::SerdeJson(_)));
383
384        let err: Error = http::Uri::from_str("ht tp://host.com").unwrap_err().into();
385        assert!(matches!(err, Error::InvalidUri(_)));
386
387        let err: Error = Error::other(Box::new(std::io::Error::new(
388            std::io::ErrorKind::Other,
389            "custom error",
390        )));
391        assert!(matches!(err, Error::Other(_)));
392    }
393
394    #[test]
395    fn test_error_display() {
396        use std::io;
397
398        let err: Error = io::Error::new(io::ErrorKind::Other, "io error").into();
399        assert_eq!(format!("{}", err), "io error");
400
401        let err: Error = ParseError::ParseFromStr.into();
402        assert_eq!(format!("{}", err), "Parse error when parse from str.");
403
404        let err: Error = StatusError::bad_request().brief("status error").into();
405        assert!(format!("{}", err).contains("status error"));
406    }
407
408    #[tokio::test]
409    async fn test_error_scribe() {
410        let mut req = Request::default();
411        let mut res = Response::default();
412        let mut depot = Depot::new();
413
414        let e = Error::from(StatusError::bad_request());
415        e.write(&mut req, &mut depot, &mut res).await;
416        assert_eq!(res.status_code, Some(StatusCode::BAD_REQUEST));
417
418        let mut res = Response::default();
419        let e = std::io::Error::new(std::io::ErrorKind::Other, "io error");
420        Error::from(e).write(&mut req, &mut depot, &mut res).await;
421        assert_eq!(res.status_code, Some(StatusCode::INTERNAL_SERVER_ERROR));
422    }
423}