Rust错误处理库actix-web-thiserror的使用:为actix-web框架提供自定义错误类型和thiserror集成支持
Rust错误处理库actix-web-thiserror的使用:为actix-web框架提供自定义错误类型和thiserror集成支持
简介
actix-web-thiserror是一个扩展thiserror crate功能的库,能够自动返回适当的actix-web响应。
错误定义示例
use actix_web_thiserror::ResponseError;
use thiserror::Error;
#[derive(Debug, Error, ResponseError)]
pub enum Base64ImageError {
#[response(reason = "INVALID_IMAGE_FORMAT")]
#[error("invalid image format")]
InvalidImageFormat,
#[response(reason = "INVALID_STRING")]
#[error("invalid string")]
InvalidString,
}
错误实现示例
pub async fn error_test() -> Result<HttpResponse, Error> {
Err(Base64ImageError::InvalidImageFormat)?
}
错误响应示例
reason
是一个字符串,可以以某种形式提供给客户端来解释错误(如果适用)。这里它是一个可以本地化的枚举。
{
"result": 0,
"reason": "INVALID_IMAGE_FORMAT"
}
错误日志记录
当错误通过HTTP响应返回时,错误文本会自动打印到日志中。
Apr 23 02:19:35.211 ERROR Response error: invalid image format
Base64ImageError(InvalidImageFormat), place: example/src/handler.rs:5 example::handler
完整示例代码
use actix_web::{web, App, HttpResponse, HttpServer, Error};
use actix_web_thiserror::ResponseError;
use thiserror::Error;
// 1. 定义自定义错误类型
#[derive(Debug, Error, ResponseError)]
pub enum MyError {
#[response(reason = "NOT_FOUND")]
#[error("resource not found")]
NotFound,
#[response(reason = "UNAUTHORIZED")]
#[error("unauthorized access")]
Unauthorized,
#[response(reason = "INTERNAL_ERROR")]
#[error("internal server error")]
InternalError,
}
// 2. 创建会返回这些错误的路由处理函数
async fn test_route() -> Result<HttpResponse, Error> {
// 模拟一个错误情况
Err(MyError::NotFound)?
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/test", web::get().to(test_route))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
安装
在项目目录中运行以下Cargo命令:
cargo add actix-web-thiserror
或者在Cargo.toml中添加以下行:
actix-web-thiserror = "0.2.7"
特性
- 与thiserror无缝集成,简化错误定义
- 自动生成适当的actix-web响应
- 内置错误日志记录
- 支持自定义错误消息和原因代码
- 轻量级实现(仅3.4 KiB)
完整示例demo
以下是一个更完整的示例,展示了如何在真实项目中使用actix-web-thiserror:
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Error};
use actix_web_thiserror::ResponseError;
use thiserror::Error;
use serde::Serialize;
// 自定义错误类型
#[derive(Debug, Error, ResponseError)]
pub enum ApiError {
#[response(reason = "USER_NOT_FOUND")]
#[error("user not found")]
UserNotFound,
#[response(reason = "INVALID_INPUT")]
#[error("invalid input data")]
InvalidInput,
#[response(reason = "DATABASE_ERROR")]
#[error("database operation failed")]
DatabaseError,
}
// 模拟用户数据结构
#[derive(Debug, Serialize)]
struct User {
id: u32,
name: String,
}
// 获取用户信息的处理函数
#[get("/users/{id}")]
async fn get_user(id: web::Path<u32>) -> Result<HttpResponse, Error> {
let user_id = id.into_inner();
// 模拟从数据库获取用户
if user_id == 0 {
return Err(ApiError::UserNotFound)?;
}
Ok(HttpResponse::Ok().json(User {
id: user_id,
name: "John Doe".to_string(),
}))
}
// 创建用户的处理函数
#[post("/users")]
async fn create_user(user: web::Json<User>) -> Result<HttpResponse, Error> {
if user.name.is_empty() {
return Err(ApiError::InvalidInput)?;
}
// 模拟数据库操作失败
if user.id == 999 {
return Err(ApiError::DatabaseError)?;
}
Ok(HttpResponse::Created().json(user.into_inner()))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(get_user)
.service(create_user)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
这个完整示例展示了:
- 定义了一个包含多种错误的ApiError枚举
- 实现了获取用户和创建用户的两个端点
- 演示了不同错误情况的处理
- 展示了如何返回JSON响应
- 包含了数据库操作和输入验证的模拟场景
当访问不存在的用户ID(0)时,会返回:
{
"result": 0,
"reason": "USER_NOT_FOUND"
}
当创建用户时提供空名称,会返回:
{
"result": 0,
"reason": "INVALID_INPUT"
}
同时错误信息也会自动记录到日志中。
1 回复
Rust错误处理库actix-web-thiserror的使用指南
actix-web-thiserror
是一个为 Actix-Web 框架提供自定义错误类型和 thiserror
集成支持的库。它简化了在 Actix-Web 应用中定义和使用自定义错误类型的过程。
主要特性
- 无缝集成
thiserror
宏 - 自动实现
ResponseError
trait - 简化错误到 HTTP 响应的转换
- 支持自定义错误响应格式
安装
在 Cargo.toml
中添加依赖:
[dependencies]
actix-web-thiserror = "1.0"
thiserror = "1.0"
actix-web = "4.0"
基本用法
1. 定义自定义错误类型
use actix_web_thiserror::ResponseError;
use thiserror::Error;
#[derive(Debug, Error, ResponseError)]
enum MyError {
#[error("Not Found: {0}")]
NotFound(String),
#[error("Internal Server Error")]
InternalError,
#[error("Bad Request: {0}")]
BadRequest(String),
#[error("Unauthorized")]
Unauthorized,
}
2. 在 Actix-Web 路由中使用
use actix_web::{get, web, App, HttpServer, Responder};
use actix_web_thiserror::ResponseError;
use thiserror::Error;
#[derive(Debug, Error, ResponseError)]
enum ApiError {
#[error("User not found")]
UserNotFound,
#[error("Invalid input: {0}")]
InvalidInput(String),
}
#[get("/user/{id}")]
async fn get_user(id: web::Path<u32>) -> Result<impl Responder, ApiError> {
if *id == 0 {
return Err(ApiError::InvalidInput("ID cannot be zero".to_string()));
}
if *id == 404 {
return Err(ApiError::UserNotFound);
}
Ok(format!("User ID: {}", id))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(get_user))
.bind("127.0.0.1:8080")?
.run()
.await
}
高级用法
自定义 HTTP 状态码
use actix_web::http::StatusCode;
use actix_web_thiserror::ResponseError;
use thiserror::Error;
#[derive(Debug, Error, ResponseError)]
enum CustomError {
#[error("Payment Required")]
#[response_error(status_code = 402)]
PaymentRequired,
#[error("Teapot")]
#[response_error(status_code = 418)]
ImATeapot,
}
自定义错误响应格式
use actix_web::HttpResponse;
use actix_web_thiserror::{ResponseError, ResponseErrorWrapper};
use serde_json::json;
use thiserror::Error;
#[derive(Debug, Error, ResponseError)]
#[response_error(render = "render_custom_error")]
enum JsonError {
#[error("Validation failed")]
ValidationError,
#[error("Database error")]
DatabaseError,
}
fn render_custom_error(err: &ResponseErrorWrapper<JsonError>) -> HttpResponse {
let status_code = err.status_code();
let body = json!({
"error": err.to_string(),
"code": status_code.as_u16(),
"details": match err.inner() {
JsonError::ValidationError => "Please check your input",
JsonError::DatabaseError => "Please try again later",
}
});
HttpResponse::build(status_code).json(body)
}
与 Actix-Web 中间件集成
use actix_web::{middleware, web, App, HttpServer};
use actix_web_thiserror::ResponseError;
use thiserror::Error;
#[derive(Debug, Error, ResponseError)]
enum MiddlewareError {
#[error("Missing API Key")]
MissingApiKey,
#[error("Invalid API Key")]
InvalidApiKey,
}
async fn check_api_key(req: actix_web::HttpRequest) -> Result<(), MiddlewareError> {
let api_key = req.headers().get("x-api-key");
if api_key.is_none() {
return Err(MiddlewareError::MissingApiKey);
}
if api_key.unwrap() != "secret" {
return Err(MiddlewareError::InvalidApiKey);
}
Ok(())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(middleware::NormalizePath::trim())
.service(
web::resource("/protected")
.to(|| async { "Protected content" })
.wrap_fn(|req, srv| {
let fut = check_api_key(req);
async move {
fut.await?;
srv.call(req).await
}
}),
)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
完整示例
下面是一个完整的 actix-web-thiserror 使用示例,展示了错误处理的全流程:
use actix_web::{get, web, App, HttpServer, Responder, HttpResponse};
use actix_web_thiserror::{ResponseError, ResponseErrorWrapper};
use serde_json::json;
use thiserror::Error;
// 1. 定义自定义错误类型
#[derive(Debug, Error, ResponseError)]
enum AppError {
#[error("Authentication failed")]
Unauthorized,
#[error("Resource not found: {0}")]
NotFound(String),
#[error("Invalid input: {0}")]
BadRequest(String),
#[error("Internal server error")]
#[response_error(status_code = 500)]
InternalError,
}
// 2. 自定义错误响应格式
fn custom_error_render(err: &ResponseErrorWrapper<AppError>) -> HttpResponse {
let status_code = err.status_code();
let body = json!({
"success": false,
"error": err.to_string(),
"status": status_code.as_u16(),
"timestamp": chrono::Utc::now().to_rfc3339()
});
HttpResponse::build(status_code).json(body)
}
// 3. 业务路由处理
#[get("/items/{id}")]
async fn get_item(id: web::Path<u32>) -> Result<impl Responder, AppError> {
match *id {
0 => Err(AppError::BadRequest("ID cannot be zero".into())),
404 => Err(AppError::NotFound("Item not found".into())),
401 => Err(AppError::Unauthorized),
500 => Err(AppError::InternalError),
_ => Ok(format!("Item ID: {}", id))
}
}
// 4. 主函数
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
// 应用自定义错误处理器
.app_data(web::JsonConfig::default().error_handler(|err, _req| {
AppError::BadRequest(format!("JSON error: {}", err)).into()
}))
.service(get_item)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
最佳实践
- 为不同的错误类别创建不同的错误枚举
- 为错误提供有意义的错误消息
- 考虑安全性,不要暴露内部错误细节
- 为前端消费设计一致的错误响应格式
- 记录内部错误以便调试
actix-web-thiserror
通过简化错误处理流程,让开发者能够更专注于业务逻辑的实现,同时保持错误处理的健壮性和一致性。