via/
error.rs

1//! Conviently work with errors that may occur in an application.
2//!
3
4use http::header::CONTENT_TYPE;
5use http::StatusCode;
6use serde::ser::SerializeStruct;
7use serde::{Serialize, Serializer};
8use std::error::Error as StdError;
9use std::fmt::{self, Debug, Display, Formatter};
10use std::io;
11
12use crate::response::Response;
13
14/// A type alias for a boxed
15/// [`Error`](std::error::Error)
16/// that is `Send + Sync`.
17///
18pub type DynError = Box<dyn std::error::Error + Send + Sync>;
19
20/// An error type that can act as a specialized version of a
21/// [`ResponseBuilder`](crate::response::ResponseBuilder).
22///
23#[derive(Debug)]
24pub struct Error {
25    as_json: bool,
26    status: StatusCode,
27    message: Option<String>,
28    error: DynError,
29}
30
31/// A serialized representation of an individual error.
32///
33struct SerializeError<'a> {
34    message: &'a str,
35}
36
37impl Error {
38    /// Create a new [`Error`] from the provided source.
39    ///
40    pub fn new(source: DynError) -> Self {
41        Self::internal_server_error(source)
42    }
43
44    /// Create a new [`Error`] from the provided [`io::Error`]. The status code
45    /// of the error returned will correspond to `source.kind()`.
46    ///
47    pub fn from_io(source: io::Error) -> Self {
48        match source.kind() {
49            io::ErrorKind::AlreadyExists => {
50                // Implies a resource already exists.
51                Self::conflict(Box::new(source))
52            }
53
54            io::ErrorKind::BrokenPipe
55            | io::ErrorKind::ConnectionReset
56            | io::ErrorKind::ConnectionAborted => {
57                // Signals a broken connection.
58                Self::bad_gateway(Box::new(source))
59            }
60
61            io::ErrorKind::ConnectionRefused => {
62                // Suggests the service is not ready or available.
63                Self::service_unavailable(Box::new(source))
64            }
65
66            io::ErrorKind::InvalidData | io::ErrorKind::InvalidInput => {
67                // Generally indicates a malformed request.
68                Self::bad_request(Box::new(source))
69            }
70
71            io::ErrorKind::NotFound => {
72                // Indicates a missing resource.
73                Self::not_found(Box::new(source))
74            }
75
76            io::ErrorKind::PermissionDenied => {
77                // Implies restricted access.
78                Self::forbidden(Box::new(source))
79            }
80
81            io::ErrorKind::TimedOut => {
82                // Implies an upstream service timeout.
83                Self::gateway_timeout(Box::new(source))
84            }
85
86            _ => {
87                // Any other kind is treated as an internal server error.
88                Self::internal_server_error(Box::new(source))
89            }
90        }
91    }
92
93    /// Returns a new [`Error`] that will be serialized to JSON when converted to
94    /// a [`Response`].
95    ///
96    pub fn as_json(self) -> Self {
97        Self {
98            as_json: true,
99            ..self
100        }
101    }
102
103    /// Returns a new [`Error`] that will eagerly map the message that will be
104    /// included in the body of the [`Response`] that will be generated from
105    /// self by calling the provided closure. If the closure returns `None`,
106    /// the message will be left unchanged.
107    ///
108    /// # Example
109    ///
110    /// ```
111    /// use via::middleware::error_boundary;
112    /// use via::{Next, Request};
113    ///
114    /// type Error = Box<dyn std::error::Error + Send + Sync>;
115    ///
116    /// #[tokio::main(flavor = "current_thread")]
117    /// async fn main() -> Result<(), Error> {
118    ///     let mut app = via::app(());
119    ///
120    ///     // Add an `ErrorBoundary` middleware to the route tree that maps
121    ///     // errors that occur in subsequent middleware by calling the `redact`
122    ///     // function.
123    ///     app.include(error_boundary::map(|_, error| {
124    ///         error.redact(|message| {
125    ///             if message.contains("password") {
126    ///                 // If password is even mentioned in the error, return an
127    ///                 // opaque message instead. You'll probably want something
128    ///                 // more sophisticated than this in production.
129    ///                 Some("An error occurred...".to_owned())
130    ///             } else {
131    ///                 // Otherwise, use the existing error message.
132    ///                 None
133    ///             }
134    ///         })
135    ///     }));
136    ///
137    ///     Ok(())
138    /// }
139    /// ```
140    ///
141    pub fn redact(self, f: impl FnOnce(&str) -> Option<String>) -> Self {
142        match &self.message {
143            Some(message) => match f(message) {
144                Some(redacted) => self.with_message(redacted),
145                None => self,
146            },
147            None => {
148                let message = self.error.to_string();
149                let redacted = f(&message).unwrap_or(message);
150
151                self.with_message(redacted)
152            }
153        }
154    }
155
156    /// Returns a new [`Error`] that will use the provided message instead of
157    /// calling the [`Display`] implementation of the error source when
158    /// converted to a [`Response`].
159    ///
160    pub fn with_message(self, message: String) -> Self {
161        Self {
162            message: Some(message),
163            ..self
164        }
165    }
166
167    /// Sets the status code of that will be used when converted to a
168    /// [`Response`].
169    ///
170    pub fn with_status(self, status: StatusCode) -> Self {
171        // Placeholder for tracing...
172        // Warn if the status code is not in the 4xx or 5xx range.
173        Self { status, ..self }
174    }
175
176    /// Returns a new [`Error`] that will use the canonical reason phrase of the
177    /// status code as the message included in the [`Response`] body that is
178    /// generated when converted to a [`Response`].
179    ///
180    /// # Example
181    ///
182    /// ```
183    /// use via::middleware::error_boundary;
184    /// use via::{Next, Request};
185    ///
186    /// type Error = Box<dyn std::error::Error + Send + Sync>;
187    ///
188    /// #[tokio::main(flavor = "current_thread")]
189    /// async fn main() -> Result<(), Error> {
190    ///     let mut app = via::app(());
191    ///
192    ///     // Add an `ErrorBoundary` middleware to the route tree that maps
193    ///     // errors that occur in subsequent middleware by calling the
194    ///     // `use_canonical_reason` function.
195    ///     app.include(error_boundary::map(|_, error| {
196    ///         // Prevent error messages that occur in downstream middleware from
197    ///         // leaking into the response body by using the reason phrase of
198    ///         // the status code associated with the error.
199    ///         error.use_canonical_reason()
200    ///     }));
201    ///
202    ///     Ok(())
203    /// }
204    /// ```
205    ///
206    pub fn use_canonical_reason(self) -> Self {
207        if let Some(reason) = self.status.canonical_reason() {
208            self.with_message(reason.to_owned())
209        } else {
210            // Placeholder for tracing...
211            self.with_message("An error occurred".to_owned())
212        }
213    }
214
215    /// Returns an iterator over the sources of this error.
216    ///
217    pub fn iter(&self) -> impl Iterator<Item = &dyn StdError> {
218        Some(self.source()).into_iter()
219    }
220
221    /// Returns a reference to the error source.
222    ///
223    pub fn source(&self) -> &(dyn StdError + 'static) {
224        &*self.error
225    }
226}
227
228impl Error {
229    /// Returns a new [`Error`] from the provided source that will generate a
230    /// [`Response`] with a `400 Bad Request` status.
231    ///
232    pub fn bad_request(source: DynError) -> Self {
233        Self::new_with_status(StatusCode::BAD_REQUEST, source)
234    }
235
236    /// Returns a new [`Error`] from the provided source that will generate a
237    /// [`Response`] with a `401 Unauthorized` status.
238    ///
239    pub fn unauthorized(source: DynError) -> Self {
240        Self::new_with_status(StatusCode::UNAUTHORIZED, source)
241    }
242
243    /// Returns a new [`Error`] from the provided source that will generate a
244    /// [`Response`] with a `402 Payment Required` status.
245    ///
246    pub fn payment_required(source: DynError) -> Self {
247        Self::new_with_status(StatusCode::PAYMENT_REQUIRED, source)
248    }
249
250    /// Returns a new [`Error`] from the provided source that will generate a
251    /// [`Response`] with a `403 Forbidden` status.
252    ///
253    pub fn forbidden(source: DynError) -> Self {
254        Self::new_with_status(StatusCode::FORBIDDEN, source)
255    }
256
257    /// Returns a new [`Error`] from the provided source that will generate a
258    /// [`Response`] with a `404 Not Found` status.
259    ///
260    pub fn not_found(source: DynError) -> Self {
261        Self::new_with_status(StatusCode::NOT_FOUND, source)
262    }
263
264    /// Returns a new [`Error`] from the provided source that will generate a
265    /// [`Response`] with a `405 Method Not Allowed` status.
266    ///
267    pub fn method_not_allowed(source: DynError) -> Self {
268        Self::new_with_status(StatusCode::METHOD_NOT_ALLOWED, source)
269    }
270
271    /// Returns a new [`Error`] from the provided source that will generate a
272    /// [`Response`] with a `406 Not Acceptable` status.
273    ///
274    pub fn not_acceptable(source: DynError) -> Self {
275        Self::new_with_status(StatusCode::NOT_ACCEPTABLE, source)
276    }
277
278    /// Returns a new [`Error`] from the provided source that will generate a
279    /// [`Response`] with a `407 Proxy Authentication Required` status.
280    ///
281    pub fn proxy_authentication_required(source: DynError) -> Self {
282        Self::new_with_status(StatusCode::PROXY_AUTHENTICATION_REQUIRED, source)
283    }
284
285    /// Returns a new [`Error`] from the provided source that will generate a
286    /// [`Response`] with a `408 Request Timeout` status.
287    ///
288    pub fn request_timeout(source: DynError) -> Self {
289        Self::new_with_status(StatusCode::REQUEST_TIMEOUT, source)
290    }
291
292    /// Returns a new [`Error`] from the provided source that will generate a
293    /// [`Response`] with a `409 Conflict` status.
294    ///
295    pub fn conflict(source: DynError) -> Self {
296        Self::new_with_status(StatusCode::CONFLICT, source)
297    }
298
299    /// Returns a new [`Error`] from the provided source that will generate a
300    /// [`Response`] with a `410 Gone` status.
301    ///
302    pub fn gone(source: DynError) -> Self {
303        Self::new_with_status(StatusCode::GONE, source)
304    }
305
306    /// Returns a new [`Error`] from the provided source that will generate a
307    /// [`Response`] with a `411 Length Required` status.
308    ///
309    pub fn length_required(source: DynError) -> Self {
310        Self::new_with_status(StatusCode::LENGTH_REQUIRED, source)
311    }
312
313    /// Returns a new [`Error`] from the provided source that will generate a
314    /// [`Response`] with a `412 Precondition Failed` status.
315    ///
316    pub fn precondition_failed(source: DynError) -> Self {
317        Self::new_with_status(StatusCode::PRECONDITION_FAILED, source)
318    }
319
320    /// Returns a new [`Error`] from the provided source that will generate a
321    /// [`Response`] with a `413 Payload Too Large` status.
322    ///
323    pub fn payload_too_large(source: DynError) -> Self {
324        Self::new_with_status(StatusCode::PAYLOAD_TOO_LARGE, source)
325    }
326
327    /// Returns a new [`Error`] from the provided source that will generate a
328    /// [`Response`] with a `414 URI Too Long` status.
329    ///
330    pub fn uri_too_long(source: DynError) -> Self {
331        Self::new_with_status(StatusCode::URI_TOO_LONG, source)
332    }
333
334    /// Returns a new [`Error`] from the provided source that will generate a
335    /// [`Response`] with a `415 Unsupported Media Type` status.
336    ///
337    pub fn unsupported_media_type(source: DynError) -> Self {
338        Self::new_with_status(StatusCode::UNSUPPORTED_MEDIA_TYPE, source)
339    }
340
341    /// Returns a new [`Error`] from the provided source that will generate a
342    /// [`Response`] with a `416 Range Not Satisfiable` status.
343    ///
344    pub fn range_not_satisfiable(source: DynError) -> Self {
345        Self::new_with_status(StatusCode::RANGE_NOT_SATISFIABLE, source)
346    }
347
348    /// Returns a new [`Error`] from the provided source that will generate a
349    /// [`Response`] with a `417 Expectation Failed` status.
350    ///
351    pub fn expectation_failed(source: DynError) -> Self {
352        Self::new_with_status(StatusCode::EXPECTATION_FAILED, source)
353    }
354
355    /// Returns a new [`Error`] from the provided source that will generate a
356    /// [`Response`] with a `418 I'm a teapot` status.
357    ///
358    pub fn im_a_teapot(source: DynError) -> Self {
359        Self::new_with_status(StatusCode::IM_A_TEAPOT, source)
360    }
361
362    /// Returns a new [`Error`] from the provided source that will generate a
363    /// [`Response`] with a `421 Misdirected Request` status.
364    ///
365    pub fn misdirected_request(source: DynError) -> Self {
366        Self::new_with_status(StatusCode::MISDIRECTED_REQUEST, source)
367    }
368
369    /// Returns a new [`Error`] from the provided source that will generate a
370    /// [`Response`] with a `422 Unprocessable Entity` status.
371    ///
372    pub fn unprocessable_entity(source: DynError) -> Self {
373        Self::new_with_status(StatusCode::UNPROCESSABLE_ENTITY, source)
374    }
375
376    /// Returns a new [`Error`] from the provided source that will generate a
377    /// [`Response`] with a `423 Locked` status.
378    ///
379    pub fn locked(source: DynError) -> Self {
380        Self::new_with_status(StatusCode::LOCKED, source)
381    }
382
383    /// Returns a new [`Error`] from the provided source that will generate a
384    /// [`Response`] with a `424 Failed Dependency` status.
385    ///
386    pub fn failed_dependency(source: DynError) -> Self {
387        Self::new_with_status(StatusCode::FAILED_DEPENDENCY, source)
388    }
389
390    /// Returns a new [`Error`] from the provided source that will generate a
391    /// [`Response`] with a `426 Upgrade Required` status.
392    ///
393    pub fn upgrade_required(source: DynError) -> Self {
394        Self::new_with_status(StatusCode::UPGRADE_REQUIRED, source)
395    }
396
397    /// Returns a new [`Error`] from the provided source that will generate a
398    /// [`Response`] with a `428 Precondition Required` status.
399    ///
400    pub fn precondition_required(source: DynError) -> Self {
401        Self::new_with_status(StatusCode::PRECONDITION_REQUIRED, source)
402    }
403
404    /// Returns a new [`Error`] from the provided source that will generate a
405    /// [`Response`] with a `429 Too Many Requests` status.
406    ///
407    pub fn too_many_requests(source: DynError) -> Self {
408        Self::new_with_status(StatusCode::TOO_MANY_REQUESTS, source)
409    }
410
411    /// Returns a new [`Error`] from the provided source that will generate a
412    /// [`Response`] with a `431 Request Header Fields Too Large` status.
413    ///
414    pub fn request_header_fields_too_large(source: DynError) -> Self {
415        Self::new_with_status(StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE, source)
416    }
417
418    /// Returns a new [`Error`] from the provided source that will generate a
419    /// [`Response`] with a `451 Unavailable For Legal Reasons` status.
420    ///
421    pub fn unavailable_for_legal_reasons(source: DynError) -> Self {
422        Self::new_with_status(StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS, source)
423    }
424
425    /// Returns a new [`Error`] from the provided source that will generate a
426    /// [`Response`] with a `500 Internal Server Error` status.
427    ///
428    pub fn internal_server_error(source: DynError) -> Self {
429        Self::new_with_status(StatusCode::INTERNAL_SERVER_ERROR, source)
430    }
431
432    /// Returns a new [`Error`] from the provided source that will generate a
433    /// [`Response`] with a `501 Not Implemented` status.
434    ///
435    pub fn not_implemented(source: DynError) -> Self {
436        Self::new_with_status(StatusCode::NOT_IMPLEMENTED, source)
437    }
438
439    /// Returns a new [`Error`] from the provided source that will generate a
440    /// [`Response`] with a `502 Bad Gateway` status.
441    ///
442    pub fn bad_gateway(source: DynError) -> Self {
443        Self::new_with_status(StatusCode::BAD_GATEWAY, source)
444    }
445
446    /// Returns a new [`Error`] from the provided source that will generate a
447    /// [`Response`] with a `503 Service Unavailable` status.
448    ///
449    pub fn service_unavailable(source: DynError) -> Self {
450        Self::new_with_status(StatusCode::SERVICE_UNAVAILABLE, source)
451    }
452
453    /// Returns a new [`Error`] from the provided source that will generate a
454    /// [`Response`] with a `504 Gateway Timeout` status.
455    ///
456    pub fn gateway_timeout(source: DynError) -> Self {
457        Self::new_with_status(StatusCode::GATEWAY_TIMEOUT, source)
458    }
459
460    /// Returns a new [`Error`] from the provided source that will generate a
461    /// [`Response`] with a `505 HTTP Version Not Supported` status.
462    ///
463    pub fn http_version_not_supported(source: DynError) -> Self {
464        Self::new_with_status(StatusCode::HTTP_VERSION_NOT_SUPPORTED, source)
465    }
466
467    /// Returns a new [`Error`] from the provided source that will generate a
468    /// [`Response`] with a `506 Variant Also Negotiates` status.
469    ///
470    pub fn variant_also_negotiates(source: DynError) -> Self {
471        Self::new_with_status(StatusCode::VARIANT_ALSO_NEGOTIATES, source)
472    }
473
474    /// Returns a new [`Error`] from the provided source that will generate a
475    /// [`Response`] with a `507 Insufficient Storage` status.
476    ///
477    pub fn insufficient_storage(source: DynError) -> Self {
478        Self::new_with_status(StatusCode::INSUFFICIENT_STORAGE, source)
479    }
480
481    /// Returns a new [`Error`] from the provided source that will generate a
482    /// [`Response`] with a `508 Loop Detected` status.
483    ///
484    pub fn loop_detected(source: DynError) -> Self {
485        Self::new_with_status(StatusCode::LOOP_DETECTED, source)
486    }
487
488    /// Returns a new [`Error`] from the provided source that will generate a
489    /// [`Response`] with a `510 Not Extended` status.
490    ///
491    pub fn not_extended(source: DynError) -> Self {
492        Self::new_with_status(StatusCode::NOT_EXTENDED, source)
493    }
494
495    /// Returns a new [`Error`] from the provided source that will generate a
496    /// [`Response`] with a `511 Network Authentication Required` status.
497    ///
498    pub fn network_authentication_required(source: DynError) -> Self {
499        Self::new_with_status(StatusCode::NETWORK_AUTHENTICATION_REQUIRED, source)
500    }
501}
502
503impl Error {
504    #[inline]
505    fn new_with_status(status: StatusCode, source: DynError) -> Self {
506        Self {
507            as_json: false,
508            message: None,
509            error: source,
510            status,
511        }
512    }
513}
514
515impl Display for Error {
516    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
517        Display::fmt(&self.error, f)
518    }
519}
520
521impl<T> From<T> for Error
522where
523    T: StdError + Send + Sync + 'static,
524{
525    fn from(source: T) -> Self {
526        Self {
527            as_json: false,
528            message: None,
529            status: StatusCode::INTERNAL_SERVER_ERROR,
530            error: Box::new(source),
531        }
532    }
533}
534
535impl From<Error> for Response {
536    fn from(error: Error) -> Response {
537        let mut respond_with_json = error.as_json;
538
539        loop {
540            if !respond_with_json {
541                let mut response = Response::new(error.to_string().into());
542
543                *response.status_mut() = error.status;
544                break response;
545            }
546
547            match serde_json::to_string(&error)
548                .map_err(Error::from)
549                .and_then(|json| {
550                    Response::build()
551                        .status(error.status)
552                        .header(CONTENT_TYPE, "application/json; charset=utf-8")
553                        .body(json)
554                }) {
555                Ok(response) => break response,
556                Err(error) => {
557                    respond_with_json = false;
558                    // Placeholder for tracing...
559                    if cfg!(debug_assertions) {
560                        eprintln!("Error: {}", error);
561                    }
562                }
563            }
564        }
565    }
566}
567
568impl Serialize for Error {
569    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
570    where
571        S: Serializer,
572    {
573        let mut state = serializer.serialize_struct("Error", 1)?;
574
575        // Serialize the error as a single element array containing an object with
576        // a message field. We do this to provide compatibility with popular API
577        // specification formats like GraphQL and JSON:API.
578        if let Some(message) = &self.message {
579            let errors = [SerializeError { message }];
580            state.serialize_field("errors", &errors)?;
581        } else {
582            let message = self.error.to_string();
583            let errors = [SerializeError { message: &message }];
584
585            state.serialize_field("errors", &errors)?;
586        }
587
588        state.end()
589    }
590}
591
592impl Serialize for SerializeError<'_> {
593    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
594    where
595        S: Serializer,
596    {
597        let mut state = serializer.serialize_struct("ErrorMessage", 1)?;
598
599        state.serialize_field("message", &self.message)?;
600        state.end()
601    }
602}