fusion_web/
error.rs

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/// A default error response for most API errors.
11#[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  /// A unique error ID.
19  // TODO 应从 tracing 中获取
20  // pub err_id: Ulid,
21
22  /// A unique error code.
23  pub err_code: i32,
24
25  /// An error message.
26  pub err_msg: String,
27
28  /// Optional Additional error details.
29  #[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  /// Create a 401 Unauthorized error
70  pub fn unauthorized(err_msg: impl Into<String>) -> Self {
71    Self::new_with_code(401, err_msg)
72  }
73
74  /// Create a 403 Forbidden error
75  pub fn forbidden(err_msg: impl Into<String>) -> Self {
76    Self::new_with_code(403, err_msg)
77  }
78
79  /// Create a 400 Bad Request error
80  pub fn bad_request(err_msg: impl Into<String>) -> Self {
81    Self::new_with_code(400, err_msg)
82  }
83
84  /// Create a 502 Bad Gateway error
85  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    // Log the source error if present
102    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    // Add details if present
109    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}