fusion_core/
data_error.rs

1use std::net::AddrParseError;
2
3use config::ConfigError;
4use fusion_common::ctx::CtxError;
5use serde::Serialize;
6use serde_json::json;
7
8use crate::{configuration::ConfigureError, security::Error as SecurityError};
9
10#[derive(Debug, Serialize)]
11pub struct DataError {
12  pub code: i32,
13  pub msg: String,
14  #[serde(skip_serializing_if = "Option::is_none")]
15  pub data: Option<serde_json::Value>,
16  #[serde(skip)]
17  pub source: Option<Box<dyn core::error::Error + Send + Sync>>,
18}
19
20impl fusion_common::DataError for DataError {
21  fn code(&self) -> i32 {
22    self.code
23  }
24
25  fn msg(&self) -> &str {
26    &self.msg
27  }
28
29  fn data(&self) -> Option<&serde_json::Value> {
30    self.data.as_ref()
31  }
32
33  fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
34    self.source.as_ref().map(|e| &**e as &(dyn core::error::Error + 'static))
35  }
36}
37
38impl core::error::Error for DataError {
39  fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
40    self.source.as_ref().map(|e| &**e as &(dyn core::error::Error + 'static))
41  }
42}
43
44impl core::fmt::Display for DataError {
45  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46    write!(f, "{}:{}", self.code, self.msg)
47  }
48}
49
50impl DataError {
51  pub fn bad_request(msg: impl Into<String>) -> Self {
52    Self { code: 400, msg: msg.into(), data: None, source: None }
53  }
54
55  pub fn not_found(msg: impl Into<String>) -> Self {
56    Self { code: 404, msg: msg.into(), data: None, source: None }
57  }
58
59  pub fn conflicted(msg: impl Into<String>) -> Self {
60    Self { code: 409, msg: msg.into(), data: None, source: None }
61  }
62
63  pub fn unauthorized(msg: impl Into<String>) -> Self {
64    Self { code: 401, msg: msg.into(), data: None, source: None }
65  }
66
67  pub fn forbidden(msg: impl Into<String>) -> Self {
68    Self { code: 403, msg: msg.into(), data: None, source: None }
69  }
70
71  pub fn server_error(msg: impl Into<String>) -> Self {
72    Self { code: 500, msg: msg.into(), data: None, source: None }
73  }
74
75  pub fn biz_error(code: i32, msg: impl Into<String>, data: Option<serde_json::Value>) -> Self {
76    Self { code, msg: msg.into(), data, source: None }
77  }
78
79  pub fn internal(
80    code: i32,
81    msg: impl Into<String>,
82    source: Option<Box<dyn core::error::Error + Send + Sync>>,
83  ) -> Self {
84    Self { code, msg: msg.into(), data: None, source }
85  }
86
87  pub fn retry_limit(msg: impl Into<String>, retry_limit: u32) -> Self {
88    let detail = json!({ "retry_limit": retry_limit });
89    Self { code: 1429, msg: msg.into(), data: Some(detail), source: None }
90  }
91}
92
93impl From<fusion_common::Error> for DataError {
94  fn from(value: fusion_common::Error) -> Self {
95    DataError::server_error(value.to_string())
96  }
97}
98
99impl From<std::time::SystemTimeError> for DataError {
100  fn from(value: std::time::SystemTimeError) -> Self {
101    Self::internal(500, "SystemTimeError", Some(Box::new(value)))
102  }
103}
104
105impl From<std::io::Error> for DataError {
106  fn from(value: std::io::Error) -> Self {
107    let error_msg = value.to_string();
108    DataError::internal(500, format!("IO error: {}", error_msg), Some(Box::new(value)))
109  }
110}
111
112impl From<serde_json::Error> for DataError {
113  fn from(value: serde_json::Error) -> Self {
114    DataError::internal(500, "JSON error", Some(Box::new(value)))
115  }
116}
117
118impl<T> From<tokio::sync::mpsc::error::SendError<T>> for DataError
119where
120  T: Send + Sync + 'static,
121{
122  fn from(e: tokio::sync::mpsc::error::SendError<T>) -> Self {
123    let compatible_error: Box<dyn std::error::Error + Send + Sync + 'static> = Box::new(e);
124    DataError::internal(500, "channel send error", Some(compatible_error))
125  }
126}
127
128impl From<tokio::sync::oneshot::error::RecvError> for DataError {
129  fn from(e: tokio::sync::oneshot::error::RecvError) -> Self {
130    // tokio::sync::oneshot::error::RecvError implements Send + Sync
131    let compatible_error: Box<dyn std::error::Error + Send + Sync + 'static> = Box::new(e);
132    DataError::internal(500, "channel recv error", Some(compatible_error))
133  }
134}
135
136impl From<tokio::task::JoinError> for DataError {
137  fn from(value: tokio::task::JoinError) -> Self {
138    // tokio::task::JoinError implements Send + Sync
139    let compatible_error: Box<dyn std::error::Error + Send + Sync + 'static> = Box::new(value);
140    DataError::internal(500, "Join tokio task error", Some(compatible_error))
141  }
142}
143
144impl From<ConfigError> for DataError {
145  fn from(value: ConfigError) -> Self {
146    DataError::server_error(format!("Config load error: {}", value))
147  }
148}
149
150impl From<AddrParseError> for DataError {
151  fn from(value: AddrParseError) -> Self {
152    DataError::server_error(format!("Addr parse error: {}", value))
153  }
154}
155
156impl From<CtxError> for DataError {
157  fn from(value: CtxError) -> Self {
158    match value {
159      CtxError::Unauthorized(msg) => DataError::unauthorized(msg),
160      CtxError::InvalidPayload => DataError::unauthorized("Invalid ctx payload"),
161    }
162  }
163}
164
165#[cfg(feature = "with-uuid")]
166impl From<uuid::Error> for DataError {
167  fn from(value: uuid::Error) -> Self {
168    DataError::internal(500, value.to_string(), None)
169  }
170}
171
172impl From<ConfigureError> for DataError {
173  fn from(value: ConfigureError) -> Self {
174    DataError::server_error(value.to_string())
175  }
176}
177
178#[cfg(feature = "tonic")]
179impl From<protobuf::ParseError> for DataError {
180  fn from(value: protobuf::ParseError) -> Self {
181    DataError::biz_error(400, format!("Protobuf parse error: {}", value), None)
182  }
183}
184
185#[cfg(feature = "tonic")]
186impl From<protobuf::SerializeError> for DataError {
187  fn from(value: protobuf::SerializeError) -> Self {
188    // protobuf::SerializeError might not implement Send + Sync, so we'll convert it to a string
189    let source: Option<Box<dyn core::error::Error + Send + Sync>> = Some(Box::new(value));
190    DataError::internal(500, "Protobuf serialize error", source)
191  }
192}
193
194#[cfg(feature = "tonic")]
195impl From<tonic::transport::Error> for DataError {
196  fn from(value: tonic::transport::Error) -> Self {
197    DataError::server_error(format!("Grpc transport error: {}", value))
198  }
199}
200
201#[cfg(feature = "tonic")]
202impl From<tonic::Status> for DataError {
203  fn from(value: tonic::Status) -> Self {
204    // TODO 更精细的 gRPC 状态转换
205    let msg = value.message();
206    match value.code() {
207      tonic::Code::Cancelled => DataError::server_error(msg),
208      tonic::Code::Unknown => DataError::server_error(msg),
209      tonic::Code::InvalidArgument => DataError::bad_request(msg),
210      tonic::Code::DeadlineExceeded => DataError::server_error(msg),
211      tonic::Code::NotFound => DataError::not_found(msg),
212      tonic::Code::AlreadyExists => DataError::conflicted(msg),
213      tonic::Code::PermissionDenied => DataError::server_error(msg),
214      tonic::Code::ResourceExhausted => DataError::server_error(msg),
215      tonic::Code::FailedPrecondition => DataError::forbidden(msg),
216      tonic::Code::Aborted => DataError::server_error(msg),
217      tonic::Code::OutOfRange => DataError::bad_request(msg),
218      tonic::Code::Unimplemented => DataError::server_error(msg),
219      tonic::Code::Internal => DataError::server_error(msg),
220      tonic::Code::Unavailable => DataError::server_error(msg),
221      tonic::Code::DataLoss => DataError::server_error(msg),
222      tonic::Code::Unauthenticated => DataError::unauthorized(msg),
223      // TODO 存在 Ok -> DataError::internal 的转换吗?
224      tonic::Code::Ok => DataError::internal(0, "", None),
225    }
226  }
227}
228
229#[cfg(feature = "tonic")]
230impl From<DataError> for tonic::Status {
231  fn from(value: DataError) -> Self {
232    let code = match value.code {
233      400 => tonic::Code::InvalidArgument,
234      401 => tonic::Code::Unauthenticated,
235      403 => tonic::Code::PermissionDenied,
236      404 => tonic::Code::NotFound,
237      409 => tonic::Code::Aborted,
238      413 => tonic::Code::ResourceExhausted,
239      429 => tonic::Code::Unavailable,
240      500 => tonic::Code::Internal,
241      501 => tonic::Code::Unimplemented,
242      503 => tonic::Code::Unavailable,
243      504 => tonic::Code::DeadlineExceeded,
244      505 => tonic::Code::Unavailable,
245      0 | (200..=299) => tonic::Code::Ok,
246      _ => tonic::Code::Unknown,
247    };
248    let mut status = tonic::Status::new(code, value.msg);
249    // if let Some(detail) = value.detail {
250    //   status.set_details(detail);
251    // }
252    if let Some(e) = value.source {
253      // Convert the boxed error to an Arc to satisfy the set_source method
254      // We need to use Arc::from to properly handle the conversion from Box<T> to Arc<T>
255      let arc_error = std::sync::Arc::from(e);
256      status.set_source(arc_error);
257    }
258    status
259  }
260}
261
262#[cfg(feature = "fusionsql")]
263impl From<fusionsql::SqlError> for DataError {
264  fn from(value: fusionsql::SqlError) -> Self {
265    match value {
266      fusionsql::SqlError::Unauthorized(e) => DataError::unauthorized(e),
267      fusionsql::SqlError::InvalidArgument { message } => DataError::bad_request(format!("InvalidArgument, {message}")),
268      fusionsql::SqlError::EntityNotFound { schema, entity, id } => {
269        DataError::not_found(format!("EntityNotFound, {}:{}:{}", schema.unwrap_or_default(), entity, id))
270      }
271      fusionsql::SqlError::NotFound { schema, table, sql } => {
272        log::debug!("NotFound, schema: {}, table: {}, sql: {}", schema.unwrap_or_default(), table, sql);
273        DataError::not_found(format!("NotFound, {}:{}", schema.unwrap_or_default(), table))
274      }
275      fusionsql::SqlError::ListLimitOverMax { max, actual } => {
276        DataError::bad_request(format!("ListLimitOverMax, max: {max}, actual: {actual}"))
277      }
278      fusionsql::SqlError::ListLimitUnderMin { min, actual } => {
279        DataError::bad_request(format!("ListLimitUnderMin, min: {min}, actual: {actual}"))
280      }
281      fusionsql::SqlError::ListPageUnderMin { min, actual } => {
282        DataError::bad_request(format!("ListPageUnderMin, min: {min}, actual: {actual}"))
283      }
284      fusionsql::SqlError::UserAlreadyExists { key, value } => {
285        DataError::conflicted(format!("UserAlreadyExists, {key}:{value}"))
286      }
287      fusionsql::SqlError::UniqueViolation { table, constraint } => {
288        DataError::conflicted(format!("UniqueViolation, {table}:{constraint}"))
289      }
290      fusionsql::SqlError::ExecuteError { table, message } => {
291        DataError::server_error(format!("ExecuteError, {}:{}", table, message))
292      }
293      fusionsql::SqlError::ExecuteFail { schema, table } => {
294        DataError::server_error(format!("ExecuteFail, {:?}:{}", schema, table))
295      }
296      fusionsql::SqlError::CountFail { schema, table } => {
297        DataError::server_error(format!("CountFail, {:?}:{}", schema, table))
298      }
299      e @ fusionsql::SqlError::InvalidDatabase(_) => DataError::server_error(e.to_string()),
300      e @ fusionsql::SqlError::CantCreateModelManagerProvider(_) => DataError::server_error(e.to_string()),
301      e @ fusionsql::SqlError::IntoSeaError(_) => DataError::server_error(e.to_string()),
302      e @ fusionsql::SqlError::SeaQueryError(_) => DataError::server_error(e.to_string()),
303      e @ fusionsql::SqlError::JsonError(_) => DataError::server_error(e.to_string()),
304      fusionsql::SqlError::DbxError(e) => {
305        // Convert to a compatible error that implements Send + Sync
306        let error_msg = e.to_string();
307        let compatible_error: Box<dyn std::error::Error + Send + Sync + 'static> =
308          Box::new(std::io::Error::other(error_msg));
309        DataError::internal(500, "Dbx Error", Some(compatible_error))
310      }
311      fusionsql::SqlError::Sqlx(e) => {
312        // Convert to a compatible error that implements Send + Sync
313        let error_msg = e.to_string();
314        let compatible_error: Box<dyn std::error::Error + Send + Sync + 'static> =
315          Box::new(std::io::Error::other(error_msg));
316        DataError::internal(500, "Sqlx Error", Some(compatible_error))
317      }
318    }
319  }
320}
321
322#[cfg(feature = "fusionsql")]
323impl From<fusionsql::store::DbxError> for DataError {
324  fn from(value: fusionsql::store::DbxError) -> Self {
325    DataError::server_error(value.to_string())
326  }
327}
328
329impl From<SecurityError> for DataError {
330  fn from(value: SecurityError) -> Self {
331    match value {
332      SecurityError::TokenExpired => DataError::unauthorized("Token expired"),
333      SecurityError::SignatureNotMatching => DataError::unauthorized("Signature not matching"),
334      SecurityError::InvalidPassword => DataError::unauthorized("Invalid password"),
335      SecurityError::FailedToVerifyPassword => DataError::unauthorized("Failed to verify password"),
336      // SecurityError::HmacFailNewFromSlice => todo!(),
337      // SecurityError::InvalidFormat => todo!(),
338      // SecurityError::CannotDecodeIdent => todo!(),
339      // SecurityError::CannotDecodeExp => todo!(),
340      // SecurityError::ExpNotIso => todo!(),
341      // SecurityError::FailedToHashPassword => todo!(),
342      _ => DataError::server_error(value.to_string()),
343    }
344  }
345}
346
347impl<T> From<mea::mpsc::SendError<T>> for DataError {
348  fn from(value: mea::mpsc::SendError<T>) -> Self {
349    DataError::server_error(format!("Send to mea::mpsc error, {}", value))
350  }
351}