1use axum::Json;
2use axum::http::StatusCode;
3use axum::response::IntoResponse;
4use fusion_core::DataError;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8pub type WebResult<T> = core::result::Result<Json<T>, WebError>;
9
10#[derive(Debug, Serialize, Deserialize)]
12#[cfg_attr(
13 feature = "with-openapi",
14 derive(utoipa::ToSchema, utoipa::ToResponse),
15 response(description = "A default error response for most API errors.")
16)]
17pub struct WebError {
18 pub err_code: i32,
24
25 pub err_msg: String,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub detail: Option<Value>,
31}
32
33impl WebError {
34 pub fn new(err_code: i32, err_msg: impl Into<String>, detail: Option<Value>) -> Self {
35 Self { err_code, err_msg: err_msg.into(), detail }
36 }
37
38 pub fn new_with_msg(err_msg: impl Into<String>) -> Self {
39 Self::new(500, err_msg, None)
40 }
41
42 pub fn new_with_code(err_code: i32, err_msg: impl Into<String>) -> Self {
43 Self::new(err_code, err_msg, None)
44 }
45
46 pub fn server_error_with_detail(err_msg: impl Into<String>, detail: Value) -> Self {
47 Self::new(500, err_msg, Some(detail))
48 }
49
50 pub fn with_err_code(mut self, err_code: i32) -> Self {
51 self.err_code = err_code;
52 self
53 }
54
55 pub fn with_details(mut self, details: Value) -> Self {
56 if details == Value::Null {
57 self.detail = None
58 } else {
59 self.detail = Some(details);
60 }
61 self
62 }
63
64 pub fn with_err_msg(mut self, err_msg: impl Into<String>) -> Self {
65 self.err_msg = err_msg.into();
66 self
67 }
68
69 pub fn unauthorized(err_msg: impl Into<String>) -> Self {
71 Self::new_with_code(401, err_msg)
72 }
73
74 pub fn forbidden(err_msg: impl Into<String>) -> Self {
76 Self::new_with_code(403, err_msg)
77 }
78
79 pub fn bad_request(err_msg: impl Into<String>) -> Self {
81 Self::new_with_code(400, err_msg)
82 }
83
84 pub fn bad_gateway(err_msg: impl Into<String>) -> Self {
86 Self::new_with_code(502, err_msg)
87 }
88}
89
90impl IntoResponse for WebError {
91 fn into_response(self) -> axum::response::Response {
92 let status = StatusCode::from_u16(self.err_code as u16).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
93 let mut res = axum::Json(self).into_response();
94 *res.status_mut() = status;
95 res
96 }
97}
98
99impl From<DataError> for WebError {
100 fn from(err: DataError) -> Self {
101 if let Some(source) = err.source.as_ref() {
103 log::error!("DataError with code {}, msg {} has source: {:?}", err.code, err.msg, source);
104 }
105
106 let mut web_error = Self::new_with_msg(err.msg.clone()).with_err_code(err.code);
107
108 if let Some(data) = err.data.as_ref() {
110 web_error = web_error.with_details(data.clone());
111 }
112
113 web_error
114 }
115}
116
117impl From<hyper::Error> for WebError {
118 fn from(value: hyper::Error) -> Self {
119 WebError::new_with_code(500, value.to_string())
120 }
121}
122
123impl From<serde_json::Error> for WebError {
124 fn from(value: serde_json::Error) -> Self {
125 WebError::new_with_code(500, value.to_string())
126 }
127}