use crate::pagination::ResultPagination;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
#[non_exhaustive]
pub enum ErrorCode {
Success = 0,
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404,
Conflict = 409,
ValidationError = 422,
InternalError = 500,
}
impl ErrorCode {
pub fn as_i32(self) -> i32 {
self as i32
}
pub fn as_str(self) -> &'static str {
match self {
ErrorCode::Success => "Success",
ErrorCode::BadRequest => "Bad Request",
ErrorCode::Unauthorized => "Unauthorized",
ErrorCode::Forbidden => "Forbidden",
ErrorCode::NotFound => "Not Found",
ErrorCode::Conflict => "Conflict",
ErrorCode::ValidationError => "Unprocessable Entity",
ErrorCode::InternalError => "Internal Server Error",
}
}
pub fn from_i32(value: i32) -> Option<Self> {
match value {
0 => Some(ErrorCode::Success),
400 => Some(ErrorCode::BadRequest),
401 => Some(ErrorCode::Unauthorized),
403 => Some(ErrorCode::Forbidden),
404 => Some(ErrorCode::NotFound),
409 => Some(ErrorCode::Conflict),
422 => Some(ErrorCode::ValidationError),
500 => Some(ErrorCode::InternalError),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(not(feature = "snake-case"), serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "snake-case", serde(rename_all = "snake_case"))]
pub struct FieldError {
pub field: String,
pub message: String,
}
impl FieldError {
pub fn new(field: impl Into<String>, message: impl Into<String>) -> Self {
Self {
field: field.into(),
message: message.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(untagged)]
#[non_exhaustive]
pub enum ResponseData<T> {
Single(T),
Multiple(Vec<T>),
#[default]
Empty,
}
impl<T> ResponseData<T> {
pub fn single(v: T) -> Self {
ResponseData::Single(v)
}
pub fn multiple(v: Vec<T>) -> Self {
ResponseData::Multiple(v)
}
pub fn is_single(&self) -> bool {
matches!(self, ResponseData::Single(_))
}
pub fn is_multiple(&self) -> bool {
matches!(self, ResponseData::Multiple(_))
}
pub fn is_empty(&self) -> bool {
matches!(self, ResponseData::Empty)
}
pub fn as_single(&self) -> Option<&T> {
match self {
ResponseData::Single(v) => Some(v),
_ => None,
}
}
pub fn as_multiple(&self) -> Option<&Vec<T>> {
match self {
ResponseData::Multiple(v) => Some(v),
_ => None,
}
}
#[inline]
pub fn len(&self) -> usize {
match self {
ResponseData::Single(_) => 1,
ResponseData::Multiple(v) => v.len(),
ResponseData::Empty => 0,
}
}
#[inline]
#[allow(clippy::len_zero)]
pub fn is_empty_data(&self) -> bool {
self.len() == 0
}
}
impl<T> From<T> for ResponseData<T> {
fn from(v: T) -> Self {
ResponseData::Single(v)
}
}
impl<T> From<Vec<T>> for ResponseData<T> {
fn from(v: Vec<T>) -> Self {
ResponseData::Multiple(v)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(not(feature = "snake-case"), serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "snake-case", serde(rename_all = "snake_case"))]
pub struct ApiResult<T> {
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<ResponseData<T>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pagination: Option<ResultPagination>,
#[serde(skip_serializing_if = "Option::is_none")]
pub errors: Option<Vec<FieldError>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trace_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub biz_code: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timestamp: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extra: Option<Value>,
}
impl<T> ApiResult<T> {
#[inline]
fn new(
success: bool,
data: Option<ResponseData<T>>,
message: Option<String>,
code: Option<i32>,
) -> Self {
ApiResult {
success,
data,
message,
code,
pagination: None,
errors: None,
trace_id: None,
biz_code: None,
timestamp: None,
extra: None,
}
}
#[inline]
pub fn value(v: T) -> Self {
Self::new(true, Some(ResponseData::single(v)), None, None)
}
#[inline]
pub fn list(v: Vec<T>) -> Self {
Self::new(true, Some(ResponseData::multiple(v)), None, None)
}
#[inline]
pub fn fail(message: &str) -> Self {
Self::failure(message)
}
#[inline]
pub fn failure(message: &str) -> Self {
Self::new(false, None, Some(message.to_string()), None)
}
#[inline]
pub fn success() -> Self {
Self::ok()
}
#[inline]
pub fn ok() -> Self {
Self::new(true, None, None, None)
}
pub fn validation_errors(errors: Vec<FieldError>) -> Self {
let mut r = Self::new(
false,
None,
Some("Validation failed".to_string()),
Some(ErrorCode::ValidationError as i32),
);
r.errors = Some(errors);
r
}
pub fn from_result<E: std::fmt::Display>(result: Result<T, E>) -> Self {
match result {
Ok(data) => Self::value(data),
Err(err) => Self::new(
false,
None,
Some(err.to_string()),
Some(ErrorCode::InternalError as i32),
),
}
}
#[inline]
pub fn is_success(&self) -> bool {
self.success
}
#[inline]
pub fn is_error(&self) -> bool {
!self.success
}
pub fn error_code(&self) -> Option<ErrorCode> {
if !self.success {
self.code.and_then(ErrorCode::from_i32)
} else {
None
}
}
#[inline]
pub fn with_extra(mut self, key: &str, value: Value) -> Self {
match self.extra {
Some(ref mut v) => {
v[key] = value;
}
None => {
let mut v = serde_json::Map::new();
v.insert(key.to_string(), value);
self.extra = Some(v.into());
}
}
self
}
#[inline]
pub fn with_code(mut self, code: i32) -> Self {
self.code = Some(code);
self
}
#[inline]
pub fn with_pagination(mut self, pagination: ResultPagination) -> Self {
self.pagination = Some(pagination);
self
}
#[inline]
pub fn with_message(mut self, message: &str) -> Self {
self.message = Some(message.to_string());
self
}
#[inline]
pub fn with_errors(mut self, errors: Vec<FieldError>) -> Self {
self.errors = Some(errors);
self
}
#[inline]
pub fn with_error(mut self, field: impl Into<String>, message: impl Into<String>) -> Self {
match self.errors {
Some(ref mut v) => v.push(FieldError::new(field, message)),
None => self.errors = Some(vec![FieldError::new(field, message)]),
}
self
}
#[inline]
pub fn with_trace_id(mut self, id: impl Into<String>) -> Self {
self.trace_id = Some(id.into());
self
}
#[inline]
pub fn with_biz_code(mut self, code: i32) -> Self {
self.biz_code = Some(code);
self
}
#[inline]
pub fn with_timestamp(mut self, ts: i64) -> Self {
self.timestamp = Some(ts);
self
}
pub fn with_current_timestamp(self) -> Self {
use std::time::{SystemTime, UNIX_EPOCH};
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as i64)
.unwrap_or(0);
self.with_timestamp(ts)
}
}
impl<T> From<anyhow::Error> for ApiResult<T> {
fn from(err: anyhow::Error) -> Self {
Self::new(
false,
None,
Some(err.to_string()),
Some(ErrorCode::InternalError as i32),
)
}
}
impl<T> From<std::io::Error> for ApiResult<T> {
fn from(err: std::io::Error) -> Self {
Self::new(
false,
None,
Some(err.to_string()),
Some(ErrorCode::InternalError as i32),
)
}
}
impl<T> From<std::string::FromUtf8Error> for ApiResult<T> {
fn from(err: std::string::FromUtf8Error) -> Self {
Self::new(
false,
None,
Some(err.to_string()),
Some(ErrorCode::InternalError as i32),
)
}
}
impl<T, E: std::fmt::Display + std::fmt::Debug> From<Result<T, E>> for ApiResult<T> {
fn from(result: Result<T, E>) -> Self {
match result {
Ok(data) => Self::value(data),
Err(err) => Self::new(
false,
None,
Some(format!("{}", err)),
Some(ErrorCode::InternalError as i32),
),
}
}
}
pub type DefaultResult<T> = Result<T, Box<dyn std::error::Error>>;