use crate::error::helpers::make_http_error_response;
use crate::http::response::anyhow::helpers::make_status_code;
use foxtive::Error;
use foxtive::prelude::AppMessage;
#[cfg(feature = "multipart")]
use foxtive_ntex_multipart::{ErrorMessage as MultipartErrorMessage, MultipartError};
use ntex::http::StatusCode;
use ntex::http::error::PayloadError;
use ntex::web::error::{BlockingError, JsonError};
use ntex::web::{HttpRequest, HttpResponse, WebResponseError};
use std::string::FromUtf8Error;
use thiserror::Error;
use tokio::task::JoinError;
#[derive(Error, Debug)]
pub enum HttpError {
#[error("{0}")]
Std(Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("{0}")]
AppError(#[from] Error),
#[error("{0}")]
AppMessage(#[from] AppMessage),
#[error("Payload Error: {0}")]
PayloadError(#[from] PayloadError),
#[error("Join Error: {0}")]
JoinError(#[from] JoinError),
#[error("Utf8 Error: {0}")]
Utf8Error(#[from] FromUtf8Error),
#[error("Json Error: {0}")]
JsonError(#[from] JsonError),
#[cfg(feature = "validator")]
#[error("Validation Error: {0}")]
ValidationError(#[from] validator::ValidationErrors),
#[cfg(feature = "multipart")]
#[error("Multipart Error: {0}")]
MultipartError(#[from] MultipartError),
}
impl HttpError {
pub fn into_app_error(self) -> foxtive::Error {
foxtive::Error::from(self)
}
}
impl From<Box<dyn std::error::Error + Send + Sync>> for HttpError {
fn from(error: Box<dyn std::error::Error + Send + Sync>) -> Self {
HttpError::Std(error)
}
}
impl From<BlockingError<Error>> for HttpError {
fn from(value: BlockingError<Error>) -> Self {
match value {
BlockingError::Error(e) => HttpError::AppError(e),
BlockingError::Canceled => {
HttpError::AppMessage(AppMessage::internal_server_error("Internal Server Error"))
}
}
}
}
impl WebResponseError for HttpError {
fn status_code(&self) -> StatusCode {
match self {
HttpError::AppMessage(m) => m.status_code(),
HttpError::AppError(e) => make_status_code(e),
#[cfg(feature = "validator")]
HttpError::ValidationError(_) => StatusCode::BAD_REQUEST,
HttpError::PayloadError(_) | HttpError::JsonError(_) => StatusCode::BAD_REQUEST,
#[cfg(feature = "multipart")]
HttpError::MultipartError(err) => match err {
MultipartError::ValidationError(err) => match err.error {
MultipartErrorMessage::InvalidFileExtension(_, _, _)
| MultipartErrorMessage::InvalidContentType(_, _, _) => {
StatusCode::UNSUPPORTED_MEDIA_TYPE
}
_ => StatusCode::BAD_REQUEST,
},
_ => StatusCode::BAD_REQUEST,
},
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self, _: &HttpRequest) -> HttpResponse {
make_http_error_response(self)
}
}
pub(crate) mod helpers {
use crate::enums::ResponseCode;
use crate::http::HttpError;
use crate::http::responder::Responder;
use crate::http::response::anyhow::helpers::make_response;
use foxtive::prelude::AppMessage;
#[cfg(feature = "multipart")]
use foxtive_ntex_multipart::MultipartError;
use ntex::web::HttpResponse;
use tracing::error;
pub(crate) fn make_http_error_response(err: &HttpError) -> HttpResponse {
match err {
HttpError::AppMessage(m) => make_response(&m.clone().into_anyhow()),
HttpError::AppError(e) => make_response(e),
#[cfg(feature = "validator")]
HttpError::ValidationError(e) => {
Responder::send_msg(e.errors(), ResponseCode::BadRequest, "Validation Error")
}
HttpError::PayloadError(e) => {
error!("Payload Error: {e}");
Responder::send_msg(e.to_string(), ResponseCode::BadRequest, "Payload Error")
}
HttpError::JsonError(e) => {
error!("Json Error: {e}");
Responder::send_msg(
e.to_string(),
ResponseCode::BadRequest,
"Json Payload Error",
)
}
#[cfg(feature = "multipart")]
HttpError::MultipartError(err) => match err {
MultipartError::ValidationError(val) => {
let msg = err.to_string();
Responder::send_msg(
serde_json::json!({"field": val.name, "error": msg}),
ResponseCode::BadRequest,
&msg,
)
}
_ => {
let msg = err.to_string();
Responder::send_msg(
serde_json::json!({"message": msg}),
ResponseCode::BadRequest,
&msg,
)
}
},
_ => {
error!("Error: {err}");
make_response(&foxtive::Error::from(AppMessage::internal_server_error(
"Internal Server Error",
)))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use foxtive::Error;
#[test]
fn test_app_error() {
let error = HttpError::AppError(Error::from(AppMessage::internal_server_error(
"Internal Server Error",
)));
let app_error = make_http_error_response(&error);
assert_eq!(app_error.status(), 500);
}
#[test]
fn test_app_message() {
let error = HttpError::AppMessage(AppMessage::internal_server_error("Error"));
let app_error = make_http_error_response(&error);
assert_eq!(app_error.status(), 500);
}
#[test]
fn test_std_error() {
#[allow(clippy::io_other_error)]
let error = HttpError::Std(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"Test",
)));
let app_error = make_http_error_response(&error);
assert_eq!(app_error.status(), 500);
}
#[test]
fn test_payload_error() {
let error = HttpError::PayloadError(PayloadError::Overflow);
let app_error = make_http_error_response(&error);
assert_eq!(app_error.status(), 400);
}
#[cfg(feature = "validator")]
#[test]
fn test_validation_error() {
let error = HttpError::ValidationError(validator::ValidationErrors::new());
let app_error = make_http_error_response(&error);
assert_eq!(app_error.status(), 400);
}
#[cfg(feature = "multipart")]
#[test]
fn test_multipart_error() {
use foxtive_ntex_multipart::InputError;
let error = HttpError::MultipartError(MultipartError::ValidationError(InputError {
error: MultipartErrorMessage::InvalidFileExtension(
"image".to_string(),
"invalid ext".to_string(),
Some("mp4".to_string()),
),
name: "image".to_string(),
}));
let app_error = make_http_error_response(&error);
assert_eq!(app_error.status(), 400);
}
}