# 错误处理集成指南
本文档介绍 how to 使用 `anycms-core` 的 thiserror 错误处理功能。
## 概述
`anycms-core` 现在提供了完整的错误处理集成,支持:
- **ErrorCode**: 标准化的错误码枚举,自动映射到 HTTP 状态码
- **ApiError**: 简单的 API 错误类型
- **AppError**: 通用的应用错误类型(使用 thiserror)
- **IntoApiResult trait**: 便捷的 Result 转换
## 核心类型
### ErrorCode
```rust
pub enum ErrorCode {
Success = 0,
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404,
Conflict = 409,
ValidationError = 422,
InternalError = 500,
// ...
}
```
### ApiError
简单直接的错误类型:
```rust
use anycms_core::{ApiError, ErrorCode};
// 使用错误码
let err = ApiError::new(ErrorCode::NotFound, "User not found");
// 使用便捷方法
let err = ApiError::not_found("User not found");
let err = ApiError::bad_request("Invalid email");
let err = ApiError::unauthorized("Please login");
```
### AppError
使用 thiserror 的通用错误类型:
```rust
use anycms_core::AppError;
use thiserror::Error;
#[derive(Error, Debug)]
enum MyError {
#[error("Database error: {0}")]
Database(String),
}
// 自动转换
let result: Result<User, MyError> = ...;
let api_result: ApiResult<User> = result.into(); // 自动转换为 ApiResult
```
## 使用模式
### 模式 1: 使用 ApiError
```rust
use anycms_core::{ApiResult, ApiError};
async fn get_user(id: u32) -> Result<User, UserServiceError> {
// ...
}
#[get("/users/{id}")]
async fn handler(path: web::Path<u32>) -> ApiResult<User> {
let user_id = path.into_inner();
get_user(user_id)
.await
.map(|user| ApiResult::value(user))
.unwrap_or_else(|e| ApiError::from(e).into())
}
```
### 模式 2: 使用 IntoApiResult trait
```rust
use anycms_core::{ApiResult, ApiError, IntoApiResult};
async fn find_user(id: u32) -> Result<User, UserServiceError> {
// ...
}
#[get("/users/{id}")]
async fn handler(path: web::Path<u32>) -> ApiResult<User> {
let user_id = path.into_inner();
find_user(user_id)
.await
.into_api_result_with(|| {
ApiError::not_found(format!("User {} not found", user_id))
})
}
```
### 模式 3: 自定义错误类型
```rust
use thiserror::Error;
use anycms_core::{ApiError, ApiResult};
#[derive(Error, Debug)]
enum UserServiceError {
#[error("User {0} not found")]
UserNotFound(u32),
#[error("Email {0} already exists")]
EmailAlreadyExists(String),
}
impl From<UserServiceError> for ApiError {
fn from(err: UserServiceError) -> Self {
match err {
UserServiceError::UserNotFound(id) => {
ApiError::not_found(format!("User {} not found", id))
}
UserServiceError::EmailAlreadyExists(email) => {
ApiError::conflict(format!("Email {} already registered", email))
}
}
}
}
// 使用
async fn handler() -> ApiResult<User> {
find_user(1)
.await
.map(|user| ApiResult::value(user))
.unwrap_or_else(|e| ApiError::from(e).into())
}
```
### 模式 4: Early Return
```rust
#[post("/users")]
async fn create_user(data: web::Json<CreateUserRequest>) -> ApiResult<User> {
// 验证
let validation = validate_email(&data.email).await;
if let Err(e) = validation {
return ApiError::from(e).into();
}
// 检查冲突
if email_exists(&data.email).await {
return ApiError::conflict("Email already registered").into();
}
// 创建用户
let user = create_user(data).await;
ApiResult::value(user)
}
```
## HTTP 状态码自动映射
当使用 ApiError 或 AppError 时,HTTP 状态码会自动根据错误码设置:
```rust
ApiError::not_found(...) // HTTP 404
ApiError::bad_request(...) // HTTP 400
ApiError::unauthorized(...) // HTTP 401
ApiError::forbidden(...) // HTTP 403
ApiError::validation(...) // HTTP 422
ApiError::conflict(...) // HTTP 409
ApiError::internal(...) // HTTP 500
```
## 完整示例
查看 `examples/error_handling_demo.rs` 了解所有错误处理模式。
运行示例:
```bash
cargo run --example error_handling_demo --features actix
```
测试不同错误类型:
```bash
# 成功
curl http://localhost:8080/users/1
# 404 Not Found
curl http://localhost:8080/users/999
# HTTP 状态码演示
curl http://localhost:8080/error-demo/not_found # 404
curl http://localhost:8080/error-demo/bad_request # 400
curl http://localhost:8080/error-demo/conflict # 409
```
## 最佳实践
1. **定义领域特定的错误类型**:使用 thiserror 定义业务错误
2. **实现 From<YourError> for ApiError**:统一转换路径
3. **使用语义化的错误码**:选择正确的 ErrorCode
4. **提供清晰的错误消息**:帮助客户端理解问题
5. **记录内部错误**:使用日志记录详细信息,但只向客户端返回友好消息
## 迁移指南
### 从旧的 ApiResult::failure 迁移
**之前:**
```rust
ApiResult::<User>::failure("User not found")
```
**现在:**
```rust
ApiError::not_found("User not found").into()
```
### 从 match 语句迁移
**之前:**
```rust
match get_user(id).await {
Some(user) => ApiResult::value(user),
None => ApiResult::<User>::failure("Not found"),
}
```
**现在:**
```rust
get_user(id)
.await
.ok_or_else(|| ApiError::not_found("User not found"))
.into_api_result()
```