1use crate::pagination::ResultPagination;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[repr(i32)]
12#[non_exhaustive]
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}
23
24impl ErrorCode {
25 pub fn as_i32(self) -> i32 {
27 self as i32
28 }
29
30 pub fn as_str(self) -> &'static str {
32 match self {
33 ErrorCode::Success => "Success",
34 ErrorCode::BadRequest => "Bad Request",
35 ErrorCode::Unauthorized => "Unauthorized",
36 ErrorCode::Forbidden => "Forbidden",
37 ErrorCode::NotFound => "Not Found",
38 ErrorCode::Conflict => "Conflict",
39 ErrorCode::ValidationError => "Unprocessable Entity",
40 ErrorCode::InternalError => "Internal Server Error",
41 }
42 }
43
44 pub fn from_i32(value: i32) -> Option<Self> {
46 match value {
47 0 => Some(ErrorCode::Success),
48 400 => Some(ErrorCode::BadRequest),
49 401 => Some(ErrorCode::Unauthorized),
50 403 => Some(ErrorCode::Forbidden),
51 404 => Some(ErrorCode::NotFound),
52 409 => Some(ErrorCode::Conflict),
53 422 => Some(ErrorCode::ValidationError),
54 500 => Some(ErrorCode::InternalError),
55 _ => None,
56 }
57 }
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
66#[cfg_attr(not(feature = "snake-case"), serde(rename_all = "camelCase"))]
67#[cfg_attr(feature = "snake-case", serde(rename_all = "snake_case"))]
68pub struct FieldError {
69 pub field: String,
70 pub message: String,
71}
72
73impl FieldError {
74 pub fn new(field: impl Into<String>, message: impl Into<String>) -> Self {
75 Self {
76 field: field.into(),
77 message: message.into(),
78 }
79 }
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, Default)]
88#[serde(untagged)]
89#[non_exhaustive]
90pub enum ResponseData<T> {
91 Single(T),
93 Multiple(Vec<T>),
95 #[default]
97 Empty,
98}
99
100impl<T> ResponseData<T> {
101 pub fn single(v: T) -> Self {
103 ResponseData::Single(v)
104 }
105
106 pub fn multiple(v: Vec<T>) -> Self {
108 ResponseData::Multiple(v)
109 }
110
111 pub fn is_single(&self) -> bool {
113 matches!(self, ResponseData::Single(_))
114 }
115
116 pub fn is_multiple(&self) -> bool {
118 matches!(self, ResponseData::Multiple(_))
119 }
120
121 pub fn is_empty(&self) -> bool {
123 matches!(self, ResponseData::Empty)
124 }
125
126 pub fn as_single(&self) -> Option<&T> {
128 match self {
129 ResponseData::Single(v) => Some(v),
130 _ => None,
131 }
132 }
133
134 pub fn as_multiple(&self) -> Option<&Vec<T>> {
136 match self {
137 ResponseData::Multiple(v) => Some(v),
138 _ => None,
139 }
140 }
141
142 #[inline]
144 pub fn len(&self) -> usize {
145 match self {
146 ResponseData::Single(_) => 1,
147 ResponseData::Multiple(v) => v.len(),
148 ResponseData::Empty => 0,
149 }
150 }
151
152 #[inline]
157 #[allow(clippy::len_zero)]
158 pub fn is_empty_data(&self) -> bool {
159 self.len() == 0
160 }
161}
162
163impl<T> From<T> for ResponseData<T> {
164 fn from(v: T) -> Self {
165 ResponseData::Single(v)
166 }
167}
168
169impl<T> From<Vec<T>> for ResponseData<T> {
170 fn from(v: Vec<T>) -> Self {
171 ResponseData::Multiple(v)
172 }
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
181#[cfg_attr(not(feature = "snake-case"), serde(rename_all = "camelCase"))]
182#[cfg_attr(feature = "snake-case", serde(rename_all = "snake_case"))]
183pub struct ApiResult<T> {
184 pub success: bool,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub data: Option<ResponseData<T>>,
187 #[serde(skip_serializing_if = "Option::is_none")]
188 pub message: Option<String>,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub code: Option<i32>,
191 #[serde(skip_serializing_if = "Option::is_none")]
192 pub pagination: Option<ResultPagination>,
193 #[serde(skip_serializing_if = "Option::is_none")]
194 pub errors: Option<Vec<FieldError>>,
195 #[serde(skip_serializing_if = "Option::is_none")]
196 pub trace_id: Option<String>,
197 #[serde(skip_serializing_if = "Option::is_none")]
198 pub biz_code: Option<i32>,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 pub timestamp: Option<i64>,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 pub extra: Option<Value>,
203}
204
205impl<T> ApiResult<T> {
206 #[inline]
210 fn new(
211 success: bool,
212 data: Option<ResponseData<T>>,
213 message: Option<String>,
214 code: Option<i32>,
215 ) -> Self {
216 ApiResult {
217 success,
218 data,
219 message,
220 code,
221 pagination: None,
222 errors: None,
223 trace_id: None,
224 biz_code: None,
225 timestamp: None,
226 extra: None,
227 }
228 }
229
230 #[inline]
234 pub fn value(v: T) -> Self {
235 Self::new(true, Some(ResponseData::single(v)), None, None)
236 }
237
238 #[inline]
240 pub fn list(v: Vec<T>) -> Self {
241 Self::new(true, Some(ResponseData::multiple(v)), None, None)
242 }
243
244 #[inline]
246 pub fn fail(message: &str) -> Self {
247 Self::failure(message)
248 }
249
250 #[inline]
252 pub fn failure(message: &str) -> Self {
253 Self::new(false, None, Some(message.to_string()), None)
254 }
255
256 #[inline]
260 pub fn success() -> Self {
261 Self::ok()
262 }
263
264 #[inline]
266 pub fn ok() -> Self {
267 Self::new(true, None, None, None)
268 }
269
270 pub fn validation_errors(errors: Vec<FieldError>) -> Self {
272 let mut r = Self::new(
273 false,
274 None,
275 Some("Validation failed".to_string()),
276 Some(ErrorCode::ValidationError as i32),
277 );
278 r.errors = Some(errors);
279 r
280 }
281
282 pub fn from_result<E: std::fmt::Display>(result: Result<T, E>) -> Self {
284 match result {
285 Ok(data) => Self::value(data),
286 Err(err) => Self::new(
287 false,
288 None,
289 Some(err.to_string()),
290 Some(ErrorCode::InternalError as i32),
291 ),
292 }
293 }
294
295 #[inline]
299 pub fn is_success(&self) -> bool {
300 self.success
301 }
302
303 #[inline]
305 pub fn is_error(&self) -> bool {
306 !self.success
307 }
308
309 pub fn error_code(&self) -> Option<ErrorCode> {
311 if !self.success {
312 self.code.and_then(ErrorCode::from_i32)
313 } else {
314 None
315 }
316 }
317
318 #[inline]
322 pub fn with_extra(mut self, key: &str, value: Value) -> Self {
323 match self.extra {
324 Some(ref mut v) => {
325 v[key] = value;
326 }
327 None => {
328 let mut v = serde_json::Map::new();
329 v.insert(key.to_string(), value);
330 self.extra = Some(v.into());
331 }
332 }
333 self
334 }
335
336 #[inline]
338 pub fn with_code(mut self, code: i32) -> Self {
339 self.code = Some(code);
340 self
341 }
342
343 #[inline]
345 pub fn with_pagination(mut self, pagination: ResultPagination) -> Self {
346 self.pagination = Some(pagination);
347 self
348 }
349
350 #[inline]
352 pub fn with_message(mut self, message: &str) -> Self {
353 self.message = Some(message.to_string());
354 self
355 }
356
357 #[inline]
359 pub fn with_errors(mut self, errors: Vec<FieldError>) -> Self {
360 self.errors = Some(errors);
361 self
362 }
363
364 #[inline]
366 pub fn with_error(mut self, field: impl Into<String>, message: impl Into<String>) -> Self {
367 match self.errors {
368 Some(ref mut v) => v.push(FieldError::new(field, message)),
369 None => self.errors = Some(vec![FieldError::new(field, message)]),
370 }
371 self
372 }
373
374 #[inline]
376 pub fn with_trace_id(mut self, id: impl Into<String>) -> Self {
377 self.trace_id = Some(id.into());
378 self
379 }
380
381 #[inline]
383 pub fn with_biz_code(mut self, code: i32) -> Self {
384 self.biz_code = Some(code);
385 self
386 }
387
388 #[inline]
390 pub fn with_timestamp(mut self, ts: i64) -> Self {
391 self.timestamp = Some(ts);
392 self
393 }
394
395 pub fn with_current_timestamp(self) -> Self {
397 use std::time::{SystemTime, UNIX_EPOCH};
398 let ts = SystemTime::now()
399 .duration_since(UNIX_EPOCH)
400 .map(|d| d.as_millis() as i64)
401 .unwrap_or(0);
402 self.with_timestamp(ts)
403 }
404}
405
406impl<T, E> From<ApiResult<T>> for Result<ApiResult<T>, E> {
418 fn from(val: ApiResult<T>) -> Self {
419 Ok(val)
420 }
421}
422
423impl<T> From<anyhow::Error> for ApiResult<T> {
428 fn from(err: anyhow::Error) -> Self {
429 Self::new(
430 false,
431 None,
432 Some(err.to_string()),
433 Some(ErrorCode::InternalError as i32),
434 )
435 }
436}
437
438impl<T> From<std::io::Error> for ApiResult<T> {
439 fn from(err: std::io::Error) -> Self {
440 Self::new(
441 false,
442 None,
443 Some(err.to_string()),
444 Some(ErrorCode::InternalError as i32),
445 )
446 }
447}
448
449impl<T> From<std::string::FromUtf8Error> for ApiResult<T> {
450 fn from(err: std::string::FromUtf8Error) -> Self {
451 Self::new(
452 false,
453 None,
454 Some(err.to_string()),
455 Some(ErrorCode::InternalError as i32),
456 )
457 }
458}
459
460impl<T, E: std::fmt::Display + std::fmt::Debug> From<Result<T, E>> for ApiResult<T> {
462 fn from(result: Result<T, E>) -> Self {
463 match result {
464 Ok(data) => Self::value(data),
465 Err(err) => Self::new(
466 false,
467 None,
468 Some(format!("{}", err)),
469 Some(ErrorCode::InternalError as i32),
470 ),
471 }
472 }
473}
474
475pub type DefaultResult<T> = Result<T, Box<dyn std::error::Error>>;