use std::fmt;
use std::io::Cursor;
use crate::response::Response;
use crate::request::Request;
use crate::http::{Status, ContentType, uri};
use crate::catcher::{Handler, BoxFuture};
use yansi::Paint;
#[derive(Clone)]
pub struct Catcher {
pub name: Option<Cow<'static, str>>,
pub base: uri::Origin<'static>,
pub code: Option<u16>,
pub handler: Box<dyn Handler>,
}
impl Catcher {
#[inline(always)]
pub fn new<S, H>(code: S, handler: H) -> Catcher
where S: Into<Option<u16>>, H: Handler
{
let code = code.into();
if let Some(code) = code {
assert!(code >= 400 && code < 600);
}
Catcher {
name: None,
base: uri::Origin::ROOT,
handler: Box::new(handler),
code,
}
}
pub fn map_base<'a, F>(
mut self,
mapper: F
) -> std::result::Result<Self, uri::Error<'static>>
where F: FnOnce(uri::Origin<'a>) -> String
{
self.base = uri::Origin::parse_owned(mapper(self.base))?.into_normalized();
self.base.clear_query();
Ok(self)
}
}
impl Default for Catcher {
fn default() -> Self {
fn handler<'r>(s: Status, req: &'r Request<'_>) -> BoxFuture<'r> {
Box::pin(async move { Ok(default_handler(s, req)) })
}
let mut catcher = Catcher::new(None, handler);
catcher.name = Some("<Rocket Catcher>".into());
catcher
}
}
#[doc(hidden)]
pub struct StaticInfo {
pub name: &'static str,
pub code: Option<u16>,
pub handler: for<'r> fn(Status, &'r Request<'_>) -> BoxFuture<'r>,
}
#[doc(hidden)]
impl From<StaticInfo> for Catcher {
#[inline]
fn from(info: StaticInfo) -> Catcher {
let mut catcher = Catcher::new(info.code, info.handler);
catcher.name = Some(info.name.into());
catcher
}
}
impl fmt::Display for Catcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref n) = self.name {
write!(f, "{}{}{} ", "(".cyan(), n.primary(), ")".cyan())?;
}
if self.base.path() != "/" {
write!(f, "{} ", self.base.path().green())?;
}
match self.code {
Some(code) => write!(f, "{}", code.blue()),
None => write!(f, "{}", "default".blue()),
}
}
}
impl fmt::Debug for Catcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Catcher")
.field("name", &self.name)
.field("base", &self.base)
.field("code", &self.code)
.finish()
}
}
macro_rules! html_error_template {
($code:expr, $reason:expr, $description:expr) => (
concat!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="color-scheme" content="light dark">
<title>"#, $code, " ", $reason, r#"</title>
</head>
<body align="center">
<div role="main" align="center">
<h1>"#, $code, ": ", $reason, r#"</h1>
<p>"#, $description, r#"</p>
<hr />
</div>
<div role="contentinfo" align="center">
<small>Rocket</small>
</div>
</body>
</html>"#
)
)
}
macro_rules! json_error_template {
($code:expr, $reason:expr, $description:expr) => (
concat!(
r#"{
"error": {
"code": "#, $code, r#",
"reason": ""#, $reason, r#"",
"description": ""#, $description, r#""
}
}"#
)
)
}
macro_rules! json_error_fmt_template {
($code:expr, $reason:expr, $description:expr) => (
concat!(
r#"{{
"error": {{
"code": "#, $code, r#",
"reason": ""#, $reason, r#"",
"description": ""#, $description, r#""
}}
}}"#
)
)
}
macro_rules! default_handler_fn {
($($code:expr, $reason:expr, $description:expr),+) => (
use std::borrow::Cow;
pub(crate) fn default_handler<'r>(
status: Status,
req: &'r Request<'_>
) -> Response<'r> {
let preferred = req.accept().map(|a| a.preferred());
let (mime, text) = if preferred.map_or(false, |a| a.is_json()) {
let json: Cow<'_, str> = match status.code {
$($code => json_error_template!($code, $reason, $description).into(),)*
code => format!(json_error_fmt_template!("{}", "Unknown Error",
"An unknown error has occurred."), code).into()
};
(ContentType::JSON, json)
} else {
let html: Cow<'_, str> = match status.code {
$($code => html_error_template!($code, $reason, $description).into(),)*
code => format!(html_error_template!("{}", "Unknown Error",
"An unknown error has occurred."), code, code).into(),
};
(ContentType::HTML, html)
};
let mut r = Response::build().status(status).header(mime).finalize();
match text {
Cow::Owned(v) => r.set_sized_body(v.len(), Cursor::new(v)),
Cow::Borrowed(v) => r.set_sized_body(v.len(), Cursor::new(v)),
};
r
}
)
}
default_handler_fn! {
400, "Bad Request", "The request could not be understood by the server due \
to malformed syntax.",
401, "Unauthorized", "The request requires user authentication.",
402, "Payment Required", "The request could not be processed due to lack of payment.",
403, "Forbidden", "The server refused to authorize the request.",
404, "Not Found", "The requested resource could not be found.",
405, "Method Not Allowed", "The request method is not supported for the requested resource.",
406, "Not Acceptable", "The requested resource is capable of generating only content not \
acceptable according to the Accept headers sent in the request.",
407, "Proxy Authentication Required", "Authentication with the proxy is required.",
408, "Request Timeout", "The server timed out waiting for the request.",
409, "Conflict", "The request could not be processed because of a conflict in the request.",
410, "Gone", "The resource requested is no longer available and will not be available again.",
411, "Length Required", "The request did not specify the length of its content, which is \
required by the requested resource.",
412, "Precondition Failed", "The server does not meet one of the \
preconditions specified in the request.",
413, "Payload Too Large", "The request is larger than the server is \
willing or able to process.",
414, "URI Too Long", "The URI provided was too long for the server to process.",
415, "Unsupported Media Type", "The request entity has a media type which \
the server or resource does not support.",
416, "Range Not Satisfiable", "The portion of the requested file cannot be \
supplied by the server.",
417, "Expectation Failed", "The server cannot meet the requirements of the \
Expect request-header field.",
418, "I'm a teapot", "I was requested to brew coffee, and I am a teapot.",
421, "Misdirected Request", "The server cannot produce a response for this request.",
422, "Unprocessable Entity", "The request was well-formed but was unable to \
be followed due to semantic errors.",
426, "Upgrade Required", "Switching to the protocol in the Upgrade header field is required.",
428, "Precondition Required", "The server requires the request to be conditional.",
429, "Too Many Requests", "Too many requests have been received recently.",
431, "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.",
451, "Unavailable For Legal Reasons", "The requested resource is unavailable \
due to a legal demand to deny access to this resource.",
500, "Internal Server Error", "The server encountered an internal error while \
processing this request.",
501, "Not Implemented", "The server either does not recognize the request \
method, or it lacks the ability to fulfill the request.",
503, "Service Unavailable", "The server is currently unavailable.",
504, "Gateway Timeout", "The server did not receive a timely response from an upstream server.",
510, "Not Extended", "Further extensions to the request are required for \
the server to fulfill it."
}