logo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! `ResponseError` trait and foreign impls.

use std::{
    error::Error as StdError,
    fmt,
    io::{self, Write as _},
};

use actix_http::{
    body::BoxBody,
    header::{self, TryIntoHeaderValue},
    Response, StatusCode,
};
use bytes::BytesMut;

use crate::{
    error::{downcast_dyn, downcast_get_type_id},
    helpers, HttpResponse,
};

/// Errors that can generate responses.
// TODO: add std::error::Error bound when replacement for Box<dyn Error> is found
pub trait ResponseError: fmt::Debug + fmt::Display {
    /// Returns appropriate status code for error.
    ///
    /// A 500 Internal Server Error is used by default. If [error_response](Self::error_response) is
    /// also implemented and does not call `self.status_code()`, then this will not be used.
    fn status_code(&self) -> StatusCode {
        StatusCode::INTERNAL_SERVER_ERROR
    }

    /// Creates full response for error.
    ///
    /// By default, the generated response uses a 500 Internal Server Error status code, a
    /// `Content-Type` of `text/plain`, and the body is set to `Self`'s `Display` impl.
    fn error_response(&self) -> HttpResponse<BoxBody> {
        let mut res = HttpResponse::new(self.status_code());

        let mut buf = BytesMut::new();
        let _ = write!(helpers::MutWriter(&mut buf), "{}", self);

        let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
        res.headers_mut().insert(header::CONTENT_TYPE, mime);

        res.set_body(BoxBody::new(buf))
    }

    downcast_get_type_id!();
}

downcast_dyn!(ResponseError);

impl ResponseError for Box<dyn StdError + 'static> {}

#[cfg(feature = "openssl")]
impl ResponseError for actix_tls::accept::openssl::reexports::Error {}

impl ResponseError for serde::de::value::Error {
    fn status_code(&self) -> StatusCode {
        StatusCode::BAD_REQUEST
    }
}

impl ResponseError for serde_json::Error {}

impl ResponseError for serde_urlencoded::ser::Error {}

impl ResponseError for std::str::Utf8Error {
    fn status_code(&self) -> StatusCode {
        StatusCode::BAD_REQUEST
    }
}

impl ResponseError for std::io::Error {
    fn status_code(&self) -> StatusCode {
        // TODO: decide if these errors should consider not found or permission errors
        match self.kind() {
            io::ErrorKind::NotFound => StatusCode::NOT_FOUND,
            io::ErrorKind::PermissionDenied => StatusCode::FORBIDDEN,
            _ => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }
}

impl ResponseError for actix_http::error::HttpError {}

impl ResponseError for actix_http::Error {
    fn status_code(&self) -> StatusCode {
        // TODO: map error kinds to status code better
        StatusCode::INTERNAL_SERVER_ERROR
    }

    fn error_response(&self) -> HttpResponse<BoxBody> {
        HttpResponse::with_body(self.status_code(), self.to_string()).map_into_boxed_body()
    }
}

impl ResponseError for actix_http::header::InvalidHeaderValue {
    fn status_code(&self) -> StatusCode {
        StatusCode::BAD_REQUEST
    }
}

impl ResponseError for actix_http::error::ParseError {
    fn status_code(&self) -> StatusCode {
        StatusCode::BAD_REQUEST
    }
}

impl ResponseError for actix_http::error::BlockingError {}

impl ResponseError for actix_http::error::PayloadError {
    fn status_code(&self) -> StatusCode {
        match *self {
            actix_http::error::PayloadError::Overflow => StatusCode::PAYLOAD_TOO_LARGE,
            _ => StatusCode::BAD_REQUEST,
        }
    }
}

impl ResponseError for actix_http::ws::ProtocolError {}

impl ResponseError for actix_http::error::ContentTypeError {
    fn status_code(&self) -> StatusCode {
        StatusCode::BAD_REQUEST
    }
}

impl ResponseError for actix_http::ws::HandshakeError {
    fn error_response(&self) -> HttpResponse<BoxBody> {
        Response::from(self).map_into_boxed_body().into()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_casting() {
        use actix_http::error::{ContentTypeError, PayloadError};

        let err = PayloadError::Overflow;
        let resp_err: &dyn ResponseError = &err;

        let err = resp_err.downcast_ref::<PayloadError>().unwrap();
        assert_eq!(err.to_string(), "Payload reached size limit.");

        let not_err = resp_err.downcast_ref::<ContentTypeError>();
        assert!(not_err.is_none());
    }
}