错误处理 #
错误类型 #
Actix Web 使用 actix_web::Error 类型表示错误,它实现了 std::error::Error trait。
text
┌─────────────────────────────────────────────────────────────┐
│ 错误处理流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 处理函数返回 Result<T, Error> │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 错误转换 │ │
│ │ 实现 ResponseError trait │ │
│ │ 将错误转换为 HTTP 响应 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────┴────┐ │
│ │ │ │
│ ▼ ▼ │
│ Ok(T) Err(Error) │
│ │ │ │
│ ▼ ▼ │
│ 正常响应 错误响应 │
│ │
└─────────────────────────────────────────────────────────────┘
ResponseError Trait #
基本实现 #
rust
use actix_web::{HttpResponse, ResponseError};
use std::fmt;
#[derive(Debug)]
pub struct AppError {
message: String,
status: actix_web::http::StatusCode,
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl ResponseError for AppError {
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status)
.json(serde_json::json!({
"error": self.message
}))
}
}
自定义错误类型 #
rust
use actix_web::{http::StatusCode, HttpResponse, ResponseError};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ApiError {
#[error("Not found: {0}")]
NotFound(String),
#[error("Bad request: {0}")]
BadRequest(String),
#[error("Unauthorized: {0}")]
Unauthorized(String),
#[error("Internal server error: {0}")]
InternalError(String),
}
impl ResponseError for ApiError {
fn error_response(&self) -> HttpResponse {
match self {
ApiError::NotFound(msg) => HttpResponse::NotFound()
.json(serde_json::json!({ "error": msg })),
ApiError::BadRequest(msg) => HttpResponse::BadRequest()
.json(serde_json::json!({ "error": msg })),
ApiError::Unauthorized(msg) => HttpResponse::Unauthorized()
.json(serde_json::json!({ "error": msg })),
ApiError::InternalError(msg) => HttpResponse::InternalServerError()
.json(serde_json::json!({ "error": msg })),
}
}
fn status_code(&self) -> StatusCode {
match self {
ApiError::NotFound(_) => StatusCode::NOT_FOUND,
ApiError::BadRequest(_) => StatusCode::BAD_REQUEST,
ApiError::Unauthorized(_) => StatusCode::UNAUTHORIZED,
ApiError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
pub type ApiResult<T> = Result<T, ApiError>;
在处理函数中使用 #
返回 Result #
rust
use actix_web::{web, Responder};
#[actix_web::get("/users/{id}")]
async fn get_user(path: web::Path<u32>) -> ApiResult<impl Responder> {
let id = path.into_inner();
if id > 100 {
return Err(ApiError::NotFound(format!("User {} not found", id)));
}
Ok(web::Json(serde_json::json!({
"id": id,
"name": "Alice"
})))
}
错误转换 #
rust
impl From<sqlx::Error> for ApiError {
fn from(err: sqlx::Error) -> Self {
match err {
sqlx::Error::RowNotFound => ApiError::NotFound("Record not found".to_string()),
_ => ApiError::InternalError(err.to_string()),
}
}
}
async fn get_user_from_db(id: u32) -> ApiResult<User> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
.fetch_one(&pool)
.await?;
Ok(user)
}
错误中间件 #
全局错误处理 #
rust
use actix_web::{
body::EitherBody,
dev::{Service, ServiceRequest, ServiceResponse, Transform},
http::StatusCode,
middleware::{ErrorHandlerResponse, ErrorHandlers},
Error, HttpResponse,
};
fn error_handler<B>(res: ServiceResponse<B>) -> actix_web::Result<ErrorHandlerResponse<B>> {
let status = res.status();
if status.is_client_error() || status.is_server_error() {
let response = HttpResponse::build(status)
.json(serde_json::json!({
"error": status.canonical_reason().unwrap_or("Unknown error"),
"status": status.as_u16()
}));
let res = ServiceResponse::new(res.request().clone(), response)
.map_into_right_body();
return Ok(ErrorHandlerResponse::Response(res));
}
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
}
App::new()
.wrap(ErrorHandlers::new()
.handler(StatusCode::NOT_FOUND, error_handler)
.handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler)
)
JSON 错误响应 #
统一错误格式 #
rust
use serde::Serialize;
#[derive(Serialize)]
struct ErrorResponse {
success: bool,
error: ErrorDetail,
}
#[derive(Serialize)]
struct ErrorDetail {
code: u16,
message: String,
#[serde(skip_serializing_if = "Option::is_none")]
details: Option<Vec<String>>,
}
impl ResponseError for ApiError {
fn error_response(&self) -> HttpResponse {
let status = self.status_code();
HttpResponse::build(status)
.json(ErrorResponse {
success: false,
error: ErrorDetail {
code: status.as_u16(),
message: self.to_string(),
details: None,
},
})
}
}
验证错误 #
rust
use validator::ValidationErrors;
#[derive(Error, Debug)]
pub enum ValidationError {
#[error("Validation failed")]
InvalidInput { errors: ValidationErrors },
}
impl ResponseError for ValidationError {
fn error_response(&self) -> HttpResponse {
match self {
ValidationError::InvalidInput { errors } => {
HttpResponse::UnprocessableEntity()
.json(serde_json::json!({
"error": "Validation failed",
"details": errors.field_errors()
.into_iter()
.map(|(field, errs)| {
(field, errs.iter().map(|e| e.code.clone()).collect::<Vec<_>>())
})
.collect::<std::collections::HashMap<_, _>>()
}))
}
}
}
fn status_code(&self) -> StatusCode {
StatusCode::UNPROCESSABLE_ENTITY
}
}
完整示例 #
rust
use actix_web::{http::StatusCode, web, App, HttpResponse, HttpServer, Responder, ResponseError};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ApiError {
#[error("Not found: {0}")]
NotFound(String),
#[error("Bad request: {0}")]
BadRequest(String),
#[error("Unauthorized")]
Unauthorized,
#[error("Internal error: {0}")]
Internal(String),
}
impl ResponseError for ApiError {
fn error_response(&self) -> HttpResponse {
let status = self.status_code();
HttpResponse::build(status).json(ErrorResponse {
success: false,
error: self.to_string(),
code: status.as_u16(),
})
}
fn status_code(&self) -> StatusCode {
match self {
ApiError::NotFound(_) => StatusCode::NOT_FOUND,
ApiError::BadRequest(_) => StatusCode::BAD_REQUEST,
ApiError::Unauthorized => StatusCode::UNAUTHORIZED,
ApiError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
#[derive(Serialize)]
struct ErrorResponse {
success: bool,
error: String,
code: u16,
}
#[derive(Serialize)]
struct User {
id: u32,
name: String,
email: String,
}
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
type Result<T> = std::result::Result<T, ApiError>;
#[actix_web::get("/users/{id}")]
async fn get_user(path: web::Path<u32>) -> Result<impl Responder> {
let id = path.into_inner();
if id > 100 {
return Err(ApiError::NotFound(format!("User {} not found", id)));
}
Ok(web::Json(User {
id,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
}))
}
#[actix_web::post("/users")]
async fn create_user(body: web::Json<CreateUser>) -> Result<impl Responder> {
if body.name.is_empty() {
return Err(ApiError::BadRequest("Name cannot be empty".to_string()));
}
if !body.email.contains('@') {
return Err(ApiError::BadRequest("Invalid email format".to_string()));
}
Ok(HttpResponse::Created().json(User {
id: 1,
name: body.name.clone(),
email: body.email.clone(),
}))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("Server running at http://127.0.0.1:8080");
HttpServer::new(|| {
App::new()
.service(get_user)
.service(create_user)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
下一步 #
现在你已经掌握了错误处理,继续学习 应用状态,深入了解状态管理!
最后更新:2026-03-29