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)]
13#[non_exhaustive]
14pub enum ErrorCode {
15 Success = 0,
16 BadRequest = 400,
17 Unauthorized = 401,
18 Forbidden = 403,
19 NotFound = 404,
20 Conflict = 409,
21 TooManyRequests = 429,
22 ValidationError = 422,
23 InternalError = 500,
24 NotImplemented = 501,
25 BadGateway = 502,
26 ServiceUnavailable = 503,
27}
28
29impl ErrorCode {
30 pub fn as_i32(self) -> i32 {
32 self as i32
33 }
34
35 pub fn as_str(self) -> &'static str {
37 match self {
38 ErrorCode::Success => "Success",
39 ErrorCode::BadRequest => "Bad Request",
40 ErrorCode::Unauthorized => "Unauthorized",
41 ErrorCode::Forbidden => "Forbidden",
42 ErrorCode::NotFound => "Not Found",
43 ErrorCode::Conflict => "Conflict",
44 ErrorCode::TooManyRequests => "Too Many Requests",
45 ErrorCode::ValidationError => "Unprocessable Entity",
46 ErrorCode::InternalError => "Internal Server Error",
47 ErrorCode::NotImplemented => "Not Implemented",
48 ErrorCode::BadGateway => "Bad Gateway",
49 ErrorCode::ServiceUnavailable => "Service Unavailable",
50 }
51 }
52
53 pub fn from_i32(value: i32) -> Option<Self> {
55 match value {
56 0 => Some(ErrorCode::Success),
57 400 => Some(ErrorCode::BadRequest),
58 401 => Some(ErrorCode::Unauthorized),
59 403 => Some(ErrorCode::Forbidden),
60 404 => Some(ErrorCode::NotFound),
61 409 => Some(ErrorCode::Conflict),
62 429 => Some(ErrorCode::TooManyRequests),
63 422 => Some(ErrorCode::ValidationError),
64 500 => Some(ErrorCode::InternalError),
65 501 => Some(ErrorCode::NotImplemented),
66 502 => Some(ErrorCode::BadGateway),
67 503 => Some(ErrorCode::ServiceUnavailable),
68 _ => None,
69 }
70 }
71}
72
73#[derive(Error, Debug, Clone, PartialEq)]
75#[error("[{}] {}", self.code.as_str(), self.message)]
76pub struct ApiError {
77 pub code: ErrorCode,
78 pub message: String,
79}
80
81impl ApiError {
82 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
84 Self {
85 code,
86 message: message.into(),
87 }
88 }
89
90 pub fn bad_request(message: impl Into<String>) -> Self {
92 Self::new(ErrorCode::BadRequest, message)
93 }
94
95 pub fn unauthorized(message: impl Into<String>) -> Self {
97 Self::new(ErrorCode::Unauthorized, message)
98 }
99
100 pub fn forbidden(message: impl Into<String>) -> Self {
102 Self::new(ErrorCode::Forbidden, message)
103 }
104
105 pub fn not_found(message: impl Into<String>) -> Self {
107 Self::new(ErrorCode::NotFound, message)
108 }
109
110 pub fn conflict(message: impl Into<String>) -> Self {
112 Self::new(ErrorCode::Conflict, message)
113 }
114
115 pub fn too_many_requests(message: impl Into<String>) -> Self {
117 Self::new(ErrorCode::TooManyRequests, message)
118 }
119
120 pub fn validation(message: impl Into<String>) -> Self {
122 Self::new(ErrorCode::ValidationError, message)
123 }
124
125 pub fn internal(message: impl Into<String>) -> Self {
127 Self::new(ErrorCode::InternalError, message)
128 }
129
130 pub fn code(&self) -> ErrorCode {
132 self.code
133 }
134
135 pub fn message(&self) -> &str {
137 &self.message
138 }
139}
140
141#[derive(Error, Debug)]
143#[non_exhaustive]
144pub enum AppError {
145 #[error("Resource not found: {0}")]
146 NotFound(String),
147
148 #[error("Bad request: {0}")]
149 BadRequest(String),
150
151 #[error("Unauthorized: {0}")]
152 Unauthorized(String),
153
154 #[error("Forbidden: {0}")]
155 Forbidden(String),
156
157 #[error("Conflict: {0}")]
158 Conflict(String),
159
160 #[error("Validation error: {0}")]
161 Validation(String),
162
163 #[error("Internal error: {0}")]
164 Internal(#[from] anyhow::Error),
165
166 #[error("Unknown error: {0}")]
167 Unknown(String),
168}
169
170impl AppError {
171 pub fn to_error_code(&self) -> ErrorCode {
173 match self {
174 AppError::NotFound(_) => ErrorCode::NotFound,
175 AppError::BadRequest(_) => ErrorCode::BadRequest,
176 AppError::Unauthorized(_) => ErrorCode::Unauthorized,
177 AppError::Forbidden(_) => ErrorCode::Forbidden,
178 AppError::Conflict(_) => ErrorCode::Conflict,
179 AppError::Validation(_) => ErrorCode::ValidationError,
180 AppError::Internal(_) => ErrorCode::InternalError,
181 AppError::Unknown(_) => ErrorCode::InternalError,
182 }
183 }
184}
185
186impl From<ApiError> for AppError {
187 fn from(err: ApiError) -> Self {
188 match err.code {
189 ErrorCode::NotFound => AppError::NotFound(err.message),
190 ErrorCode::BadRequest => AppError::BadRequest(err.message),
191 ErrorCode::Unauthorized => AppError::Unauthorized(err.message),
192 ErrorCode::Forbidden => AppError::Forbidden(err.message),
193 ErrorCode::Conflict => AppError::Conflict(err.message),
194 ErrorCode::ValidationError => AppError::Validation(err.message),
195 _ => AppError::Internal(anyhow::anyhow!(err.message)),
196 }
197 }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
206#[serde(rename_all = "camelCase")]
207pub struct FieldError {
208 pub field: String,
209 pub message: String,
210}
211
212impl FieldError {
213 pub fn new(field: impl Into<String>, message: impl Into<String>) -> Self {
214 Self {
215 field: field.into(),
216 message: message.into(),
217 }
218 }
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
227#[serde(untagged)]
228#[non_exhaustive]
229pub enum ResponseData<T> {
230 Single(T),
232 Multiple(Vec<T>),
234}
235
236impl<T> ResponseData<T> {
237 pub fn single(v: T) -> Self {
239 ResponseData::Single(v)
240 }
241
242 pub fn multiple(v: Vec<T>) -> Self {
244 ResponseData::Multiple(v)
245 }
246
247 pub fn is_single(&self) -> bool {
249 matches!(self, ResponseData::Single(_))
250 }
251
252 pub fn is_multiple(&self) -> bool {
254 matches!(self, ResponseData::Multiple(_))
255 }
256
257 pub fn as_single(&self) -> Option<&T> {
259 match self {
260 ResponseData::Single(v) => Some(v),
261 ResponseData::Multiple(_) => None,
262 }
263 }
264
265 pub fn as_multiple(&self) -> Option<&Vec<T>> {
267 match self {
268 ResponseData::Single(_) => None,
269 ResponseData::Multiple(v) => Some(v),
270 }
271 }
272}
273
274impl<T> From<T> for ResponseData<T> {
275 fn from(v: T) -> Self {
276 ResponseData::Single(v)
277 }
278}
279
280impl<T> From<Vec<T>> for ResponseData<T> {
281 fn from(v: Vec<T>) -> Self {
282 ResponseData::Multiple(v)
283 }
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
292#[serde(rename_all = "camelCase")]
293pub struct ApiResult<T> {
294 pub success: bool,
295 #[serde(skip_serializing_if = "Option::is_none")]
296 pub data: Option<ResponseData<T>>,
297 #[serde(skip_serializing_if = "Option::is_none")]
298 pub message: Option<String>,
299 #[serde(skip_serializing_if = "Option::is_none")]
300 pub code: Option<i32>,
301 #[serde(skip_serializing_if = "Option::is_none")]
302 pub pagination: Option<ResultPagination>,
303 #[serde(skip_serializing_if = "Option::is_none")]
304 pub errors: Option<Vec<FieldError>>,
305 #[serde(skip_serializing_if = "Option::is_none")]
306 pub trace_id: Option<String>,
307 #[serde(skip_serializing_if = "Option::is_none")]
308 pub extra: Option<Value>,
309}
310
311impl<T> ApiResult<T> {
312 pub fn value(v: T) -> Self {
314 ApiResult {
315 success: true,
316 data: Some(ResponseData::single(v)),
317 message: None,
318 code: None,
319 pagination: None,
320 errors: None,
321 trace_id: None,
322 extra: None,
323 }
324 }
325
326 pub fn list(v: Vec<T>) -> Self {
328 ApiResult {
329 success: true,
330 data: Some(ResponseData::multiple(v)),
331 message: None,
332 code: None,
333 pagination: None,
334 errors: None,
335 trace_id: None,
336 extra: None,
337 }
338 }
339
340 pub fn fail(message: &str) -> Self {
342 Self::failure(message)
343 }
344
345 pub fn failure(message: &str) -> Self {
347 ApiResult {
348 success: false,
349 data: None,
350 message: Some(message.to_string()),
351 code: None,
352 pagination: None,
353 errors: None,
354 trace_id: None,
355 extra: None,
356 }
357 }
358
359 pub fn success() -> Self {
363 Self::ok()
364 }
365
366 pub fn ok() -> Self {
368 ApiResult {
369 success: true,
370 data: None,
371 message: None,
372 code: None,
373 pagination: None,
374 errors: None,
375 trace_id: None,
376 extra: None,
377 }
378 }
379
380 pub fn validation_errors(errors: Vec<FieldError>) -> Self {
382 ApiResult {
383 success: false,
384 data: None,
385 message: Some("Validation failed".to_string()),
386 code: Some(ErrorCode::ValidationError as i32),
387 pagination: None,
388 errors: Some(errors),
389 trace_id: None,
390 extra: None,
391 }
392 }
393
394 pub fn from_result<E: std::fmt::Display>(result: Result<T, E>) -> Self {
396 match result {
397 Ok(data) => ApiResult::value(data),
398 Err(err) => ApiResult {
399 success: false,
400 data: None,
401 message: Some(err.to_string()),
402 code: Some(ErrorCode::InternalError as i32),
403 pagination: None,
404 errors: None,
405 trace_id: None,
406 extra: None,
407 },
408 }
409 }
410
411 pub fn with_extra(mut self, key: &str, value: Value) -> Self {
413 match self.extra {
414 Some(ref mut v) => {
415 v[key] = value;
416 }
417 None => {
418 let mut v = serde_json::Map::new();
419 v.insert(key.to_string(), value);
420 self.extra = Some(v.into());
421 }
422 }
423 self
424 }
425
426 pub fn with_code(mut self, code: i32) -> Self {
428 self.code = Some(code);
429 self
430 }
431
432 pub fn with_pagination(mut self, pagination: ResultPagination) -> Self {
434 self.pagination = Some(pagination);
435 self
436 }
437
438 pub fn with_message(mut self, message: &str) -> Self {
440 self.message = Some(message.to_string());
441 self
442 }
443
444 pub fn with_errors(mut self, errors: Vec<FieldError>) -> Self {
446 self.errors = Some(errors);
447 self
448 }
449
450 pub fn with_error(mut self, field: impl Into<String>, message: impl Into<String>) -> Self {
452 match self.errors {
453 Some(ref mut v) => v.push(FieldError::new(field, message)),
454 None => self.errors = Some(vec![FieldError::new(field, message)]),
455 }
456 self
457 }
458
459 pub fn with_trace_id(mut self, id: impl Into<String>) -> Self {
461 self.trace_id = Some(id.into());
462 self
463 }
464}
465
466impl<T, E> Into<Result<ApiResult<T>, E>> for ApiResult<T> {
468 fn into(self) -> Result<ApiResult<T>, E> {
469 Ok(self)
470 }
471}
472
473impl<T> From<ApiError> for ApiResult<T> {
478 fn from(err: ApiError) -> Self {
479 ApiResult {
480 success: false,
481 data: None,
482 message: Some(err.message),
483 code: Some(err.code as i32),
484 pagination: None,
485 errors: None,
486 trace_id: None,
487 extra: None,
488 }
489 }
490}
491
492impl<T> From<AppError> for ApiResult<T> {
493 fn from(err: AppError) -> Self {
494 ApiResult {
495 success: false,
496 data: None,
497 message: Some(err.to_string()),
498 code: Some(err.to_error_code() as i32),
499 pagination: None,
500 errors: None,
501 trace_id: None,
502 extra: None,
503 }
504 }
505}
506
507impl<T> From<anyhow::Error> for ApiResult<T> {
508 fn from(err: anyhow::Error) -> Self {
509 ApiResult {
510 success: false,
511 data: None,
512 message: Some(err.to_string()),
513 code: Some(ErrorCode::InternalError as i32),
514 pagination: None,
515 errors: None,
516 trace_id: None,
517 extra: None,
518 }
519 }
520}
521
522impl<T, E: std::fmt::Display + std::fmt::Debug> From<Result<T, E>> for ApiResult<T> {
524 fn from(result: Result<T, E>) -> Self {
525 match result {
526 Ok(data) => ApiResult::value(data),
527 Err(err) => ApiResult {
528 success: false,
529 data: None,
530 message: Some(format!("{}", err)),
531 code: Some(ErrorCode::InternalError as i32),
532 pagination: None,
533 errors: None,
534 trace_id: None,
535 extra: None,
536 },
537 }
538 }
539}
540
541pub trait IntoApiResult<T> {
543 fn into_api_result_with<F>(self, f: F) -> ApiResult<T>
545 where
546 F: FnOnce() -> ApiError;
547
548 fn into_api_result(self) -> ApiResult<T>;
550}
551
552impl<T, E: Into<ApiError>> IntoApiResult<T> for Result<T, E> {
553 fn into_api_result_with<F>(self, f: F) -> ApiResult<T>
554 where
555 F: FnOnce() -> ApiError,
556 {
557 match self {
558 Ok(data) => ApiResult::value(data),
559 Err(_) => ApiResult::from(f()),
560 }
561 }
562
563 fn into_api_result(self) -> ApiResult<T> {
564 match self {
565 Ok(data) => ApiResult::value(data),
566 Err(err) => ApiResult::from(err.into()),
567 }
568 }
569}
570
571pub type DefaultResult<T> = Result<T, Box<dyn std::error::Error>>;
573
574pub type AnyhowResult<T> = Result<T, anyhow::Error>;
576
577pub type EmptyResult = ApiResult<()>;