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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
use async_trait::async_trait;
use http::StatusCode;
use mime::Mime;
use std::error::Error as StdError;
use std::fmt;

use crate::{Depot, Request, Response, Writer};

fn error_html(code: StatusCode, name: &str, summary: Option<&str>, detail: Option<&str>) -> String {
    format!(
        "<!DOCTYPE html>
<html>
<head>
    <meta charset=\"utf-8\">
    <title>{0}: {1}</title>
    <style>
    :root {{
        --bg-color: #fff;
        --text-color: #222;
    }}
    body {{
        background: var(--bg-color);
        color: var(--text-color);
        text-align: center;
    }}
    @media (prefers-color-scheme: dark) {{
        :root {{
            --bg-color: #222;
            --text-color: #ddd;
        }}
    }}
    </style>
</head>
<body>
    <div>
        <h1>{0}: {1}</h1>{2}{3}<hr />
        <small>salvo</small>
    </div>
</body>
</html>",
        code.as_u16(),
        name,
        summary.map(|summary| format!("<h3>{}</h3>", summary)).unwrap_or_default(),
        detail.map(|detail| format!("<p>{}</p>", detail)).unwrap_or_default(),
    )
}
fn error_json(code: StatusCode, name: &str, summary: Option<&str>, detail: Option<&str>) -> String {
    format!(
        "{{\"error\":{{\"code\":{},\"name\":\"{}\",\"summary\":\"{}\",\"detail\":\"{}\"}}}}",
        code.as_u16(),
        name,
        summary.unwrap_or(name),
        detail.unwrap_or("there is no more detailed explanation")
    )
}
fn error_text(code: StatusCode, name: &str, summary: Option<&str>, detail: Option<&str>) -> String {
    format!(
        "code:{},\nname:{},\nsummary:{},\ndetail:{}",
        code.as_u16(),
        name,
        summary.unwrap_or(name),
        detail.unwrap_or("there is no more detailed explanation")
    )
}
fn error_xml(code: StatusCode, name: &str, summary: Option<&str>, detail: Option<&str>) -> String {
    format!(
        "<error><code>{}</code><name>{}</name><summary>{}</summary><detail>{}</detail></error>",
        code.as_u16(),
        name,
        summary.unwrap_or(name),
        detail.unwrap_or("there is no more detailed explanation")
    )
}

pub type HttpResult<T> = Result<T, HttpError>;

#[derive(Debug)]
pub struct HttpError {
    pub code: StatusCode,
    pub name: String,
    pub summary: Option<String>,
    pub detail: Option<String>,
}
impl HttpError {
    pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
        self.summary = Some(summary.into());
        self
    }
    pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
        self.detail = Some(detail.into());
        self
    }
}

impl StdError for HttpError {}

impl fmt::Display for HttpError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "name: {}", &self.name)?;
        write!(f, "summary: {:?}", &self.summary)?;
        write!(f, "detail: {:?}", &self.detail)?;
        Ok(())
    }
}
impl HttpError {
    pub fn as_bytes(&self, prefer_format: &Mime) -> (Mime, Vec<u8>) {
        let format = if prefer_format.subtype() != mime::JSON && prefer_format.subtype() != mime::HTML {
            "text/html".parse().unwrap()
        } else {
            prefer_format.clone()
        };
        let content = match format.subtype().as_ref() {
            "text" => error_text(self.code, &self.name, self.summary.as_deref(), self.detail.as_deref()),
            "json" => error_json(self.code, &self.name, self.summary.as_deref(), self.detail.as_deref()),
            "xml" => error_xml(self.code, &self.name, self.summary.as_deref(), self.detail.as_deref()),
            _ => error_html(self.code, &self.name, self.summary.as_deref(), self.detail.as_deref()),
        };
        (format, content.as_bytes().to_owned())
    }
}
#[async_trait]
impl Writer for HttpError {
    async fn write(mut self, req: &mut Request, _depot: &mut Depot, res: &mut Response) {
        res.set_status_code(self.code);
        let format = crate::http::guess_accept_mime(req, None);
        let (format, data) = self.as_bytes(&format);
        res.render_binary(format.to_string().parse().unwrap(), &data);
    }
}

macro_rules! default_errors {
    ($($sname:ident, $code:expr, $name:expr, $summary:expr);+) => {
        $(
            #[allow(non_snake_case)]
            pub fn $sname() -> HttpError {
                HttpError {
                    code: $code,
                    name: $name.into(),
                    summary: None,
                    detail: None,
                }
            }
        )+
    }
}

