1use crate::error::helpers::make_http_error_response;
2use crate::http::response::anyhow::helpers::make_status_code;
3use foxtive::Error;
4use foxtive::prelude::AppMessage;
5#[cfg(feature = "multipart")]
6use foxtive_ntex_multipart::{ErrorMessage as MultipartErrorMessage, MultipartError};
7use ntex::http::StatusCode;
8use ntex::http::error::PayloadError;
9use ntex::web::error::{BlockingError, JsonError};
10use ntex::web::{HttpRequest, HttpResponse, WebResponseError};
11use std::string::FromUtf8Error;
12use thiserror::Error;
13use tokio::task::JoinError;
14
15#[derive(Error, Debug)]
16pub enum HttpError {
17 #[error("{0}")]
18 Std(Box<dyn std::error::Error + Send + Sync + 'static>),
19 #[error("{0}")]
20 AppError(#[from] Error),
21 #[error("{0}")]
22 AppMessage(#[from] AppMessage),
23 #[error("Payload Error: {0}")]
24 PayloadError(#[from] PayloadError),
25 #[error("Join Error: {0}")]
26 JoinError(#[from] JoinError),
27 #[error("Utf8 Error: {0}")]
28 Utf8Error(#[from] FromUtf8Error),
29 #[error("Json Error: {0}")]
30 JsonError(#[from] JsonError),
31 #[cfg(feature = "validator")]
32 #[error("Validation Error: {0}")]
33 ValidationError(#[from] validator::ValidationErrors),
34 #[cfg(feature = "multipart")]
35 #[error("Multipart Error: {0}")]
36 MultipartError(#[from] MultipartError),
37}
38
39impl HttpError {
40 pub fn into_app_error(self) -> foxtive::Error {
41 foxtive::Error::from(self)
42 }
43}
44
45impl From<Box<dyn std::error::Error + Send + Sync>> for HttpError {
46 fn from(error: Box<dyn std::error::Error + Send + Sync>) -> Self {
47 HttpError::Std(error)
48 }
49}
50
51impl From<BlockingError<Error>> for HttpError {
52 fn from(value: BlockingError<Error>) -> Self {
53 match value {
54 BlockingError::Error(e) => HttpError::AppError(e),
55 BlockingError::Canceled => HttpError::AppMessage(AppMessage::InternalServerError),
56 }
57 }
58}
59
60impl WebResponseError for HttpError {
61 fn status_code(&self) -> StatusCode {
62 match self {
63 HttpError::AppMessage(m) => m.status_code(),
64 HttpError::AppError(e) => make_status_code(e),
65 #[cfg(feature = "validator")]
66 HttpError::ValidationError(_) => StatusCode::BAD_REQUEST,
67 HttpError::PayloadError(_) | HttpError::JsonError(_) => StatusCode::BAD_REQUEST,
68 #[cfg(feature = "multipart")]
69 HttpError::MultipartError(err) => match err {
70 MultipartError::ValidationError(err) => match err.error {
71 MultipartErrorMessage::InvalidFileExtension(_, _, _)
72 | MultipartErrorMessage::InvalidContentType(_, _, _) => {
73 StatusCode::UNSUPPORTED_MEDIA_TYPE
74 }
75 _ => StatusCode::BAD_REQUEST,
76 },
77 _ => StatusCode::BAD_REQUEST,
78 },
79 _ => StatusCode::INTERNAL_SERVER_ERROR,
80 }
81 }
82
83 fn error_response(&self, _: &HttpRequest) -> HttpResponse {
84 make_http_error_response(self)
85 }
86}
87
88pub(crate) mod helpers {
89 use crate::enums::ResponseCode;
90 use crate::http::HttpError;
91 use crate::http::responder::Responder;
92 use crate::http::response::anyhow::helpers::make_response;
93 use foxtive::prelude::AppMessage;
94 #[cfg(feature = "multipart")]
95 use foxtive_ntex_multipart::MultipartError;
96 use ntex::web::HttpResponse;
97 use tracing::error;
98
99 pub(crate) fn make_http_error_response(err: &HttpError) -> HttpResponse {
100 match err {
101 HttpError::AppMessage(m) => make_response(&m.clone().ae()),
102 HttpError::AppError(e) => make_response(e),
103 #[cfg(feature = "validator")]
104 HttpError::ValidationError(e) => {
105 Responder::send_msg(e.errors(), ResponseCode::BadRequest, "Validation Error")
106 }
107 HttpError::PayloadError(e) => {
108 error!("Payload Error: {e}");
109 Responder::send_msg(e.to_string(), ResponseCode::BadRequest, "Payload Error")
110 }
111 HttpError::JsonError(e) => {
112 error!("Json Error: {e}");
113 Responder::send_msg(
114 e.to_string(),
115 ResponseCode::BadRequest,
116 "Json Payload Error",
117 )
118 }
119 #[cfg(feature = "multipart")]
120 HttpError::MultipartError(err) => match err {
121 MultipartError::ValidationError(val) => {
122 let msg = err.to_string();
123 Responder::send_msg(
124 serde_json::json!({"field": val.name, "error": msg}),
125 ResponseCode::BadRequest,
126 &msg,
127 )
128 }
129 _ => {
130 let msg = err.to_string();
131 Responder::send_msg(
132 serde_json::json!({"message": msg}),
133 ResponseCode::BadRequest,
134 &msg,
135 )
136 }
137 },
138 _ => {
139 error!("Error: {err}");
140 make_response(&foxtive::Error::from(AppMessage::InternalServerError))
141 }
142 }
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use foxtive::Error;
150
151 #[test]
152 fn test_app_error() {
153 let error = HttpError::AppError(Error::from(AppMessage::InternalServerError));
154 let app_error = make_http_error_response(&error);
155 assert_eq!(app_error.status(), 500);
156 }
157
158 #[test]
159 fn test_app_message() {
160 let error = HttpError::AppMessage(AppMessage::InternalServerError);
161 let app_error = make_http_error_response(&error);
162 assert_eq!(app_error.status(), 500);
163 }
164
165 #[test]
166 fn test_std_error() {
167 #[allow(clippy::io_other_error)]
168 let error = HttpError::Std(Box::new(std::io::Error::new(
169 std::io::ErrorKind::Other,
170 "Test",
171 )));
172 let app_error = make_http_error_response(&error);
173 assert_eq!(app_error.status(), 500);
174 }
175
176 #[test]
177 fn test_payload_error() {
178 let error = HttpError::PayloadError(PayloadError::Overflow);
179 let app_error = make_http_error_response(&error);
180 assert_eq!(app_error.status(), 400);
181 }
182
183 #[cfg(feature = "validator")]
184 #[test]
185 fn test_validation_error() {
186 let error = HttpError::ValidationError(validator::ValidationErrors::new());
187 let app_error = make_http_error_response(&error);
188 assert_eq!(app_error.status(), 400);
189 }
190
191 #[cfg(feature = "multipart")]
192 #[test]
193 fn test_multipart_error() {
194 use foxtive_ntex_multipart::InputError;
195
196 let error = HttpError::MultipartError(MultipartError::ValidationError(InputError {
197 error: MultipartErrorMessage::InvalidFileExtension(
198 "image".to_string(),
199 "invalid ext".to_string(),
200 Some("mp4".to_string()),
201 ),
202 name: "image".to_string(),
203 }));
204
205 let app_error = make_http_error_response(&error);
206
207 assert_eq!(app_error.status(), 400);
208 }
209}