axum_webtools/http/
response.rs1use axum::http::StatusCode;
2use axum::response::IntoResponse;
3use axum::Json;
4use derive_more::with_trait::Display;
5use serde::{Deserialize, Serialize};
6use serde_json::json;
7use std::fmt;
8use std::fmt::{Debug, Formatter};
9use thiserror::Error;
10use validator::{ValidationError, ValidationErrors};
11
12#[derive(Debug)]
13pub struct HttpErrorDetails {
14 pub message: String,
15 pub status_code: StatusCode,
16 pub headers: Vec<(String, String)>,
17}
18
19#[derive(Debug, Error, Display)]
20pub enum HttpError {
21 #[error(transparent)]
22 SqlxError(#[from] sqlx::Error),
23 WithDetails(HttpErrorDetails),
24 ValidationError(ValidationErrorResponse),
25}
26
27#[derive(Debug, Serialize, Deserialize, Clone)]
28pub struct ValidationErrorResponse {
29 pub validation_errors: Vec<ValidationError>,
30}
31
32impl ValidationErrorResponse {
33 pub fn from(validation_errors: ValidationErrors) -> ValidationErrorResponse {
34 let validation_errors = validation_errors
35 .field_errors()
36 .into_values()
37 .flat_map(|v| v.clone())
38 .collect();
39
40 ValidationErrorResponse { validation_errors }
41 }
42}
43
44impl Display for ValidationErrorResponse {
45 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
46 write!(f, "{:?}", self.validation_errors)
47 }
48}
49
50impl From<ValidationErrors> for HttpError {
51 fn from(validation_errors: ValidationErrors) -> Self {
52 HttpError::ValidationError(ValidationErrorResponse::from(validation_errors))
53 }
54}
55
56impl Display for HttpErrorDetails {
57 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
58 let headers = self
59 .headers
60 .iter()
61 .map(|(k, v)| format!("{}: {}", k, v))
62 .collect::<Vec<String>>()
63 .join(", ");
64 write!(
65 f,
66 "{:?}: {:?} ({:?})",
67 self.status_code, self.message, headers
68 )
69 }
70}
71
72impl IntoResponse for HttpError {
73 fn into_response(self) -> axum::response::Response {
74 let (status_code, message) = match self {
75 Self::SqlxError(sqlx_error) => (
76 StatusCode::INTERNAL_SERVER_ERROR,
77 format!("{:?}", sqlx_error),
78 ),
79 Self::WithDetails(details) => (details.status_code, details.message),
80 Self::ValidationError(validation_error_response) => (
81 StatusCode::BAD_REQUEST,
82 format!("{:?}", validation_error_response),
83 ),
84 };
85
86 let body = Json(json!({
87 "message": message
88 }));
89 (status_code, body).into_response()
90 }
91}
92
93macro_rules! http_error {
94 ($name:ident,$status_code:expr) => {
95 #[allow(missing_docs, unused)]
96 pub fn $name<T>(message: impl Into<String>) -> Result<T, HttpError> {
97 Err(HttpError::WithDetails(HttpErrorDetails {
98 message: message.into(),
99 status_code: $status_code,
100 headers: vec![],
101 }))
102 }
103 };
104}
105
106http_error!(conflict, StatusCode::CONFLICT);
107
108http_error!(unauthorized, StatusCode::UNAUTHORIZED);
109
110http_error!(bad_request, StatusCode::BAD_REQUEST);
111
112http_error!(not_found, StatusCode::NOT_FOUND);
113
114http_error!(internal_server_error, StatusCode::INTERNAL_SERVER_ERROR);
115
116macro_rules! http_response {
117 ($name:ident,$status:expr) => {
118 #[allow(non_snake_case, missing_docs)]
119 pub fn $name(
120 value: impl Serialize + 'static,
121 ) -> Result<axum::response::Response, HttpError> {
122 Ok(($status, Json(value)).into_response())
123 }
124 };
125}
126
127http_response!(ok, StatusCode::OK);