use std::collections::HashMap;
use std::fmt;
use std::io;
use failure::*;
use serde_json::Value;
use super::*;
#[derive(Debug)]
pub struct ApiError {
inner: Context<ApiErrorData>,
}
impl ApiError {
pub fn new<S, M>(status: S, message: M) -> Self
where
S: Into<StatusCode>,
M: Into<String>,
{
let data = (status, message).into();
Self {
inner: Context::new(data),
}
}
pub fn with_data(data: ApiErrorData) -> Self {
Self {
inner: Context::new(data),
}
}
pub fn with_failure<S, M, F>(status: S, message: M, err: F) -> Self
where
S: Into<StatusCode>,
M: Into<String>,
F: Fail,
{
(status, message, err).into()
}
pub fn message(&self) -> &str {
&self.context().message
}
pub fn status(&self) -> StatusCode {
self.context().status.clone()
}
pub fn type_url(&self) -> Option<&str> {
self.context().type_url.as_ref().map(|s| &**s)
}
pub fn fields(&self) -> &HashMap<String, Value> {
&self.context().fields
}
pub fn title(&self) -> Option<&str> {
self.context().title.as_ref().map(|s| &**s)
}
pub fn context(&self) -> &ApiErrorData {
&self.inner.get_context()
}
pub fn to_http_api_problem(&self) -> HttpApiProblem {
self.context().to_http_api_problem()
}
#[cfg(feature = "with_hyper")]
pub fn to_hyper_response(&self) -> hyper::Response<hyper::Body>{
let problem = self.to_http_api_problem();
problem.to_hyper_response()
}
#[cfg(feature = "with_actix_web")]
pub fn to_actix_web_response(&self) -> actix_web::HttpResponse{
let problem = self.to_http_api_problem();
problem.into()
}
}
impl Fail for ApiError {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
}
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
}
fn name(&self) -> Option<&str> {
Some("http_api_problem::ApiError")
}
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.inner)
}
}
#[derive(Debug, Fail, Clone)]
#[fail(display = "An error with status code {} occured: {}", status, message)]
pub struct ApiErrorData {
pub message: String,
pub status: StatusCode,
pub type_url: Option<String>,
pub fields: HashMap<String, Value>,
pub title: Option<String>,
}
impl ApiErrorData {
pub fn new<S, M>(status: S, message: M) -> ApiErrorData
where
M: Into<String>,
S: Into<StatusCode>,
{
ApiErrorData {
status: status.into(),
message: message.into(),
type_url: None,
fields: HashMap::new(),
title: None,
}
}
pub fn with_type_url<T: Into<String>>(mut self, type_url: T) -> Self {
self.type_url = Some(type_url.into());
self
}
pub fn with_field_value<T: Into<String>>(mut self, name: T, value: Value) -> Self {
self.fields.insert(name.into(), value);
self
}
pub fn with_field<T: Into<String>, V: Serialize>(
mut self,
name: T,
value: V,
) -> Result<Self, Error> {
if let Err(err) = self.try_add_field(name, value) {
Err(err)
} else {
Ok(self)
}
}
pub fn with_field_careless<T: Into<String>, V: Serialize>(mut self, name: T, value: V) -> Self {
self.add_field(name, value);
self
}
pub fn add_field<T: Into<String>, V: Serialize>(&mut self, name: T, value: V) -> bool {
self.try_add_field(name, value).is_ok()
}
pub fn try_add_field<T: Into<String>, V: Serialize>(
&mut self,
name: T,
value: V,
) -> Result<(), Error> {
match serde_json::to_value(value) {
Ok(value) => {
self.fields.insert(name.into(), value);
Ok(())
}
Err(err) => Err(err.into()),
}
}
pub fn change_status<T: Into<StatusCode>>(mut self, new_status: T) -> Self {
self.status = new_status.into();
self
}
pub fn change_message<T: Into<String>>(mut self, new_message: T) -> Self {
self.message = new_message.into();
self
}
pub fn with_title<S>(mut self, title: S) -> Self
where
S: Into<String>,
{
self.title = Some(title.into());
self
}
pub fn to_http_api_problem(&self) -> HttpApiProblem {
let mut problem = HttpApiProblem::with_title_and_type_from_status(self.status)
.set_detail(self.message.clone());
if let Some(custom_type_url) = self.type_url.as_ref() {
problem.type_url = Some(custom_type_url.to_string())
}
if self.status != StatusCode::UNAUTHORIZED {
for (key, value) in self.fields.iter() {
let _ = problem.set_value(key.to_string(), value);
}
}
if let Some(title) = self.title.as_ref() {
problem.title = title.to_owned();
}
problem
}
}
impl<S, M, F> From<(S, M, F)> for ApiError
where
S: Into<StatusCode>,
M: Into<String>,
F: Fail,
{
fn from(v: (S, M, F)) -> Self {
let (status, message, fail) = v;
let data = (status, message).into();
fail.context(data).into()
}
}
impl From<Context<ApiErrorData>> for ApiError {
fn from(context: Context<ApiErrorData>) -> Self {
ApiError { inner: context }
}
}
impl From<ApiErrorData> for ApiError {
fn from(error_data: ApiErrorData) -> Self {
Context::new(error_data).into()
}
}
impl<S, M> From<(S, M)> for ApiErrorData
where
S: Into<StatusCode>,
M: Into<String>,
{
fn from(v: (S, M)) -> Self {
let (status, message) = v;
Self::new(status, message)
}
}
impl From<ApiError> for HttpApiProblem {
fn from(error: ApiError) -> Self {
let mut problem = HttpApiProblem::with_title_and_type_from_status(error.status())
.set_detail(error.message());
if let Some(custom_type_url) = error.type_url() {
problem.type_url = Some(custom_type_url.to_string())
}
if error.status() != StatusCode::UNAUTHORIZED {
for (key, value) in error.fields().iter() {
let _ = problem.set_value(key.to_string(), value);
}
}
if let Some(title) = error.title() {
problem.title = title.to_string();
}
problem
}
}
impl From<io::Error> for ApiError {
fn from(error: io::Error) -> Self {
let data = ApiErrorData::new(
StatusCode::INTERNAL_SERVER_ERROR,
"An internal error IO error occurred",
);
error.context(data).into()
}
}
#[cfg(feature = "with_hyper")]
impl From<hyper::error::Error> for ApiError {
fn from(error: hyper::error::Error) -> Self {
let data = ApiErrorData::new(
StatusCode::INTERNAL_SERVER_ERROR,
"An internal error caused by hyper occurred",
);
error.context(data).into()
}
}
#[cfg(feature = "with_actix_web")]
impl From<actix::prelude::MailboxError> for ApiError {
fn from(error: actix::prelude::MailboxError) -> Self {
let data = ApiErrorData::new(
StatusCode::INTERNAL_SERVER_ERROR,
"An internal error caused by internal messaging occured",
);
error.context(data).into()
}
}
#[cfg(feature = "with_hyper")]
impl From<ApiError> for hyper::Response<hyper::Body> {
fn from(error: ApiError) -> hyper::Response<hyper::Body> {
error.to_hyper_response()
}
}
#[cfg(feature = "with_actix_web")]
impl From<ApiError> for actix_web::HttpResponse {
fn from(error: ApiError) -> Self {
error.to_actix_web_response()
}
}
#[cfg(feature = "with_actix_web")]
impl actix_web::error::ResponseError for ApiError {
fn error_response(&self) -> actix_web::HttpResponse {
let json = self.to_http_api_problem().json_bytes();
let actix_status = actix_web::http::StatusCode::from_u16(self.status().as_u16())
.unwrap_or(actix_web::http::StatusCode::INTERNAL_SERVER_ERROR);
let response = actix_web::HttpResponse::build(actix_status)
.header(
actix_web::http::header::CONTENT_TYPE,
PROBLEM_JSON_MEDIA_TYPE,
)
.body(json);
response
}
}