use crate::{
Components, Content, HandlerOut, Object, Operation, Ref, RefOr, Response, Responses, Schema,
ToResponses, ToSchema,
};
use hypers_core::{hyper::StatusCode, prelude::Responder, Error};
use std::fmt::{self, Display, Formatter};
macro_rules! default_errors {
(
$(
$(#[$docs:meta])*
$sname:ident, $code:expr, $name:expr, $detail:expr);
+) =>
{
$(
#[doc=concat!($detail,"\n ")]
$(#[$docs])*
pub fn $sname() -> StatusError {
StatusError {
code: $code,
name: $name.into(),
detail: $detail.into(),
}
}
)+
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct StatusError {
pub code: StatusCode,
pub name: String,
pub detail: String,
}
impl Display for StatusError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "code: {}", self.code)?;
write!(f, "name: {}", self.name)?;
write!(f, "detail: {:?}", self.detail)?;
Ok(())
}
}
impl std::error::Error for StatusError {}
impl StatusError {
pub fn detail(mut self, detail: impl Into<String>) -> Self {
self.detail = detail.into();
self
}
default_errors! {
bad_request, 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.";
payment_required, 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.";
not_found, StatusCode::NOT_FOUND, "Not Found", "The requested resource could not be found.";
method_not_allowed, StatusCode::METHOD_NOT_ALLOWED, "Method Not Allowed", "The request method is not supported for the requested resource.";
not_acceptable, 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.";
proxy_authentication_required, StatusCode::PROXY_AUTHENTICATION_REQUIRED, "Proxy Authentication Required", "Authentication with the proxy is required.";
request_timeout, 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.";
length_required, StatusCode::LENGTH_REQUIRED, "Length Required", "The request did not specify the length of its content, which is required by the requested resource.";
precondition_failed, StatusCode::PRECONDITION_FAILED, "Precondition Failed", "The server does not meet one of the preconditions specified in the request.";
payload_too_large, StatusCode::PAYLOAD_TOO_LARGE, "Payload Too Large", "The request is larger than the server is willing or able to process.";
uri_too_long, StatusCode::URI_TOO_LONG, "URI Too Long", "The URI provided was too long for the server to process.";
unsupported_media_type, StatusCode::UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type", "The request entity has a media type which the server or resource does not support.";
range_not_satisfiable, StatusCode::RANGE_NOT_SATISFIABLE, "Range Not Satisfiable", "The portion of the requested file cannot be supplied by the server.";
expectation_failed, StatusCode::EXPECTATION_FAILED, "Expectation Failed", "The server cannot meet the requirements of the expect request-header field.";
im_a_teapot, StatusCode::IM_A_TEAPOT, "I'm a teapot", "I was requested to brew coffee, and I am a teapot.";
misdirected_request, StatusCode::MISDIRECTED_REQUEST, "Misdirected Request", "The server cannot produce a response for this request.";
unprocessable_entity, 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.";
failed_dependency, 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.";
upgrade_required, StatusCode::UPGRADE_REQUIRED, "Upgrade Required", "Switching to the protocol in the Upgrade header field is required.";
precondition_required, StatusCode::PRECONDITION_REQUIRED, "Precondition Required", "The server requires the request to be conditional.";
too_many_requests, StatusCode::TOO_MANY_REQUESTS, "Too Many Requests", "Too many requests have been received recently.";
request_header_fields_toolarge, 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.";
unavailable_for_legalreasons, 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.";
internal_server_error, StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error", "The server encountered an internal error while processing this request.";
not_implemented, StatusCode::NOT_IMPLEMENTED, "Not Implemented", "The server either does not recognize the request method, or it lacks the ability to fulfill the request.";
bad_gateway, StatusCode::BAD_GATEWAY, "Bad Gateway", "Received an invalid response from an inbound server it accessed while attempting to fulfill the request.";
service_unavailable, StatusCode::SERVICE_UNAVAILABLE, "Service Unavailable", "The server is currently unavailable.";
gateway_timeout, StatusCode::GATEWAY_TIMEOUT, "Gateway Timeout", "The server did not receive a timely response from an upstream server.";
http_version_not_supported, 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.";
variant_also_negotiates, StatusCode::VARIANT_ALSO_NEGOTIATES, "Variant Also Negotiates", "The server has an internal configuration error.";
insufficient_storage, 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.";
loop_detected, StatusCode::LOOP_DETECTED, "Loop Detected", "the server terminated an operation because it encountered an infinite loop while processing a request with \"Depth: infinity\".";
not_extended, StatusCode::NOT_EXTENDED, "Not Extended", "Further extensions to the request are required for the server to fulfill it.";
network_authentication_required, StatusCode::NETWORK_AUTHENTICATION_REQUIRED, "Network Authentication Required", "the client needs to authenticate to gain network access."
}
}
impl ToSchema for StatusError {
#[inline]
fn to_schema(components: &mut Components) -> RefOr<Schema> {
let symbol = std::any::type_name::<StatusError>().replace("::", ".");
let schema = Schema::from(
Object::new()
.property("code", u16::to_schema(components))
.required("code")
.required("name")
.property("name", String::to_schema(components))
.required("detail")
.property("detail", String::to_schema(components)),
);
components.schemas.insert(symbol.clone(), schema.into());
RefOr::Ref(Ref::new(format!("#/components/schemas/{}", symbol)))
}
}
impl ToResponses for StatusError {
#[inline]
fn to_responses(components: &mut Components) -> Responses {
let mut responses = Responses::new();
let errors = vec![
StatusError::bad_request(),
StatusError::unauthorized(),
StatusError::payment_required(),
StatusError::forbidden(),
StatusError::not_found(),
StatusError::method_not_allowed(),
StatusError::not_acceptable(),
StatusError::proxy_authentication_required(),
StatusError::request_timeout(),
StatusError::conflict(),
StatusError::gone(),
StatusError::length_required(),
StatusError::precondition_failed(),
StatusError::payload_too_large(),
StatusError::uri_too_long(),
StatusError::unsupported_media_type(),
StatusError::range_not_satisfiable(),
StatusError::expectation_failed(),
StatusError::im_a_teapot(),
StatusError::misdirected_request(),
StatusError::unprocessable_entity(),
StatusError::locked(),
StatusError::failed_dependency(),
StatusError::upgrade_required(),
StatusError::precondition_required(),
StatusError::too_many_requests(),
StatusError::request_header_fields_toolarge(),
StatusError::unavailable_for_legalreasons(),
StatusError::internal_server_error(),
StatusError::not_implemented(),
StatusError::bad_gateway(),
StatusError::service_unavailable(),
StatusError::gateway_timeout(),
StatusError::http_version_not_supported(),
StatusError::variant_also_negotiates(),
StatusError::insufficient_storage(),
StatusError::loop_detected(),
StatusError::not_extended(),
StatusError::network_authentication_required(),
];
for StatusError { code, detail, .. } in errors {
responses.insert(
code.as_str(),
Response::new(detail).add_content(
"application/json",
Content::new(StatusError::to_schema(components)),
),
)
}
responses
}
}
impl HandlerOut for StatusCode {
#[inline]
fn register(components: &mut Components, operation: &mut Operation) {
for code in [
StatusCode::CONTINUE,
StatusCode::SWITCHING_PROTOCOLS,
StatusCode::PROCESSING,
StatusCode::OK,
StatusCode::CREATED,
StatusCode::ACCEPTED,
StatusCode::NON_AUTHORITATIVE_INFORMATION,
StatusCode::NO_CONTENT,
StatusCode::RESET_CONTENT,
StatusCode::PARTIAL_CONTENT,
StatusCode::MULTI_STATUS,
StatusCode::ALREADY_REPORTED,
StatusCode::IM_USED,
StatusCode::MULTIPLE_CHOICES,
StatusCode::MOVED_PERMANENTLY,
StatusCode::FOUND,
StatusCode::SEE_OTHER,
StatusCode::NOT_MODIFIED,
StatusCode::USE_PROXY,
StatusCode::TEMPORARY_REDIRECT,
StatusCode::PERMANENT_REDIRECT,
] {
operation.responses.insert(
code.as_str(),
Response::new(
code.canonical_reason()
.unwrap_or("No further explanation is available."),
),
)
}
operation
.responses
.append(&mut StatusError::to_responses(components));
}
}
impl HandlerOut for StatusError {
#[inline]
fn register(components: &mut Components, operation: &mut Operation) {
operation
.responses
.append(&mut Self::to_responses(components));
}
}
impl HandlerOut for Error {
fn register(components: &mut Components, operation: &mut Operation) {
operation
.responses
.append(&mut StatusError::to_responses(components));
}
}
impl ToSchema for Error {
#[inline]
fn to_schema(components: &mut Components) -> RefOr<Schema> {
StatusError::to_schema(components)
}
}
impl ToResponses for Error {
#[inline]
fn to_responses(components: &mut Components) -> Responses {
StatusError::to_responses(components)
}
}
impl Responder for StatusError {
#[inline]
fn response(self, mut res: hypers_core::prelude::Response) -> hypers_core::prelude::Response {
res.status(self.code).body(self.detail);
res
}
}