1use crate::pagination::ResultPagination;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use thiserror::Error;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[repr(i32)]
13pub enum ErrorCode {
14 Success = 0,
15 BadRequest = 400,
16 Unauthorized = 401,
17 Forbidden = 403,
18 NotFound = 404,
19 Conflict = 409,
20 ValidationError = 422,
21 InternalError = 500,
22 NotImplemented = 501,
23 BadGateway = 502,
24 ServiceUnavailable = 503,
25}
26
27impl ErrorCode {
28 pub fn as_i32(self) -> i32 {
30 self as i32
31 }
32
33 pub fn as_str(self) -> &'static str {
35 match self {
36 ErrorCode::Success => "Success",
37 ErrorCode::BadRequest => "Bad Request",
38 ErrorCode::Unauthorized => "Unauthorized",
39 ErrorCode::Forbidden => "Forbidden",
40 ErrorCode::NotFound => "Not Found",
41 ErrorCode::Conflict => "Conflict",
42 ErrorCode::ValidationError => "Unprocessable Entity",
43 ErrorCode::InternalError => "Internal Server Error",
44 ErrorCode::NotImplemented => "Not Implemented",
45 ErrorCode::BadGateway => "Bad Gateway",
46 ErrorCode::ServiceUnavailable => "Service Unavailable",
47 }
48 }
49
50 pub fn from_i32(value: i32) -> Option<Self> {
52 match value {
53 0 => Some(ErrorCode::Success),
54 400 => Some(ErrorCode::BadRequest),
55 401 => Some(ErrorCode::Unauthorized),
56 403 => Some(ErrorCode::Forbidden),
57 404 => Some(ErrorCode::NotFound),
58 409 => Some(ErrorCode::Conflict),
59 422 => Some(ErrorCode::ValidationError),
60 500 => Some(ErrorCode::InternalError),
61 501 => Some(ErrorCode::NotImplemented),
62 502 => Some(ErrorCode::BadGateway),
63 503 => Some(ErrorCode::ServiceUnavailable),
64 _ => None,
65 }
66 }
67
68 #[cfg(feature = "axum")]
70 pub fn to_axum_status(self) -> axum::http::StatusCode {
71 match self {
72 ErrorCode::Success => axum::http::StatusCode::OK,
73 ErrorCode::BadRequest => axum::http::StatusCode::BAD_REQUEST,
74 ErrorCode::Unauthorized => axum::http::StatusCode::UNAUTHORIZED,
75 ErrorCode::Forbidden => axum::http::StatusCode::FORBIDDEN,
76 ErrorCode::NotFound => axum::http::StatusCode::NOT_FOUND,
77 ErrorCode::Conflict => axum::http::StatusCode::CONFLICT,
78 ErrorCode::ValidationError => axum::http::StatusCode::UNPROCESSABLE_ENTITY,
79 ErrorCode::InternalError => axum::http::StatusCode::INTERNAL_SERVER_ERROR,
80 ErrorCode::NotImplemented => axum::http::StatusCode::NOT_IMPLEMENTED,
81 ErrorCode::BadGateway => axum::http::StatusCode::BAD_GATEWAY,
82 ErrorCode::ServiceUnavailable => axum::http::StatusCode::SERVICE_UNAVAILABLE,
83 }
84 }
85
86 #[cfg(feature = "actix")]
88 pub fn to_actix_status(self) -> actix_web::http::StatusCode {
89 match self {
90 ErrorCode::Success => actix_web::http::StatusCode::OK,
91 ErrorCode::BadRequest => actix_web::http::StatusCode::BAD_REQUEST,
92 ErrorCode::Unauthorized => actix_web::http::StatusCode::UNAUTHORIZED,
93 ErrorCode::Forbidden => actix_web::http::StatusCode::FORBIDDEN,
94 ErrorCode::NotFound => actix_web::http::StatusCode::NOT_FOUND,
95 ErrorCode::Conflict => actix_web::http::StatusCode::CONFLICT,
96 ErrorCode::ValidationError => actix_web::http::StatusCode::UNPROCESSABLE_ENTITY,
97 ErrorCode::InternalError => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
98 ErrorCode::NotImplemented => actix_web::http::StatusCode::NOT_IMPLEMENTED,
99 ErrorCode::BadGateway => actix_web::http::StatusCode::BAD_GATEWAY,
100 ErrorCode::ServiceUnavailable => actix_web::http::StatusCode::SERVICE_UNAVAILABLE,
101 }
102 }
103}
104
105#[derive(Error, Debug)]
121pub struct ApiError {
122 pub code: ErrorCode,
123 pub message: String,
124}
125
126impl ApiError {
127 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
129 Self {
130 code,
131 message: message.into(),
132 }
133 }
134
135 pub fn not_found(message: impl Into<String>) -> Self {
137 Self::new(ErrorCode::NotFound, message)
138 }
139
140 pub fn bad_request(message: impl Into<String>) -> Self {
142 Self::new(ErrorCode::BadRequest, message)
143 }
144
145 pub fn unauthorized(message: impl Into<String>) -> Self {
147 Self::new(ErrorCode::Unauthorized, message)
148 }
149
150 pub fn forbidden(message: impl Into<String>) -> Self {
152 Self::new(ErrorCode::Forbidden, message)
153 }
154
155 pub fn validation(message: impl Into<String>) -> Self {
157 Self::new(ErrorCode::ValidationError, message)
158 }
159
160 pub fn conflict(message: impl Into<String>) -> Self {
162 Self::new(ErrorCode::Conflict, message)
163 }
164
165 pub fn internal(message: impl Into<String>) -> Self {
167 Self::new(ErrorCode::InternalError, message)
168 }
169
170 pub fn code(&self) -> ErrorCode {
172 self.code
173 }
174
175 pub fn message(&self) -> &str {
177 &self.message
178 }
179}
180
181impl std::fmt::Display for ApiError {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 write!(f, "[{}] {}", self.code.as_str(), self.message)
184 }
185}
186
187#[derive(Error, Debug)]
192pub enum AppError {
193 #[error("Resource not found: {0}")]
194 NotFound(String),
195
196 #[error("Bad request: {0}")]
197 BadRequest(String),
198
199 #[error("Unauthorized: {0}")]
200 Unauthorized(String),
201
202 #[error("Forbidden: {0}")]
203 Forbidden(String),
204
205 #[error("Conflict: {0}")]
206 Conflict(String),
207
208 #[error("Validation error: {0}")]
209 Validation(String),
210
211 #[error("Internal error: {0}")]
212 Internal(#[from] anyhow::Error),
213
214 #[error("Unknown error: {0}")]
215 Unknown(String),
216}
217
218impl AppError {
219 pub fn to_error_code(&self) -> ErrorCode {
221 match self {
222 AppError::NotFound(_) => ErrorCode::NotFound,
223 AppError::BadRequest(_) => ErrorCode::BadRequest,
224 AppError::Unauthorized(_) => ErrorCode::Unauthorized,
225 AppError::Forbidden(_) => ErrorCode::Forbidden,
226 AppError::Conflict(_) => ErrorCode::Conflict,
227 AppError::Validation(_) => ErrorCode::ValidationError,
228 AppError::Internal(_) => ErrorCode::InternalError,
229 AppError::Unknown(_) => ErrorCode::InternalError,
230 }
231 }
232}
233
234impl From<ApiError> for AppError {
235 fn from(err: ApiError) -> Self {
236 match err.code {
237 ErrorCode::NotFound => AppError::NotFound(err.message),
238 ErrorCode::BadRequest => AppError::BadRequest(err.message),
239 ErrorCode::Unauthorized => AppError::Unauthorized(err.message),
240 ErrorCode::Forbidden => AppError::Forbidden(err.message),
241 ErrorCode::Conflict => AppError::Conflict(err.message),
242 ErrorCode::ValidationError => AppError::Validation(err.message),
243 _ => AppError::Internal(anyhow::anyhow!(err.message)),
244 }
245 }
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
257#[serde(untagged)]
258pub enum ResponseData<T> {
259 Single(T),
261 Multiple(Vec<T>),
263}
264
265impl<T> ResponseData<T> {
266 pub fn single(v: T) -> Self {
268 ResponseData::Single(v)
269 }
270
271 pub fn multiple(v: Vec<T>) -> Self {
273 ResponseData::Multiple(v)
274 }
275
276 pub fn is_single(&self) -> bool {
278 matches!(self, ResponseData::Single(_))
279 }
280
281 pub fn is_multiple(&self) -> bool {
283 matches!(self, ResponseData::Multiple(_))
284 }
285
286 pub fn as_single(&self) -> Option<&T> {
288 match self {
289 ResponseData::Single(v) => Some(v),
290 ResponseData::Multiple(_) => None,
291 }
292 }
293
294 pub fn as_multiple(&self) -> Option<&Vec<T>> {
296 match self {
297 ResponseData::Single(_) => None,
298 ResponseData::Multiple(v) => Some(v),
299 }
300 }
301}
302
303#[derive(Debug, Serialize, Deserialize)]
332#[serde(rename_all = "camelCase")]
333pub struct ApiResult<T> {
334 pub success: bool,
335 #[serde(skip_serializing_if = "Option::is_none")]
336 pub data: Option<ResponseData<T>>,
337 #[serde(skip_serializing_if = "Option::is_none")]
338 pub message: Option<String>,
339 #[serde(skip_serializing_if = "Option::is_none")]
340 pub code: Option<i32>,
341 #[serde(skip_serializing_if = "Option::is_none")]
342 pub pagination: Option<ResultPagination>,
343 #[serde(skip_serializing_if = "Option::is_none")]
344 pub extra: Option<Value>,
345}
346
347impl<T> ApiResult<T> {
348 pub fn value(v: T) -> Self {
350 ApiResult {
351 success: true,
352 data: Some(ResponseData::single(v)),
353 message: None,
354 code: None,
355 pagination: None,
356 extra: None,
357 }
358 }
359
360 pub fn list(v: Vec<T>) -> Self {
362 ApiResult {
363 success: true,
364 data: Some(ResponseData::multiple(v)),
365 message: None,
366 code: None,
367 pagination: None,
368 extra: None,
369 }
370 }
371
372 pub fn fail(message: &str) -> Self {
374 Self::failure(message)
375 }
376
377 pub fn failure(message: &str) -> Self {
379 ApiResult {
380 success: false,
381 data: None,
382 message: Some(message.to_string()),
383 code: None,
384 pagination: None,
385 extra: None,
386 }
387 }
388
389 pub fn success() -> Self {
393 Self::ok()
394 }
395
396 pub fn ok() -> Self {
398 ApiResult {
399 success: true,
400 data: None,
401 message: None,
402 code: None,
403 pagination: None,
404 extra: None,
405 }
406 }
407
408 pub fn with_extra(mut self, key: &str, value: Value) -> Self {
420 match self.extra {
421 Some(ref mut v) => {
422 v[key] = value;
423 }
424 None => {
425 let mut v = serde_json::Map::new();
426 v.insert(key.to_string(), value);
427 self.extra = Some(v.into());
428 }
429 }
430 self
431 }
432
433 pub fn with_code(mut self, code: i32) -> Self {
435 self.code = Some(code);
436 self
437 }
438
439 pub fn with_pagination(mut self, pagination: ResultPagination) -> Self {
441 self.pagination = Some(pagination);
442 self
443 }
444
445 pub fn with_message(mut self, message: &str) -> Self {
447 self.message = Some(message.to_string());
448 self
449 }
450}
451
452impl<T, E> Into<Result<ApiResult<T>, E>> for ApiResult<T> {
454 fn into(self) -> Result<ApiResult<T>, E> {
455 Ok(self)
456 }
457}
458
459impl<T> From<ApiError> for ApiResult<T> {
464 fn from(err: ApiError) -> Self {
465 ApiResult {
466 success: false,
467 data: None,
468 message: Some(err.message),
469 code: Some(err.code.as_i32()),
470 pagination: None,
471 extra: None,
472 }
473 }
474}
475
476impl<T> From<AppError> for ApiResult<T> {
477 fn from(err: AppError) -> Self {
478 ApiResult {
479 success: false,
480 data: None,
481 message: Some(err.to_string()),
482 code: Some(err.to_error_code().as_i32()),
483 pagination: None,
484 extra: None,
485 }
486 }
487}
488
489impl<T> From<anyhow::Error> for ApiResult<T> {
490 fn from(err: anyhow::Error) -> Self {
491 ApiResult {
492 success: false,
493 data: None,
494 message: Some(err.to_string()),
495 code: Some(ErrorCode::InternalError.as_i32()),
496 pagination: None,
497 extra: None,
498 }
499 }
500}
501
502impl<T, E: std::fmt::Display + std::fmt::Debug> From<Result<T, E>> for ApiResult<T> {
504 fn from(result: Result<T, E>) -> Self {
505 match result {
506 Ok(data) => ApiResult::value(data),
507 Err(err) => ApiResult {
508 success: false,
509 data: None,
510 message: Some(format!("{}", err)),
511 code: Some(ErrorCode::InternalError.as_i32()),
512 pagination: None,
513 extra: None,
514 },
515 }
516 }
517}
518
519pub trait IntoApiResult<T> {
521 fn into_api_result_with<F>(self, f: F) -> ApiResult<T>
523 where
524 F: FnOnce() -> ApiError;
525
526 fn into_api_result(self) -> ApiResult<T>;
528}
529
530impl<T, E: Into<ApiError>> IntoApiResult<T> for Result<T, E> {
532 fn into_api_result_with<F>(self, f: F) -> ApiResult<T>
533 where
534 F: FnOnce() -> ApiError,
535 {
536 match self {
537 Ok(data) => ApiResult::value(data),
538 Err(_) => ApiResult::from(f()),
539 }
540 }
541
542 fn into_api_result(self) -> ApiResult<T> {
543 match self {
544 Ok(data) => ApiResult::value(data),
545 Err(err) => ApiResult::from(err.into()),
546 }
547 }
548}
549
550pub type DefaultResult<T> = Result<T, Box<dyn std::error::Error>>;
552
553pub type AnyhowResult<T> = Result<T, anyhow::Error>;
555
556pub type EmptyResult = ApiResult<()>;