You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Actix Web数据库查询时处理Panic并返回JSON错误

问题描述

我在API中查询MySQL数据库的代码如下:

#[get("/api/v2/tags")]
pub async fn get_tags(
    db: Data<R2D2Pool<MySqlConnectionManager>>
) -> Result<Json<Vec<Tag>>, GenericError> {
    let mut conn: PooledConnection<MySqlConnectionManager> = db.get().unwrap();

    let sql_str = "SELECT * FROM table_non_exist".to_string();
    let res = conn.query_map( sql_str, |(id, tagname, description)| Tag {
        id,
        tagname,
        description
    }).expect("Query failed.");

    println!("{:?}", res);

    Ok(Json(res))
}

当SQL语句出现错误(例如查询不存在的表)时,程序会触发Panic并崩溃,报错信息如下:

thread 'actix-rt|system:0|arbiter:0' panicked at 'Query failed.: MySqlError { ERROR 1146 (42S02): Table 'myblog.some' doesn't exist }', src/api/tag.rs:30:8
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

请问如何妥善处理该Panic,返回JSON格式的错误信息而非导致应用崩溃?

解决方案

要避免panic并返回JSON格式错误,核心是不要用unwrap()和expect()强制解包Result,而是通过?操作符将错误传播到函数返回值,同时确保错误类型能被Actix-web正确序列化为JSON。

步骤1:替换panic式的错误处理

把代码里的unwrap()和expect()换成?,让错误被自动转换为函数返回的GenericError:

#[get("/api/v2/tags")]
pub async fn get_tags(
    db: Data<R2D2Pool<MySqlConnectionManager>>
) -> Result<Json<Vec<Tag>>, GenericError> {
    // 用?替换unwrap(),连接池获取失败时返回错误而非panic
    let mut conn = db.get()?;

    let sql_str = "SELECT * FROM table_non_exist".to_string();
    // 用?替换expect(),查询失败时返回错误而非panic
    let res = conn.query_map(sql_str, |(id, tagname, description)| Tag {
        id,
        tagname,
        description
    })?;

    println!("{:?}", res);

    Ok(Json(res))
}

步骤2:确保GenericError支持JSON序列化

如果你的GenericError是自定义类型,需要实现ResponseError(Actix-web要求)和Serialize(JSON序列化) trait,同时为数据库相关错误实现From转换:

use actix_web::{error::ResponseError, HttpResponse};
use serde::Serialize;
use std::fmt;

// 自定义错误类型,包含错误码和消息
#[derive(Debug, Serialize)]
pub struct GenericError {
    pub code: u16,
    pub message: String,
}

// 实现Display trait,用于错误消息展示
impl fmt::Display for GenericError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.message)
    }
}

// 实现ResponseError trait,让Actix-web能将错误转为HTTP响应
impl ResponseError for GenericError {
    fn error_response(&self) -> HttpResponse {
        HttpResponse::build(self.status_code())
            .json(self) // 返回JSON格式的错误
    }

    fn status_code(&self) -> actix_web::http::StatusCode {
        actix_web::http::StatusCode::from_u16(self.code)
            .unwrap_or(actix_web::http::StatusCode::INTERNAL_SERVER_ERROR)
    }
}

// 将MySQL错误转换为GenericError
impl From<mysql::Error> for GenericError {
    fn from(err: mysql::Error) -> Self {
        let code = match err {
            mysql::Error::MySqlError(ref e) => match e.code {
                1146 => 404, // 表不存在映射为404
                _ => 500,    // 其他数据库错误映射为500
            },
            _ => 500,
        };
        GenericError {
            code,
            message: err.to_string(),
        }
    }
}

// 将R2D2连接池错误转换为GenericError
impl From<r2d2::Error> for GenericError {
    fn from(err: r2d2::Error) -> Self {
        GenericError {
            code: 500,
            message: format!("数据库连接失败: {}", err),
        }
    }
}

关键说明

  • ?操作符会自动将mysql::Error和r2d2::Error转换为GenericError,依赖我们实现的From trait。
  • 自定义的GenericError会被Actix-web序列化为JSON响应,比如表不存在时客户端会收到:
    {"code":404,"message":"ERROR 1146 (42S02): Table 'myblog.table_non_exist' doesn't exist"}
    
  • 生产代码中应彻底避免unwrap()和expect(),除非能100%确定Result为Ok值,否则都会引发panic导致服务崩溃。

内容的提问来源于stack exchange,提问作者Jun Q

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 17:32:46