前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【技术布局】Rust Axum 如何优雅的返回数据

【技术布局】Rust Axum 如何优雅的返回数据

原创
作者头像
blueflyming
修改2025-01-16 17:11:52
修改2025-01-16 17:11:52
1410
举报

一、背景说明

最近在用rust写一套web脚手架,在定义返回结果的时候发现axum自带的返回写法挺丑的,所以打算简单封装下。这一部分使用到的库主要为:

  • axum
  • tokio
  • serde
  • thiserror

二、通用返回体

在当前设计下,暂且假定只要服务器接收到客户端请求,统一返回状态码为200,返回的结构体中包含结果状态码、消息、内容三个部分。由于axum的返回都需要实现IntoResponse trait。同时再为它提供两个方法用于创建对象。

代码语言:rust
复制
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并向上抛出。

代码语言:rust
复制
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

项目中所有的路由handler都统一返回Result<ApiResult<T>,ApiError>结构。

代码语言:rust
复制
pub async fn test_handler() -> Result<ApiResult<ResStruct>, ApiError> {
    let res = ResStruct {}
    Ok(ApiResult::success(res))
}

五、请求处理

一般不会在handler中直接处理请求,所以会有一个单独的处理函数,处理函数一般情况返回Result<ResStruct,ApiError>对象,如果遇到其他可能会抛出的异常,则通过map_err函数传入对应的转化函数来转化成ApiError。如下,在发生异常时转化为ApiError返回,如果处理正常则返回实际结果。

代码语言:rust
复制
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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景说明
  • 二、通用返回体
  • 三、通用异常
  • 四、路由handler
  • 五、请求处理
  • 六、结果展示
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档