最近在用rust写一套web脚手架,在定义返回结果的时候发现axum自带的返回写法挺丑的,所以打算简单封装下。这一部分使用到的库主要为:
在当前设计下,暂且假定只要服务器接收到客户端请求,统一返回状态码为200,返回的结构体中包含结果状态码、消息、内容三个部分。由于axum的返回都需要实现IntoResponse trait。同时再为它提供两个方法用于创建对象。
use axum::http::StatusCode;
use axum::{response::IntoResponse, Json};
use serde::Serialize;
use serde_json::json;
// 成功响应码和信息
const SUCCESS_CODE: i32 = StatusCode::OK.as_u16() as i32;
const SUCCESS_MESSAGE: &str = "success";
/// 响应结构体
#[derive(Debug, Serialize)]
pub struct ApiResult<T> {
code: i32, // 响应码,通常用于表示请求的结果状态
data: Option<T>, // 可选的数据部分,包含请求成功时返回的数据
message: String, // 响应信息,描述请求的结果或错误信息
}
// 实现 `IntoResponse` trait 以将 ApiResult 转换为 Axum 响应
impl<T: Serialize> IntoResponse for ApiResult<T> {
fn into_response(self) -> axum::response::Response {
let val = json!(self); // 将 ApiResult 转换为 JSON 格式
Json(val).into_response() // 将 JSON 响应转换为 Axum 的响应格式
}
}
// 封装成功和错误响应
impl<T> ApiResult<T> {
/// 成功响应
/// 响应码为 200, 响应信息为 "success", data 为传入的 data 可选
pub fn success(data: T) -> Self {
Self {
code: SUCCESS_CODE, // 成功状态码
data: Some(data), // 包含成功时返回的数据
message: SUCCESS_MESSAGE.to_owned(), // 成功信息
}
}
/// 错误响应
/// 指定错误码和错误信息
pub fn error(code: i32, message: String) -> Self {
Self {
code: code, // 错误状态码
data: None, // 不包含数据
message: message, // 错误信息
}
}
}
在系统中提供一个统一的全局异常,包装所有其他的异常,并且提供可扩展的异常信息。然后为它实现IntoResponse的trait,将所有异常信息匹配为ApiResult对象返回给客户端。
下面代码中的 DatabaseError
是匹配该异常,创建为ApiError
异常。
同时提供一个map_db_error
方法,用于在可能会抛出sea_orm::DbErr
的地方通过.map_err(map_db_error)
来转化成ApiErr
并向上抛出。
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use thiserror::Error;
use super::api_result::ApiResult;
/// 定义 API 错误类型
#[derive(Error, Debug)]
pub enum ApiError {
// 数据库错误,允许将 sea_orm::DbErr 转换为 ApiError
#[error(transparent)]
DatabaseError(#[from] sea_orm::DbErr),
// 其他错误,包含错误信息
#[error("服务异常: {0}")]
OtherError(String),
}
/// 实现 IntoResponse trait 以将 ApiError 转换为 Axum 响应
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
// 根据错误类型生成相应的状态码和消息
let (status_code, message) = match &self {
ApiError::DatabaseError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.to_string()),
ApiError::OtherError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.to_string()),
};
// 构建 ApiResult 错误响应并转换为 Axum 响应
let into_response_tuple = (
StatusCode::OK, // 这里的状态码可以根据需要调整
ApiResult::<()>::error(status_code.as_u16() as i32, message), // 使用 ApiResult 构建错误响应
)
.into_response();
into_response_tuple.into_response() // 返回最终的响应
}
}
pub fn map_db_error(err: sea_orm::DbErr) -> ApiError {
ApiError::DatabaseError(err)
}
项目中所有的路由handler都统一返回Result<ApiResult<T>,ApiError>
结构。
pub async fn test_handler() -> Result<ApiResult<ResStruct>, ApiError> {
let res = ResStruct {}
Ok(ApiResult::success(res))
}
一般不会在handler中直接处理请求,所以会有一个单独的处理函数,处理函数一般情况返回Result<ResStruct,ApiError>
对象,如果遇到其他可能会抛出的异常,则通过map_err函数传入对应的转化函数来转化成ApiError。如下,在发生异常时转化为ApiError返回,如果处理正常则返回实际结果。
let async fn test_service(db: &DatabaseConnection)
-> Result<system_account::Entity, ApiError> {
let option = system_account::Entity::find()
.filter(system_account::Column::Username.eq(login_req.username.clone()))
.order_by_asc(system_account::Column::Id)
.one(db)
.await
.map_err(map_db_error)?; // 这里转化数据库异常为ApiError
if option.is_none() {
// 这里返回自定义的异常信息
return Err(ApiError::OtherError("用户不存在!".to_string()));
}
Ok(option.unwrap())
}
附上两张请求通过和异常的截图:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。