use crate::Error;
use serde::Serialize;
#[cfg(feature = "axum")]
use axum::{
response::{IntoResponse, Json},
http::StatusCode,
};
pub mod codes {
pub const OK: i32 = 0;
pub const BAD_REQUEST: i32 = 400;
pub const UNAUTHORIZED: i32 = 401;
pub const FORBIDDEN: i32 = 403;
pub const NOT_FOUND: i32 = 404;
pub const METHOD_NOT_ALLOWED: i32 = 405;
pub const CONFLICT: i32 = 409;
pub const UNPROCESSABLE_ENTITY: i32 = 422;
pub const TOO_MANY_REQUESTS: i32 = 429;
pub const INTERNAL: i32 = 500;
pub const SERVICE_UNAVAILABLE: i32 = 503;
}
#[derive(Debug, Clone, Serialize)]
pub struct Res<T: Serialize = ()> {
pub code: i32,
pub msg: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<T>,
}
#[derive(Debug, Clone, Serialize)]
pub struct PageData<T: Serialize> {
pub list: T,
pub total: u64,
pub page: u64,
pub page_size: u64,
}
pub type ResResult<T> = std::result::Result<Res<T>, ApiError>;
#[derive(Debug, Clone, serde::Deserialize)]
pub struct PageQuery {
pub page: u64,
pub page_size: u64,
}
impl PageQuery {
pub fn new(page: u64, page_size: u64) -> Self {
let page = if page < 1 { 1 } else { page };
let page_size = if page_size < 1 { 10 } else if page_size > 1000 { 1000 } else { page_size };
Self { page, page_size }
}
pub fn offset(&self) -> u64 { (self.page - 1) * self.page_size }
pub fn limit(&self) -> u64 { self.page_size }
}
impl Res<()> {
pub fn ok_empty() -> Self {
Self { code: codes::OK, msg: "ok".into(), data: None }
}
pub fn ok_msg(msg: impl Into<String>) -> Self {
Self { code: codes::OK, msg: msg.into(), data: None }
}
}
impl<T: Serialize> Res<T> {
pub fn ok(data: T) -> Self {
Self { code: codes::OK, msg: "ok".into(), data: Some(data) }
}
pub fn ok_with_msg(data: T, msg: impl Into<String>) -> Self {
Self { code: codes::OK, msg: msg.into(), data: Some(data) }
}
pub fn fail(code: i32, msg: impl Into<String>) -> Self {
Self { code, msg: msg.into(), data: None }
}
}
impl<T: Serialize> Res<PageData<T>> {
pub fn page(list: T, total: u64, page: u64, page_size: u64) -> Self {
Self::ok(PageData { list, total, page, page_size })
}
}
#[derive(Debug)]
pub struct ApiError {
pub code: i32,
pub msg: String,
pub status: u16,
pub internal_detail: Option<String>,
}
impl ApiError {
pub fn new(status: u16, code: i32, msg: impl Into<String>) -> Self {
Self { status, code, msg: msg.into(), internal_detail: None }
}
fn with_detail(mut self, detail: impl Into<String>) -> Self {
self.internal_detail = Some(detail.into());
self
}
pub fn bad_request(msg: impl Into<String>) -> Self {
Self::new(400, codes::BAD_REQUEST, msg)
}
pub fn unauthorized(msg: impl Into<String>) -> Self {
Self::new(401, codes::UNAUTHORIZED, msg)
}
pub fn forbidden(msg: impl Into<String>) -> Self {
Self::new(403, codes::FORBIDDEN, msg)
}
pub fn not_found(msg: impl Into<String>) -> Self {
Self::new(404, codes::NOT_FOUND, msg)
}
pub fn method_not_allowed(msg: impl Into<String>) -> Self {
Self::new(405, codes::METHOD_NOT_ALLOWED, msg)
}
pub fn conflict(msg: impl Into<String>) -> Self {
Self::new(409, codes::CONFLICT, msg)
}
pub fn unprocessable_entity(msg: impl Into<String>) -> Self {
Self::new(422, codes::UNPROCESSABLE_ENTITY, msg)
}
pub fn too_many_requests(msg: impl Into<String>) -> Self {
Self::new(429, codes::TOO_MANY_REQUESTS, msg)
}
pub fn internal(msg: impl Into<String>) -> Self {
Self::new(500, codes::INTERNAL, msg)
}
pub fn internal_masked(public_msg: impl Into<String>, detail: impl Into<String>) -> Self {
Self::new(500, codes::INTERNAL, public_msg)
.with_detail(detail)
}
pub fn service_unavailable(msg: impl Into<String>) -> Self {
Self::new(503, codes::SERVICE_UNAVAILABLE, msg)
}
}
impl From<Error> for ApiError {
fn from(e: Error) -> Self {
ApiError::internal_masked("服务器内部错误", e.to_string())
}
}
#[cfg(feature = "axum")]
impl<T: Serialize> IntoResponse for Res<T> {
fn into_response(self) -> axum::response::Response {
let mut resp = Json(self).into_response();
resp.headers_mut().insert(
axum::http::HeaderName::from_static("content-type"),
axum::http::HeaderValue::from_static("application/json; charset=utf-8"),
);
resp
}
}
#[cfg(feature = "axum")]
impl IntoResponse for ApiError {
fn into_response(self) -> axum::response::Response {
if let Some(ref detail) = self.internal_detail {
let status = StatusCode::from_u16(self.status)
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
tracing::error!(status = status.as_u16(), code = self.code, detail = %detail,
"请求处理异常");
}
let body = Res::<()>::fail(self.code, self.msg);
let status = StatusCode::from_u16(self.status)
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
let mut resp = (status, Json(body)).into_response();
resp.headers_mut().insert(
axum::http::HeaderName::from_static("content-type"),
axum::http::HeaderValue::from_static("application/json; charset=utf-8"),
);
resp
}
}