1use crate::Error;
7use serde::Serialize;
8
9#[cfg(feature = "axum")]
10use axum::{
11 response::{IntoResponse, Json},
12 http::StatusCode,
13};
14
15pub mod codes {
18 pub const OK: i32 = 0;
19 pub const BAD_REQUEST: i32 = 400;
20 pub const UNAUTHORIZED: i32 = 401;
21 pub const FORBIDDEN: i32 = 403;
22 pub const NOT_FOUND: i32 = 404;
23 pub const METHOD_NOT_ALLOWED: i32 = 405;
24 pub const CONFLICT: i32 = 409;
25 pub const UNPROCESSABLE_ENTITY: i32 = 422;
26 pub const TOO_MANY_REQUESTS: i32 = 429;
27 pub const INTERNAL: i32 = 500;
28 pub const SERVICE_UNAVAILABLE: i32 = 503;
29}
30
31#[derive(Debug, Clone, Serialize)]
35pub struct Res<T: Serialize = ()> {
36 pub code: i32,
38 pub msg: String,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub data: Option<T>,
43}
44
45#[derive(Debug, Clone, Serialize)]
47pub struct PageData<T: Serialize> {
48 pub list: T,
50 pub total: u64,
52 pub page: u64,
54 pub page_size: u64,
56}
57
58pub type ResResult<T> = std::result::Result<Res<T>, ApiError>;
60
61#[derive(Debug, Clone, serde::Deserialize)]
65pub struct PageQuery {
66 pub page: u64,
68 pub page_size: u64,
70}
71
72impl PageQuery {
73 pub fn new(page: u64, page_size: u64) -> Self {
80 let page = if page < 1 { 1 } else { page };
81 let page_size = if page_size < 1 { 10 } else if page_size > 1000 { 1000 } else { page_size };
82 Self { page, page_size }
83 }
84
85 pub fn offset(&self) -> u64 { (self.page - 1) * self.page_size }
87 pub fn limit(&self) -> u64 { self.page_size }
89}
90
91impl Res<()> {
94 pub fn ok_empty() -> Self {
96 Self { code: codes::OK, msg: "ok".into(), data: None }
97 }
98
99 pub fn ok_msg(msg: impl Into<String>) -> Self {
101 Self { code: codes::OK, msg: msg.into(), data: None }
102 }
103}
104
105impl<T: Serialize> Res<T> {
106 pub fn ok(data: T) -> Self {
115 Self { code: codes::OK, msg: "ok".into(), data: Some(data) }
116 }
117
118 pub fn ok_with_msg(data: T, msg: impl Into<String>) -> Self {
120 Self { code: codes::OK, msg: msg.into(), data: Some(data) }
121 }
122
123 pub fn fail(code: i32, msg: impl Into<String>) -> Self {
131 Self { code, msg: msg.into(), data: None }
132 }
133}
134
135impl<T: Serialize> Res<PageData<T>> {
136 pub fn page(list: T, total: u64, page: u64, page_size: u64) -> Self {
138 Self::ok(PageData { list, total, page, page_size })
139 }
140}
141
142#[derive(Debug)]
148pub struct ApiError {
149 pub code: i32,
151 pub msg: String,
153 pub status: u16,
155 pub internal_detail: Option<String>,
157}
158
159impl ApiError {
160 pub fn new(status: u16, code: i32, msg: impl Into<String>) -> Self {
166 Self { status, code, msg: msg.into(), internal_detail: None }
167 }
168
169 fn with_detail(mut self, detail: impl Into<String>) -> Self {
171 self.internal_detail = Some(detail.into());
172 self
173 }
174
175 pub fn bad_request(msg: impl Into<String>) -> Self {
179 Self::new(400, codes::BAD_REQUEST, msg)
180 }
181
182 pub fn unauthorized(msg: impl Into<String>) -> Self {
184 Self::new(401, codes::UNAUTHORIZED, msg)
185 }
186
187 pub fn forbidden(msg: impl Into<String>) -> Self {
189 Self::new(403, codes::FORBIDDEN, msg)
190 }
191
192 pub fn not_found(msg: impl Into<String>) -> Self {
194 Self::new(404, codes::NOT_FOUND, msg)
195 }
196
197 pub fn method_not_allowed(msg: impl Into<String>) -> Self {
199 Self::new(405, codes::METHOD_NOT_ALLOWED, msg)
200 }
201
202 pub fn conflict(msg: impl Into<String>) -> Self {
204 Self::new(409, codes::CONFLICT, msg)
205 }
206
207 pub fn unprocessable_entity(msg: impl Into<String>) -> Self {
209 Self::new(422, codes::UNPROCESSABLE_ENTITY, msg)
210 }
211
212 pub fn too_many_requests(msg: impl Into<String>) -> Self {
214 Self::new(429, codes::TOO_MANY_REQUESTS, msg)
215 }
216
217 pub fn internal(msg: impl Into<String>) -> Self {
221 Self::new(500, codes::INTERNAL, msg)
222 }
223
224 pub fn internal_masked(public_msg: impl Into<String>, detail: impl Into<String>) -> Self {
229 Self::new(500, codes::INTERNAL, public_msg)
230 .with_detail(detail)
231 }
232
233 pub fn service_unavailable(msg: impl Into<String>) -> Self {
235 Self::new(503, codes::SERVICE_UNAVAILABLE, msg)
236 }
237}
238
239impl From<Error> for ApiError {
240 fn from(e: Error) -> Self {
241 ApiError::internal_masked("服务器内部错误", e.to_string())
242 }
243}
244
245#[cfg(feature = "axum")]
248impl<T: Serialize> IntoResponse for Res<T> {
249 fn into_response(self) -> axum::response::Response {
250 let mut resp = Json(self).into_response();
251 resp.headers_mut().insert(
252 axum::http::HeaderName::from_static("content-type"),
253 axum::http::HeaderValue::from_static("application/json; charset=utf-8"),
254 );
255 resp
256 }
257}
258
259#[cfg(feature = "axum")]
260impl IntoResponse for ApiError {
261 fn into_response(self) -> axum::response::Response {
262 if let Some(ref detail) = self.internal_detail {
263 let status = StatusCode::from_u16(self.status)
264 .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
265 tracing::error!(status = status.as_u16(), code = self.code, detail = %detail,
266 "请求处理异常");
267 }
268 let body = Res::<()>::fail(self.code, self.msg);
269 let status = StatusCode::from_u16(self.status)
270 .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
271 let mut resp = (status, Json(body)).into_response();
272 resp.headers_mut().insert(
273 axum::http::HeaderName::from_static("content-type"),
274 axum::http::HeaderValue::from_static("application/json; charset=utf-8"),
275 );
276 resp
277 }
278}
279
280