#[macro_use]
extern crate serde;
extern crate serde_json;
#[cfg(feature = "with_iron")]
extern crate iron;
#[cfg(feature = "with_hyper")]
extern crate hyper;
#[cfg(feature = "with_hyper")]
extern crate mime;
#[cfg(feature = "with_rocket")]
extern crate rocket;
use std::fmt;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub static PROBLEM_JSON_MEDIA_TYPE: &'static str = "application/problem+json";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HttpApiProblem {
#[cfg_attr(feature = "serde", serde(rename = "type"))]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub type_url: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub status: Option<HttpStatusCode>,
pub title: String,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub detail: Option<String>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub instance: Option<String>,
}
impl HttpApiProblem {
pub fn new<T: Into<String>>(title: T) -> HttpApiProblem {
HttpApiProblem {
type_url: None,
status: None,
title: title.into(),
detail: None,
instance: None,
}
}
pub fn with_title_and_type_from_status<T: Into<HttpStatusCode>>(status: T) -> HttpApiProblem {
let status = status.into();
HttpApiProblem {
type_url: Some(format!("https://httpstatuses.com/{}", status.to_u16())),
status: Some(status.into()),
title: status.title().to_string(),
detail: None,
instance: None,
}
}
pub fn with_title_from_status<T: Into<HttpStatusCode>>(status: T) -> HttpApiProblem {
let status = status.into();
HttpApiProblem {
type_url: None,
status: Some(status.into()),
title: status.title().to_string(),
detail: None,
instance: None,
}
}
pub fn set_type_url<T: Into<String>>(self, type_url: T) -> HttpApiProblem {
let mut s = self;
s.type_url = Some(type_url.into());
s
}
pub fn set_status<T: Into<HttpStatusCode>>(self, status: T) -> HttpApiProblem {
let status = status.into();
let mut s = self;
s.status = Some(status.into());
s
}
pub fn set_title<T: Into<String>>(self, title: T) -> HttpApiProblem {
let mut s = self;
s.title = title.into();
s
}
pub fn set_detail<T: Into<String>>(self, detail: T) -> HttpApiProblem {
let mut s = self;
s.detail = Some(detail.into());
s
}
pub fn set_instance<T: Into<String>>(self, instance: T) -> HttpApiProblem {
let mut s = self;
s.instance = Some(instance.into());
s
}
pub fn json_bytes(&self) -> Vec<u8> {
serde_json::to_vec(self).unwrap()
}
pub fn json_string(&self) -> String {
serde_json::to_string(self).unwrap()
}
#[cfg(feature = "with_iron")]
pub fn to_iron_response(&self) -> ::iron::response::Response {
use iron::headers::ContentType;
use iron::mime::Mime;
use iron::status::Status;
use iron::*;
let status: Status = self.status.unwrap_or(HttpStatusCode::InternalServerError).into();
let mut response = Response::with((status, self.json_bytes()));
let mime: Mime = PROBLEM_JSON_MEDIA_TYPE.parse().unwrap();
response.headers.set(ContentType(mime));
response
}
#[cfg(feature = "with_hyper")]
pub fn to_hyper_response(&self) -> hyper::Response {
use hyper::StatusCode;
use hyper::header::{ContentLength, ContentType};
use hyper::*;
use mime::*;
let status: StatusCode = self.status.unwrap_or(HttpStatusCode::InternalServerError).into();
let mime_type: Mime = PROBLEM_JSON_MEDIA_TYPE.parse().unwrap();
let json = self.json_bytes();
let length = json.len() as u64;
let response = Response::new()
.with_status(status)
.with_body(json)
.with_header(ContentType(mime_type))
.with_header(ContentLength(length));
response
}
#[cfg(feature = "with_rocket")]
pub fn to_rocket_response(&self) -> rocket::Response<'static> {
use rocket::Response;
use rocket::http::ContentType;
use rocket::http::Status;
use std::io::Cursor;
let status: Status = self.status.unwrap_or(HttpStatusCode::InternalServerError).into();
let content_type: ContentType = PROBLEM_JSON_MEDIA_TYPE.parse().unwrap();
let json = self.json_bytes();
let response = Response::build()
.status(status)
.sized_body(Cursor::new(json))
.header(content_type)
.finalize();
response
}
}
impl From<HttpStatusCode> for HttpApiProblem {
fn from(status: HttpStatusCode) -> HttpApiProblem {
HttpApiProblem::with_title_from_status(status)
}
}
#[cfg(feature = "with_iron")]
pub fn into_iron_response<T: Into<HttpApiProblem>>(what: T) -> ::iron::response::Response {
let problem: HttpApiProblem = what.into();
problem.to_iron_response()
}
#[cfg(feature = "with_iron")]
impl From<HttpApiProblem> for ::iron::response::Response {
fn from(problem: HttpApiProblem) -> ::iron::response::Response {
problem.to_iron_response()
}
}
#[cfg(feature = "with_hyper")]
pub fn into_hyper_response<T: Into<HttpApiProblem>>(what: T) -> hyper::Response {
let problem: HttpApiProblem = what.into();
problem.to_hyper_response()
}
#[cfg(feature = "with_hyper")]
impl From<HttpApiProblem> for hyper::Response {
fn from(problem: HttpApiProblem) -> hyper::Response {
problem.to_hyper_response()
}
}
#[cfg(feature = "with_rocket")]
pub fn into_rocket_response<T: Into<HttpApiProblem>>(what: T) -> ::rocket::Response<'static> {
let problem: HttpApiProblem = what.into();
problem.to_rocket_response()
}
#[cfg(feature = "with_rocket")]
impl From<HttpApiProblem> for ::rocket::Response<'static> {
fn from(problem: HttpApiProblem) -> ::rocket::Response<'static> {
problem.to_rocket_response()
}
}
#[cfg(feature = "with_rocket")]
impl<'r> ::rocket::response::Responder<'r> for HttpApiProblem {
fn respond_to(self, _request: &::rocket::Request) -> Result<::rocket::Response<'r>, ::rocket::http::Status> {
Ok(self.into())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpStatusCode {
Continue,
SwitchingProtocols,
Processing,
Ok,
Created,
Accepted,
NonAuthoritativeInformation,
NoContent,
ResetContent,
PartialContent,
MultiStatus,
AlreadyReported,
ImUsed,
MultipleChoices,
MovedPermanently,
Found,
SeeOther,
NotModified,
UseProxy,
TemporaryRedirect,
PermanentRedirect,
BadRequest,
Unauthorized,
PaymentRequired,
Forbidden,
NotFound,
MethodNotAllowed,
NotAcceptable,
ProxyAuthenticationRequired,
RequestTimeout,
Conflict,
Gone,
LengthRequired,
PreconditionFailed,
PayloadTooLarge,
UriTooLong,
UnsupportedMediaType,
RangeNotSatisfiable,
ExpectationFailed,
ImATeapot,
MisdirectedRequest,
UnprocessableEntity,
Locked,
FailedDependency,
UpgradeRequired,
PreconditionRequired,
TooManyRequests,
RequestHeaderFieldsTooLarge,
UnavailableForLegalReasons,
InternalServerError,
NotImplemented,
BadGateway,
ServiceUnavailable,
GatewayTimeout,
HttpVersionNotSupported,
VariantAlsoNegotiates,
InsufficientStorage,
LoopDetected,
NotExtended,
NetworkAuthenticationRequired,
Unregistered(u16),
}
impl HttpStatusCode {
pub fn title(&self) -> &'static str {
use HttpStatusCode::*;
match *self {
Continue => "Continue",
SwitchingProtocols => "Switching Protocols",
Processing => "Processing",
Ok => "Ok",
Created => "Created",
Accepted => "Accepted",
NonAuthoritativeInformation => "Non Authoritative Information",
NoContent => "No Content",
ResetContent => "Reset Content",
PartialContent => "Partial Content",
MultiStatus => "Multi Status",
AlreadyReported => "Already Reported",
ImUsed => "Im Used",
MultipleChoices => "Multiple Choices",
MovedPermanently => "Moved Permanently",
Found => "Found",
SeeOther => "See Other",
NotModified => "Not Modified",
UseProxy => "Use Proxy",
TemporaryRedirect => "Temporary Redirect",
PermanentRedirect => "Permanent Redirect",
BadRequest => "Bad Request",
Unauthorized => "Unauthorized",
PaymentRequired => "Payment Required",
Forbidden => "Forbidden",
NotFound => "Not Found",
MethodNotAllowed => "Method Not Allowed",
NotAcceptable => "Not Acceptable",
ProxyAuthenticationRequired => "Proxy Authentication Required",
RequestTimeout => "Request Timeout",
Conflict => "Conflict",
Gone => "Gone",
LengthRequired => "Length Required",
PreconditionFailed => "Precondition Failed",
PayloadTooLarge => "Payload Too Large",
UriTooLong => "Uri Too Long",
UnsupportedMediaType => "Unsupported Media Type",
RangeNotSatisfiable => "Range Not Satisfiable",
ExpectationFailed => "Expectation Failed",
ImATeapot => "Im A Teapot",
MisdirectedRequest => "Misdirected Request",
UnprocessableEntity => "Unprocessable Entity",
Locked => "Locked",
FailedDependency => "Failed Dependency",
UpgradeRequired => "Upgrade Required",
PreconditionRequired => "Precondition Required",
TooManyRequests => "Too Many Requests",
RequestHeaderFieldsTooLarge => "Request Header Fields Too Large",
UnavailableForLegalReasons => "Unavailable For Legal Reasons",
InternalServerError => "Internal Server Error",
NotImplemented => "Not Implemented",
BadGateway => "Bad Gateway",
ServiceUnavailable => "Service Unavailable",
GatewayTimeout => "Gateway Timeout",
HttpVersionNotSupported => "HTTP Version Not Supported",
VariantAlsoNegotiates => "Variant Also Negotiates",
InsufficientStorage => "Insufficient Storage",
LoopDetected => "Loop Detected",
NotExtended => "Not Extended",
NetworkAuthenticationRequired => "Network Authentication Required",
Unregistered(code) => {
if code / 100 == 4 {
"<Unregistered Client Error>"
} else if code / 100 == 5 {
"<Unregistered Server Error>"
} else {
"<Unregistered Status Code>"
}
}
}
}
pub fn to_u16(&self) -> u16 {
use HttpStatusCode::*;
match *self {
Continue => 100,
SwitchingProtocols => 101,
Processing => 102,
Ok => 200,
Created => 201,
Accepted => 202,
NonAuthoritativeInformation => 203,
NoContent => 204,
ResetContent => 205,
PartialContent => 206,
MultiStatus => 207,
AlreadyReported => 208,
ImUsed => 226,
MultipleChoices => 300,
MovedPermanently => 301,
Found => 302,
SeeOther => 303,
NotModified => 304,
UseProxy => 305,
TemporaryRedirect => 307,
PermanentRedirect => 308,
BadRequest => 400,
Unauthorized => 401,
PaymentRequired => 402,
Forbidden => 403,
NotFound => 404,
MethodNotAllowed => 405,
NotAcceptable => 406,
ProxyAuthenticationRequired => 407,
RequestTimeout => 408,
Conflict => 409,
Gone => 410,
LengthRequired => 411,
PreconditionFailed => 412,
PayloadTooLarge => 413,
UriTooLong => 414,
UnsupportedMediaType => 415,
RangeNotSatisfiable => 416,
ExpectationFailed => 417,
ImATeapot => 418,
MisdirectedRequest => 421,
UnprocessableEntity => 422,
Locked => 423,
FailedDependency => 424,
UpgradeRequired => 426,
PreconditionRequired => 428,
TooManyRequests => 429,
RequestHeaderFieldsTooLarge => 431,
UnavailableForLegalReasons => 451,
InternalServerError => 500,
NotImplemented => 501,
BadGateway => 502,
ServiceUnavailable => 503,
GatewayTimeout => 504,
HttpVersionNotSupported => 505,
VariantAlsoNegotiates => 506,
InsufficientStorage => 507,
LoopDetected => 508,
NotExtended => 510,
NetworkAuthenticationRequired => 511,
Unregistered(n) => n,
}
}
}
impl From<u16> for HttpStatusCode {
fn from(n: u16) -> HttpStatusCode {
use HttpStatusCode::*;
match n {
100 => Continue,
101 => SwitchingProtocols,
102 => Processing,
200 => Ok,
201 => Created,
202 => Accepted,
203 => NonAuthoritativeInformation,
204 => NoContent,
205 => ResetContent,
206 => PartialContent,
207 => MultiStatus,
208 => AlreadyReported,
226 => ImUsed,
300 => MultipleChoices,
301 => MovedPermanently,
302 => Found,
303 => SeeOther,
304 => NotModified,
305 => UseProxy,
307 => TemporaryRedirect,
308 => PermanentRedirect,
400 => BadRequest,
401 => Unauthorized,
402 => PaymentRequired,
403 => Forbidden,
404 => NotFound,
405 => MethodNotAllowed,
406 => NotAcceptable,
407 => ProxyAuthenticationRequired,
408 => RequestTimeout,
409 => Conflict,
410 => Gone,
411 => LengthRequired,
412 => PreconditionFailed,
413 => PayloadTooLarge,
414 => UriTooLong,
415 => UnsupportedMediaType,
416 => RangeNotSatisfiable,
417 => ExpectationFailed,
418 => ImATeapot,
421 => MisdirectedRequest,
422 => UnprocessableEntity,
423 => Locked,
424 => FailedDependency,
426 => UpgradeRequired,
428 => PreconditionRequired,
429 => TooManyRequests,
431 => RequestHeaderFieldsTooLarge,
451 => UnavailableForLegalReasons,
500 => InternalServerError,
501 => NotImplemented,
502 => BadGateway,
503 => ServiceUnavailable,
504 => GatewayTimeout,
505 => HttpVersionNotSupported,
506 => VariantAlsoNegotiates,
507 => InsufficientStorage,
508 => LoopDetected,
510 => NotExtended,
511 => NetworkAuthenticationRequired,
_ => Unregistered(n),
}
}
}
impl fmt::Display for HttpStatusCode {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{} {}", self.to_u16(), self.title())
}
}
impl Serialize for HttpStatusCode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u16(self.to_u16())
}
}
impl<'de> Deserialize<'de> for HttpStatusCode {
fn deserialize<D>(deserializer: D) -> Result<HttpStatusCode, D::Error>
where
D: Deserializer<'de>,
{
u16::deserialize(deserializer).map(Into::into)
}
}
#[cfg(feature = "with_hyper")]
impl From<::hyper::StatusCode> for HttpStatusCode {
fn from(hyper_status: hyper::StatusCode) -> HttpStatusCode {
hyper_status.as_u16().into()
}
}
#[cfg(feature = "with_hyper")]
impl From<HttpStatusCode> for ::hyper::StatusCode {
fn from(status: HttpStatusCode) -> ::hyper::StatusCode {
::hyper::StatusCode::try_from(status.to_u16())
.unwrap_or_else(|_| ::hyper::StatusCode::Unregistered(status.to_u16()))
}
}
#[cfg(feature = "with_iron")]
impl From<::iron::status::Status> for HttpStatusCode {
fn from(iron_status: ::iron::status::Status) -> HttpStatusCode {
iron_status.to_u16().into()
}
}
#[cfg(feature = "with_iron")]
impl From<HttpStatusCode> for ::iron::status::Status {
fn from(status: HttpStatusCode) -> ::iron::status::Status {
::iron::status::Status::from_u16(status.to_u16())
}
}
#[cfg(feature = "with_rocket")]
impl From<::rocket::http::Status> for HttpStatusCode {
fn from(rocket_status: ::rocket::http::Status) -> HttpStatusCode {
rocket_status.code.into()
}
}
#[cfg(feature = "with_rocket")]
impl From<HttpStatusCode> for ::rocket::http::Status {
fn from(status: HttpStatusCode) -> ::rocket::http::Status {
use rocket::http::Status;
let code = status.to_u16();
Status::from_code(code).unwrap_or(Status::new(code, ""))
}
}