Table of contents
Open Table of contents
Handler 返回值的唯一要求
实现 IntoResponse trait:
trait IntoResponse {
fn into_response(self) -> Response; // 把自己变成 HTTP Response
}
内置的 IntoResponse 实现
// 字符串 → 200 + text/plain
async fn a() -> &'static str { "hello" }
async fn b() -> String { "hello".to_string() }
// Json → 200 + application/json
async fn c() -> Json<Player> { Json(player) }
// 状态码
async fn d() -> StatusCode { StatusCode::NOT_FOUND }
// 元组:(状态码, 内容)
async fn e() -> (StatusCode, String) {
(StatusCode::CREATED, "done".into())
}
// 元组:(状态码, 响应头, 内容)
async fn f() -> (StatusCode, [(&str, &str); 1], String) {
(StatusCode::OK, [("X-Custom", "value")], "body".into())
}
// Result<T, E>:T 和 E 都必须实现 IntoResponse
async fn g() -> Result<Json<Player>, StatusCode> { ... }
自定义类型实现 IntoResponse
struct HtmlPage(String);
impl IntoResponse for HtmlPage {
fn into_response(self) -> Response {
(
StatusCode::OK,
[("Content-Type", "text/html")],
self.0,
).into_response()
}
}
async fn page() -> HtmlPage { HtmlPage("<h1>Hi</h1>".into()) }
Result 返回值与 ? 操作符
问题:为什么要用 Result 做返回值?
为了在 handler 里用 ? 操作符。没有 Result 返回值,? 没地方 return Err。
问题:Result<Json<Player>, StatusCode> 能配合 ? 吗?
几乎不能。因为 ? 需要通过 From trait 把错误类型转成 StatusCode,而没有任何库的错误类型实现了这个转换:
async fn handler(State(db): State<PgPool>) -> Result<Json<Player>, StatusCode> {
let player = sqlx::query_as("SELECT ...")
.fetch_one(&db)
.await?; // 编译错误!没有 From<sqlx::Error> for StatusCode
Ok(Json(player))
}
Result<_, StatusCode> 只能手动构造 Err:
async fn handler() -> Result<Json<Player>, StatusCode> {
if some_condition {
return Err(StatusCode::NOT_FOUND); // 只能手动
}
Ok(Json(player))
}
实际做法:自定义错误类型
要让 ? 能用,需要一个同时实现了 IntoResponse 和 From<各种错误> 的类型:
struct AppError(anyhow::Error);
// 让 ? 能用:任何错误 → AppError
impl<E: Into<anyhow::Error>> From<E> for AppError {
fn from(err: E) -> Self {
AppError(err.into())
}
}
// 让 axum 能用:AppError → HTTP 响应
impl IntoResponse for AppError {
fn into_response(self) -> Response {
(StatusCode::INTERNAL_SERVER_ERROR, self.0.to_string()).into_response()
}
}
现在 handler 里所有 ? 都能用了:
async fn handler(State(db): State<PgPool>) -> Result<Json<Player>, AppError> {
let player = sqlx::query_as("SELECT ...")
.fetch_one(&db)
.await?; // sqlx::Error → anyhow::Error → AppError ✓
let token = generate_jwt(&player)?; // jwt::Error → anyhow::Error → AppError ✓
cache_session(&token).await?; // redis::Error → anyhow::Error → AppError ✓
Ok(Json(player))
}
? 的完整链路
handler 返回 Result<T, E>
成功路径:
Ok(Json(player))
→ axum 调 Json(player).into_response()
→ 200 + application/json + 序列化的 player
失败路径(? 触发):
sqlx::Error 发生
→ ? 调用 From::from(sqlx_error)
→ sqlx::Error → anyhow::Error → AppError
→ return Err(AppError(...))
→ axum 调 AppError.into_response()
→ 500 + 错误信息
更精细的错误映射
上面的方案把所有错误都返回 500。实际项目中可以区分错误类型返回不同状态码:
enum AppError {
NotFound(String),
BadRequest(String),
Internal(anyhow::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
match self {
AppError::NotFound(msg) =>
(StatusCode::NOT_FOUND, msg).into_response(),
AppError::BadRequest(msg) =>
(StatusCode::BAD_REQUEST, msg).into_response(),
AppError::Internal(err) =>
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(),
}
}
}
impl From<sqlx::Error> for AppError {
fn from(e: sqlx::Error) -> Self {
match e {
sqlx::Error::RowNotFound => AppError::NotFound("not found".into()),
other => AppError::Internal(other.into()),
}
}
}
Extractor 与 IntoResponse 的对称设计
请求进来 响应出去
│ ▲
▼ │
Extractor IntoResponse
(FromRequest trait) (IntoResponse trait)
"怎么从请求中取数据" "怎么把结果变成响应"
│ ▲
▼ │
└──► handler(提取的参数) -> 返回值 ──────────────┘
你的业务逻辑
两头都靠 trait 定义行为,中间是你的业务逻辑。