pub fn from_code(code: StatusCode) -> Option<HttpError> {
    match code {
        StatusCode::BAD_REQUEST => Some(BadRequest()),
        StatusCode::UNAUTHORIZED => Some(Unauthorized()),
        StatusCode::PAYMENT_REQUIRED => Some(PaymentRequired()),
        StatusCode::FORBIDDEN => Some(Forbidden()),
        StatusCode::NOT_FOUND => Some(NotFound()),
        StatusCode::METHOD_NOT_ALLOWED => Some(MethodNotAllowed()),
        StatusCode::NOT_ACCEPTABLE => Some(NotAcceptable()),
        StatusCode::PROXY_AUTHENTICATION_REQUIRED => Some(ProxyAuthenticationRequired()),
        StatusCode::REQUEST_TIMEOUT => Some(RequestTimeout()),
        StatusCode::CONFLICT => Some(Conflict()),
        StatusCode::GONE => Some(Gone()),
        StatusCode::LENGTH_REQUIRED => Some(LengthRequired()),
        StatusCode::PRECONDITION_FAILED => Some(PreconditionFailed()),
        StatusCode::PAYLOAD_TOO_LARGE => Some(PayloadTooLarge()),
        StatusCode::URI_TOO_LONG => Some(UriTooLong()),
        StatusCode::UNSUPPORTED_MEDIA_TYPE => Some(UnsupportedMediaType()),
        StatusCode::RANGE_NOT_SATISFIABLE => Some(RangeNotSatisfiable()),
        StatusCode::EXPECTATION_FAILED => Some(ExpectationFailed()),
        StatusCode::IM_A_TEAPOT => Some(ImATeapot()),
        StatusCode::MISDIRECTED_REQUEST => Some(MisdirectedRequest()),
        StatusCode::UNPROCESSABLE_ENTITY => Some(UnprocessableEntity()),
        StatusCode::LOCKED => Some(Locked()),
        StatusCode::FAILED_DEPENDENCY => Some(FailedDependency()),
        StatusCode::UPGRADE_REQUIRED => Some(UpgradeRequired()),
        StatusCode::PRECONDITION_REQUIRED => Some(PreconditionRequired()),
        StatusCode::TOO_MANY_REQUESTS => Some(TooManyRequests()),
        StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE => Some(RequestHeaderFieldsTooLarge()),
        StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS => Some(UnavailableForLegalReasons()),
        StatusCode::INTERNAL_SERVER_ERROR => Some(InternalServerError()),
        StatusCode::NOT_IMPLEMENTED => Some(NotImplemented()),
        StatusCode::BAD_GATEWAY => Some(BadGateway()),
        StatusCode::SERVICE_UNAVAILABLE => Some(ServiceUnavailable()),
        StatusCode::GATEWAY_TIMEOUT => Some(GatewayTimeout()),
        StatusCode::HTTP_VERSION_NOT_SUPPORTED => Some(HttpVersionNotSupported()),
        StatusCode::VARIANT_ALSO_NEGOTIATES => Some(VariantAlsoNegotiates()),
        StatusCode::INSUFFICIENT_STORAGE => Some(InsufficientStorage()),
        StatusCode::LOOP_DETECTED => Some(LoopDetected()),
        StatusCode::NOT_EXTENDED => Some(NotExtended()),
        StatusCode::NETWORK_AUTHENTICATION_REQUIRED => Some(NetworkAuthenticationRequired()),
        _ => None,
    }
}
default_errors! {
    BadRequest,            StatusCode::BAD_REQUEST,            "Bad Request", "The request could not be understood by the server due to malformed syntax.";
    Unauthorized,          StatusCode::UNAUTHORIZED,           "Unauthorized", "The request requires user authentication.";
    PaymentRequired,       StatusCode::PAYMENT_REQUIRED,       "Payment Required", "The request could not be processed due to lack of payment.";
    Forbidden,             StatusCode::FORBIDDEN,              "Forbidden", "The server refused to authorize the request.";
    NotFound,              StatusCode::NOT_FOUND,              "Not Found", "The requested resource could not be found.";
    MethodNotAllowed,      StatusCode::METHOD_NOT_ALLOWED,     "Method Not Allowed", "The request method is not supported for the requested resource.";
    NotAcceptable,         StatusCode::NOT_ACCEPTABLE,         "Not Acceptable", "The requested resource is capable of generating only content not acceptable according to the Accept headers sent in the request.";
    ProxyAuthenticationRequired, StatusCode::PROXY_AUTHENTICATION_REQUIRED,  "Proxy Authentication Required", "Authentication with the proxy is required.";
    RequestTimeout,        StatusCode::REQUEST_TIMEOUT,        "Request Timeout", "The server timed out waiting for the request.";
    Conflict,              StatusCode::CONFLICT,               "Conflict", "The request could not be processed because of a conflict in the request.";
    Gone,                  StatusCode::GONE,                   "Gone", "The resource requested is no longer available and will not be available again.";
    LengthRequired,        StatusCode::LENGTH_REQUIRED,        "Length Required", "The request did not specify the length of its content, which is required by the requested resource.";
    PreconditionFailed,    StatusCode::PRECONDITION_FAILED,    "Precondition Failed", "The server does not meet one of the preconditions specified in the request.";
    PayloadTooLarge,       StatusCode::PAYLOAD_TOO_LARGE,      "Payload Too Large", "The request is larger than the server is willing or able to process.";
    UriTooLong,            StatusCode::URI_TOO_LONG,           "URI Too Long", "The URI provided was too long for the server to process.";
    UnsupportedMediaType,  StatusCode::UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type", "The request entity has a media type which the server or resource does not support.";
    RangeNotSatisfiable,   StatusCode::RANGE_NOT_SATISFIABLE,  "Range Not Satisfiable", "The portion of the requested file cannot be supplied by the server.";
    ExpectationFailed,     StatusCode::EXPECTATION_FAILED,     "Expectation Failed", "The server cannot meet the requirements of the expect request-header field.";
    ImATeapot,             StatusCode::IM_A_TEAPOT,            "I'm a teapot", "I was requested to brew coffee, and I am a teapot.";
    MisdirectedRequest,    StatusCode::MISDIRECTED_REQUEST,    "Misdirected Request", "The server cannot produce a response for this request.";
    UnprocessableEntity,   StatusCode::UNPROCESSABLE_ENTITY,   "Unprocessable Entity", "The request was well-formed but was unable to be followed due to semantic errors.";
    Locked,                StatusCode::LOCKED,                 "Locked", "The source or destination resource of a method is locked.";
    FailedDependency,      StatusCode::FAILED_DEPENDENCY,      "Failed Dependency", "The method could not be performed on the resource because the requested action depended on another action and that action failed.";
    UpgradeRequired,       StatusCode::UPGRADE_REQUIRED,       "Upgrade Required", "Switching to the protocol in the Upgrade header field is required.";
    PreconditionRequired,  StatusCode::PRECONDITION_REQUIRED,  "Precondition Required", "The server requires the request to be conditional.";
    TooManyRequests,       StatusCode::TOO_MANY_REQUESTS,      "Too Many Requests", "Too many requests have been received recently.";
    RequestHeaderFieldsTooLarge,   StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,    "Request Header Fields Too Large", "The server is unwilling to process the request because either  an individual header field, or all the header fields collectively, are too large.";
    UnavailableForLegalReasons,    StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS,      "Unavailable For Legal Reasons", "The requested resource is unavailable due to a legal demand to deny access to this resource.";
    InternalServerError,        StatusCode::INTERNAL_SERVER_ERROR,  "Internal Server Error", "The server encountered an internal error while processing this request.";
    NotImplemented,        StatusCode::NOT_IMPLEMENTED,        "Not Implemented", "The server either does not recognize the request method, or it lacks the ability to fulfill the request.";
    BadGateway,            StatusCode::BAD_GATEWAY,            "Bad Gateway", "Received an invalid response from an inbound server it accessed while attempting to fulfill the request.";
    ServiceUnavailable,    StatusCode::SERVICE_UNAVAILABLE,    "Service Unavailable", "The server is currently unavailable.";
    GatewayTimeout,        StatusCode::GATEWAY_TIMEOUT,        "Gateway Timeout", "The server did not receive a timely response from an upstream server.";
    HttpVersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED, "HTTP Version Not Supported", "The server does not support, or refuses to support, the major version of HTTP that was used in the request message.";
    VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES, "Variant Also Negotiates", "The server has an internal configuration error.";
    InsufficientStorage,   StatusCode::INSUFFICIENT_STORAGE,    "Insufficient Storage", "The method could not be performed on the resource because the server is unable to store the representation needed to successfully complete the request.";
    LoopDetected,          StatusCode::LOOP_DETECTED,           "Loop Detected", "the server terminated an operation because it encountered an infinite loop while processing a request with \"Depth: infinity\".";
    NotExtended,           StatusCode::NOT_EXTENDED,            "Not Extended", "Further extensions to the request are required for the server to fulfill it.";
    NetworkAuthenticationRequired, StatusCode::NETWORK_AUTHENTICATION_REQUIRED, "Network Authentication Required", "the client needs to authenticate to gain network access."
}