如何在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,依赖我们实现的Fromtrait。- 自定义的
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
相关产品推荐
相关产品推荐

