1use axum::{
2 http::StatusCode,
3 response::{IntoResponse, Response},
4 Json,
5};
6use serde::Serialize;
7use serde_json::Value;
8
9use crate::ValidationErrors;
10
11#[derive(Serialize)]
22struct ErrorBody {
23 #[serde(rename = "statusCode")]
24 status_code: u16,
25 error: String,
26 message: String,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 details: Option<Value>,
29}
30
31#[derive(Debug, Clone)]
36pub struct HttpException {
37 pub status: StatusCode,
38 pub message: String,
39 pub details: Option<Value>,
40}
41
42impl HttpException {
43 pub fn new(status: StatusCode, message: impl Into<String>) -> Self {
47 Self {
48 status,
49 message: message.into(),
50 details: None,
51 }
52 }
53
54 pub fn with_details(status: StatusCode, message: impl Into<String>, details: Value) -> Self {
55 Self {
56 status,
57 message: message.into(),
58 details: Some(details),
59 }
60 }
61
62 pub fn bad_request(message: impl Into<String>) -> Self {
66 Self::new(StatusCode::BAD_REQUEST, message)
67 }
68
69 pub fn bad_request_validation(errors: ValidationErrors) -> Self {
70 let message = "Validation failed".to_string();
71 let details = serde_json::to_value(errors).unwrap_or(Value::Null);
72 Self::with_details(StatusCode::BAD_REQUEST, message, details)
73 }
74
75 pub fn unauthorized(message: impl Into<String>) -> Self {
76 Self::new(StatusCode::UNAUTHORIZED, message)
77 }
78
79 pub fn not_found(message: impl Into<String>) -> Self {
80 Self::new(StatusCode::NOT_FOUND, message)
81 }
82
83 pub fn internal_server_error(message: impl Into<String>) -> Self {
84 Self::new(StatusCode::INTERNAL_SERVER_ERROR, message)
85 }
86}
87
88impl IntoResponse for HttpException {
96 fn into_response(self) -> Response {
97 let error_name = self
98 .status
99 .canonical_reason()
100 .unwrap_or("Error")
101 .to_string();
102
103 let body = ErrorBody {
104 status_code: self.status.as_u16(),
105 error: error_name,
106 message: self.message,
107 details: self.details,
108 };
109
110 (self.status, Json(body)).into_response()
111 }
112}