ข้ามไปยังเนื้อหา

การจัดการ Error

ใน Express errors propagate ผ่าน next(err) ไปยัง 4-argument error handler ที่ลงทะเบียนที่ด้านล่างของ middleware stack ใน Axum ไม่มี global error handler แต่ handlers return Result<T, E> ที่ E implement IntoResponse แทน ถ้า handler return Err(e) Axum จะเรียก e.into_response() โดยอัตโนมัติ

TypeScript
// Express — propagate error to error handler
app.get('/users/:id', async (req, res, next) => {
try {
const user = await db.findUser(req.params.id);
if (!user) return next(new NotFoundError('User not found'));
res.json(user);
} catch (err) {
next(err); // pass to error middleware
}
});
// 4-argument error handler
app.use((err, req, res, next) => {
const status = err.status ?? 500;
res.status(status).json({ error: err.message });
});
Rust
use axum::{
http::StatusCode,
response::{IntoResponse, Json, Response},
};
use serde_json::json;
// Custom error type
#[derive(thiserror::Error, Debug)]
enum AppError {
#[error("not found: {0}")]
NotFound(String),
#[error("database error")]
Database(#[from] sqlx::Error),
}
// Implement IntoResponse so Axum can convert errors to HTTP responses
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match &self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
AppError::Database(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
"Internal server error".to_string(),
),
};
(status, Json(json!({ "error": message }))).into_response()
}
}
// Handler returns Result<Json<User>, AppError>
async fn get_user(Path(id): Path<u32>) -> Result<Json<User>, AppError> {
let user = db_find(id).await.map_err(AppError::Database)?;
user.ok_or_else(|| AppError::NotFound(format!("user {} not found", id)))
.map(Json)
}

? operator คือ shorthand สำหรับ early-return ของ Result ใน Rust ใช้แทน try/catch สำหรับ error ที่กู้คืนได้:

async fn get_user(Path(id): Path<u32>) -> Result<Json<User>, AppError> {
// ? returns Err(AppError::Database(...)) early if the query fails
let user = db_find(id).await?;
// Option::ok_or converts None to Err
let user = user.ok_or_else(|| AppError::NotFound(format!("user {id} not found")))?;
Ok(Json(user))
}

thiserror generates Display และ Error implementations จาก attribute macros — เทียบเท่ากับการกำหนด discriminated union ของ error cases ใน TypeScript:

TypeScript
// TypeScript — discriminated union
type AppError =
| { kind: 'NotFound'; message: string }
| { kind: 'Validation'; fields: string[] }
| { kind: 'Database'; cause: Error };
function toHttpError(err: AppError): [number, string] {
switch (err.kind) {
case 'NotFound': return [404, err.message];
case 'Validation': return [422, err.fields.join(', ')];
case 'Database': return [500, 'Internal server error'];
}
}
Rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("not found: {0}")]
NotFound(String),
#[error("validation error: {0}")]
Validation(String),
#[error("database error: {0}")]
Database(#[from] sqlx::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
match self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg).into_response(),
AppError::Validation(msg) => (StatusCode::UNPROCESSABLE_ENTITY, msg).into_response(),
AppError::Database(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
}
}
}

รันบนเครื่องของคุณ — การจัดการ error ต้องการ Axum server ที่กำลังทำงาน ทดสอบด้วย curl -v http://localhost:3000/users/999 เพื่อดู error response

`?` ทำอะไรเมื่อใช้ใน `async fn` ที่ return `Result<T, AppError>`?
trait ใดที่ custom error type ต้องการ implement เพื่อ return จาก Axum handler?
crate ใดที่ให้ `#[error("...")]` derive macro สำหรับ error enums ที่สะอาดขึ้น?