actix_web/error/
response_error.rs

1//! `ResponseError` trait and foreign impls.
2
3use std::{
4    convert::Infallible,
5    error::Error as StdError,
6    fmt,
7    io::{self, Write as _},
8};
9
10use actix_http::Response;
11use bytes::BytesMut;
12
13use crate::{
14    body::BoxBody,
15    error::{downcast_dyn, downcast_get_type_id},
16    helpers,
17    http::{
18        header::{self, TryIntoHeaderValue},
19        StatusCode,
20    },
21    HttpResponse,
22};
23
24/// Errors that can generate responses.
25// TODO: flesh out documentation
26pub trait ResponseError: fmt::Debug + fmt::Display {
27    /// Returns appropriate status code for error.
28    ///
29    /// A 500 Internal Server Error is used by default. If [error_response](Self::error_response) is
30    /// also implemented and does not call `self.status_code()`, then this will not be used.
31    fn status_code(&self) -> StatusCode {
32        StatusCode::INTERNAL_SERVER_ERROR
33    }
34
35    /// Creates full response for error.
36    ///
37    /// By default, the generated response uses a 500 Internal Server Error status code, a
38    /// `Content-Type` of `text/plain`, and the body is set to `Self`'s `Display` impl.
39    fn error_response(&self) -> HttpResponse<BoxBody> {
40        let mut res = HttpResponse::new(self.status_code());
41
42        let mut buf = BytesMut::new();
43        let _ = write!(helpers::MutWriter(&mut buf), "{}", self);
44
45        let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
46        res.headers_mut().insert(header::CONTENT_TYPE, mime);
47
48        res.set_body(BoxBody::new(buf))
49    }
50
51    downcast_get_type_id!();
52}
53
54downcast_dyn!(ResponseError);
55
56impl ResponseError for Box<dyn StdError + 'static> {}
57
58impl ResponseError for Infallible {
59    fn status_code(&self) -> StatusCode {
60        match *self {}
61    }
62    fn error_response(&self) -> HttpResponse<BoxBody> {
63        match *self {}
64    }
65}
66
67#[cfg(feature = "openssl")]
68impl ResponseError for actix_tls::accept::openssl::reexports::Error {}
69
70impl ResponseError for serde::de::value::Error {
71    fn status_code(&self) -> StatusCode {
72        StatusCode::BAD_REQUEST
73    }
74}
75
76impl ResponseError for serde_json::Error {}
77
78impl ResponseError for serde_urlencoded::ser::Error {}
79
80impl ResponseError for std::str::Utf8Error {
81    fn status_code(&self) -> StatusCode {
82        StatusCode::BAD_REQUEST
83    }
84}
85
86impl ResponseError for std::io::Error {
87    fn status_code(&self) -> StatusCode {
88        match self.kind() {
89            io::ErrorKind::NotFound => StatusCode::NOT_FOUND,
90            io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
91            _ => StatusCode::INTERNAL_SERVER_ERROR,
92        }
93    }
94}
95
96impl ResponseError for actix_http::error::HttpError {}
97
98impl ResponseError for actix_http::Error {
99    fn status_code(&self) -> StatusCode {
100        StatusCode::INTERNAL_SERVER_ERROR
101    }
102
103    fn error_response(&self) -> HttpResponse<BoxBody> {
104        HttpResponse::with_body(self.status_code(), self.to_string()).map_into_boxed_body()
105    }
106}
107
108impl ResponseError for actix_http::header::InvalidHeaderValue {
109    fn status_code(&self) -> StatusCode {
110        StatusCode::BAD_REQUEST
111    }
112}
113
114impl ResponseError for actix_http::error::ParseError {
115    fn status_code(&self) -> StatusCode {
116        StatusCode::BAD_REQUEST
117    }
118}
119
120impl ResponseError for actix_http::error::PayloadError {
121    fn status_code(&self) -> StatusCode {
122        match *self {
123            actix_http::error::PayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE,
124            _ => StatusCode::BAD_REQUEST,
125        }
126    }
127}
128
129impl ResponseError for actix_http::ws::ProtocolError {}
130
131impl ResponseError for actix_http::error::ContentTypeError {
132    fn status_code(&self) -> StatusCode {
133        StatusCode::BAD_REQUEST
134    }
135}
136
137impl ResponseError for actix_http::ws::HandshakeError {
138    fn error_response(&self) -> HttpResponse<BoxBody> {
139        Response::from(self).map_into_boxed_body().into()
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_error_casting() {
149        use actix_http::error::{ContentTypeError, PayloadError};
150
151        let err = PayloadError::Overflow;
152        let resp_err: &dyn ResponseError = &err;
153
154        let err = resp_err.downcast_ref::<PayloadError>().unwrap();
155        assert_eq!(err.to_string(), "payload reached size limit");
156
157        let not_err = resp_err.downcast_ref::<ContentTypeError>();
158        assert!(not_err.is_none());
159    }
160}