actix_web/error/
internal.rs

1use std::{cell::RefCell, fmt, io::Write as _};
2
3use actix_http::{
4    body::BoxBody,
5    header::{self, TryIntoHeaderValue as _},
6    StatusCode,
7};
8use bytes::{BufMut as _, BytesMut};
9
10use crate::{Error, HttpRequest, HttpResponse, Responder, ResponseError};
11
12/// Wraps errors to alter the generated response status code.
13///
14/// In following example, the `io::Error` is wrapped into `ErrorBadRequest` which will generate a
15/// response with the 400 Bad Request status code instead of the usual status code generated by
16/// an `io::Error`.
17///
18/// # Examples
19/// ```
20/// # use std::io;
21/// # use actix_web::{error, HttpRequest};
22/// async fn handler_error() -> Result<String, actix_web::Error> {
23///     let err = io::Error::new(io::ErrorKind::Other, "error");
24///     Err(error::ErrorBadRequest(err))
25/// }
26/// ```
27pub struct InternalError<T> {
28    cause: T,
29    status: InternalErrorType,
30}
31
32enum InternalErrorType {
33    Status(StatusCode),
34    Response(RefCell<Option<HttpResponse>>),
35}
36
37impl<T> InternalError<T> {
38    /// Constructs an `InternalError` with given status code.
39    pub fn new(cause: T, status: StatusCode) -> Self {
40        InternalError {
41            cause,
42            status: InternalErrorType::Status(status),
43        }
44    }
45
46    /// Constructs an `InternalError` with pre-defined response.
47    pub fn from_response(cause: T, response: HttpResponse) -> Self {
48        InternalError {
49            cause,
50            status: InternalErrorType::Response(RefCell::new(Some(response))),
51        }
52    }
53}
54
55impl<T: fmt::Debug> fmt::Debug for InternalError<T> {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        self.cause.fmt(f)
58    }
59}
60
61impl<T: fmt::Display> fmt::Display for InternalError<T> {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        self.cause.fmt(f)
64    }
65}
66
67impl<T> ResponseError for InternalError<T>
68where
69    T: fmt::Debug + fmt::Display,
70{
71    fn status_code(&self) -> StatusCode {
72        match self.status {
73            InternalErrorType::Status(st) => st,
74            InternalErrorType::Response(ref resp) => {
75                if let Some(resp) = resp.borrow().as_ref() {
76                    resp.head().status
77                } else {
78                    StatusCode::INTERNAL_SERVER_ERROR
79                }
80            }
81        }
82    }
83
84    fn error_response(&self) -> HttpResponse {
85        match self.status {
86            InternalErrorType::Status(status) => {
87                let mut res = HttpResponse::new(status);
88                let mut buf = BytesMut::new().writer();
89                let _ = write!(buf, "{}", self);
90
91                let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
92                res.headers_mut().insert(header::CONTENT_TYPE, mime);
93
94                res.set_body(BoxBody::new(buf.into_inner()))
95            }
96
97            InternalErrorType::Response(ref resp) => {
98                if let Some(resp) = resp.borrow_mut().take() {
99                    resp
100                } else {
101                    HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR)
102                }
103            }
104        }
105    }
106}
107
108impl<T> Responder for InternalError<T>
109where
110    T: fmt::Debug + fmt::Display + 'static,
111{
112    type Body = BoxBody;
113
114    fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
115        HttpResponse::from_error(self)
116    }
117}
118
119macro_rules! error_helper {
120    ($name:ident, $status:ident) => {
121        #[doc = concat!("Helper function that wraps any error and generates a `", stringify!($status), "` response.")]
122        #[allow(non_snake_case)]
123        pub fn $name<T>(err: T) -> Error
124        where
125            T: fmt::Debug + fmt::Display + 'static,
126        {
127            InternalError::new(err, StatusCode::$status).into()
128        }
129    };
130}
131
132error_helper!(ErrorBadRequest, BAD_REQUEST);
133error_helper!(ErrorUnauthorized, UNAUTHORIZED);
134error_helper!(ErrorPaymentRequired, PAYMENT_REQUIRED);
135error_helper!(ErrorForbidden, FORBIDDEN);
136error_helper!(ErrorNotFound, NOT_FOUND);
137error_helper!(ErrorMethodNotAllowed, METHOD_NOT_ALLOWED);
138error_helper!(ErrorNotAcceptable, NOT_ACCEPTABLE);
139error_helper!(
140    ErrorProxyAuthenticationRequired,
141    PROXY_AUTHENTICATION_REQUIRED
142);
143error_helper!(ErrorRequestTimeout, REQUEST_TIMEOUT);
144error_helper!(ErrorConflict, CONFLICT);
145error_helper!(ErrorGone, GONE);
146error_helper!(ErrorLengthRequired, LENGTH_REQUIRED);
147error_helper!(ErrorPayloadTooLarge, PAYLOAD_TOO_LARGE);
148error_helper!(ErrorUriTooLong, URI_TOO_LONG);
149error_helper!(ErrorUnsupportedMediaType, UNSUPPORTED_MEDIA_TYPE);
150error_helper!(ErrorRangeNotSatisfiable, RANGE_NOT_SATISFIABLE);
151error_helper!(ErrorImATeapot, IM_A_TEAPOT);
152error_helper!(ErrorMisdirectedRequest, MISDIRECTED_REQUEST);
153error_helper!(ErrorUnprocessableEntity, UNPROCESSABLE_ENTITY);
154error_helper!(ErrorLocked, LOCKED);
155error_helper!(ErrorFailedDependency, FAILED_DEPENDENCY);
156error_helper!(ErrorUpgradeRequired, UPGRADE_REQUIRED);
157error_helper!(ErrorPreconditionFailed, PRECONDITION_FAILED);
158error_helper!(ErrorPreconditionRequired, PRECONDITION_REQUIRED);
159error_helper!(ErrorTooManyRequests, TOO_MANY_REQUESTS);
160error_helper!(
161    ErrorRequestHeaderFieldsTooLarge,
162    REQUEST_HEADER_FIELDS_TOO_LARGE
163);
164error_helper!(
165    ErrorUnavailableForLegalReasons,
166    UNAVAILABLE_FOR_LEGAL_REASONS
167);
168error_helper!(ErrorExpectationFailed, EXPECTATION_FAILED);
169error_helper!(ErrorInternalServerError, INTERNAL_SERVER_ERROR);
170error_helper!(ErrorNotImplemented, NOT_IMPLEMENTED);
171error_helper!(ErrorBadGateway, BAD_GATEWAY);
172error_helper!(ErrorServiceUnavailable, SERVICE_UNAVAILABLE);
173error_helper!(ErrorGatewayTimeout, GATEWAY_TIMEOUT);
174error_helper!(ErrorHttpVersionNotSupported, HTTP_VERSION_NOT_SUPPORTED);
175error_helper!(ErrorVariantAlsoNegotiates, VARIANT_ALSO_NEGOTIATES);
176error_helper!(ErrorInsufficientStorage, INSUFFICIENT_STORAGE);
177error_helper!(ErrorLoopDetected, LOOP_DETECTED);
178error_helper!(ErrorNotExtended, NOT_EXTENDED);
179error_helper!(
180    ErrorNetworkAuthenticationRequired,
181    NETWORK_AUTHENTICATION_REQUIRED
182);
183
184#[cfg(test)]
185mod tests {
186    use actix_http::error::ParseError;
187
188    use super::*;
189
190    #[test]
191    fn test_internal_error() {
192        let err = InternalError::from_response(ParseError::Method, HttpResponse::Ok().finish());
193        let resp: HttpResponse = err.error_response();
194        assert_eq!(resp.status(), StatusCode::OK);
195    }
196
197    #[test]
198    fn test_error_helpers() {
199        let res: HttpResponse = ErrorBadRequest("err").into();
200        assert_eq!(res.status(), StatusCode::BAD_REQUEST);
201
202        let res: HttpResponse = ErrorUnauthorized("err").into();
203        assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
204
205        let res: HttpResponse = ErrorPaymentRequired("err").into();
206        assert_eq!(res.status(), StatusCode::PAYMENT_REQUIRED);
207
208        let res: HttpResponse = ErrorForbidden("err").into();
209        assert_eq!(res.status(), StatusCode::FORBIDDEN);
210
211        let res: HttpResponse = ErrorNotFound("err").into();
212        assert_eq!(res.status(), StatusCode::NOT_FOUND);
213
214        let res: HttpResponse = ErrorMethodNotAllowed("err").into();
215        assert_eq!(res.status(), StatusCode::METHOD_NOT_ALLOWED);
216
217        let res: HttpResponse = ErrorNotAcceptable("err").into();
218        assert_eq!(res.status(), StatusCode::NOT_ACCEPTABLE);
219
220        let res: HttpResponse = ErrorProxyAuthenticationRequired("err").into();
221        assert_eq!(res.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED);
222
223        let res: HttpResponse = ErrorRequestTimeout("err").into();
224        assert_eq!(res.status(), StatusCode::REQUEST_TIMEOUT);
225
226        let res: HttpResponse = ErrorConflict("err").into();
227        assert_eq!(res.status(), StatusCode::CONFLICT);
228
229        let res: HttpResponse = ErrorGone("err").into();
230        assert_eq!(res.status(), StatusCode::GONE);
231
232        let res: HttpResponse = ErrorLengthRequired("err").into();
233        assert_eq!(res.status(), StatusCode::LENGTH_REQUIRED);
234
235        let res: HttpResponse = ErrorPreconditionFailed("err").into();
236        assert_eq!(res.status(), StatusCode::PRECONDITION_FAILED);
237
238        let res: HttpResponse = ErrorPayloadTooLarge("err").into();
239        assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE);
240
241        let res: HttpResponse = ErrorUriTooLong("err").into();
242        assert_eq!(res.status(), StatusCode::URI_TOO_LONG);
243
244        let res: HttpResponse = ErrorUnsupportedMediaType("err").into();
245        assert_eq!(res.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
246
247        let res: HttpResponse = ErrorRangeNotSatisfiable("err").into();
248        assert_eq!(res.status(), StatusCode::RANGE_NOT_SATISFIABLE);
249
250        let res: HttpResponse = ErrorExpectationFailed("err").into();
251        assert_eq!(res.status(), StatusCode::EXPECTATION_FAILED);
252
253        let res: HttpResponse = ErrorImATeapot("err").into();
254        assert_eq!(res.status(), StatusCode::IM_A_TEAPOT);
255
256        let res: HttpResponse = ErrorMisdirectedRequest("err").into();
257        assert_eq!(res.status(), StatusCode::MISDIRECTED_REQUEST);
258
259        let res: HttpResponse = ErrorUnprocessableEntity("err").into();
260        assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY);
261
262        let res: HttpResponse = ErrorLocked("err").into();
263        assert_eq!(res.status(), StatusCode::LOCKED);
264
265        let res: HttpResponse = ErrorFailedDependency("err").into();
266        assert_eq!(res.status(), StatusCode::FAILED_DEPENDENCY);
267
268        let res: HttpResponse = ErrorUpgradeRequired("err").into();
269        assert_eq!(res.status(), StatusCode::UPGRADE_REQUIRED);
270
271        let res: HttpResponse = ErrorPreconditionRequired("err").into();
272        assert_eq!(res.status(), StatusCode::PRECONDITION_REQUIRED);
273
274        let res: HttpResponse = ErrorTooManyRequests("err").into();
275        assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS);
276
277        let res: HttpResponse = ErrorRequestHeaderFieldsTooLarge("err").into();
278        assert_eq!(res.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE);
279
280        let res: HttpResponse = ErrorUnavailableForLegalReasons("err").into();
281        assert_eq!(res.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
282
283        let res: HttpResponse = ErrorInternalServerError("err").into();
284        assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
285
286        let res: HttpResponse = ErrorNotImplemented("err").into();
287        assert_eq!(res.status(), StatusCode::NOT_IMPLEMENTED);
288
289        let res: HttpResponse = ErrorBadGateway("err").into();
290        assert_eq!(res.status(), StatusCode::BAD_GATEWAY);
291
292        let res: HttpResponse = ErrorServiceUnavailable("err").into();
293        assert_eq!(res.status(), StatusCode::SERVICE_UNAVAILABLE);
294
295        let res: HttpResponse = ErrorGatewayTimeout("err").into();
296        assert_eq!(res.status(), StatusCode::GATEWAY_TIMEOUT);
297
298        let res: HttpResponse = ErrorHttpVersionNotSupported("err").into();
299        assert_eq!(res.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED);
300
301        let res: HttpResponse = ErrorVariantAlsoNegotiates("err").into();
302        assert_eq!(res.status(), StatusCode::VARIANT_ALSO_NEGOTIATES);
303
304        let res: HttpResponse = ErrorInsufficientStorage("err").into();
305        assert_eq!(res.status(), StatusCode::INSUFFICIENT_STORAGE);
306
307        let res: HttpResponse = ErrorLoopDetected("err").into();
308        assert_eq!(res.status(), StatusCode::LOOP_DETECTED);
309
310        let res: HttpResponse = ErrorNotExtended("err").into();
311        assert_eq!(res.status(), StatusCode::NOT_EXTENDED);
312
313        let res: HttpResponse = ErrorNetworkAuthenticationRequired("err").into();
314        assert_eq!(res.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED);
315    }
316}