2025/05/12
はじめに
Rustのコードでよく thiserrorを使ってErrorの伝搬を楽にしているのだが、 axumのhandlerでは、axum::response::Response
そのもの、あるいは、axum::response::IntoResponse
を実装した型でなければならない。
実装例
thiserror::Error
を実装した型をそのままhandlerのResultには渡せないので、axum::response::IntoResponse
の実装を行うことで、handlerのResultに渡せるようにする。
返却するための型(ここでは ApiError
)は、 into_response()
内部に書いても構わない。が、 serde::Serialize
は必要である。
また、APIのレスポンスに詳細の情報を含めないようにするために、 match
で分岐するのもよいかもしれない。
#[derive(Debug, thiserror::Error)]
pub enum AppError {
#[error("Io error: {0}")]
Io(#[from] std::io::Error),
#[error("custom error: {0}")]
Custom(String),
}
#[derive(serde::Serialize)]
pub struct ApiError {
pub code: u16,
pub message: String,
}
impl axum::response::IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(ApiError {
code: 500,
message: self.to_string(),
}),
)
.into_response()
}
}
おわりに
IntoResponse
の実装を準備しておくことで、handler実装のResultに直接 thiserror
実装を渡すことができるようになり、実装を進めるのが楽になる。
それだけではなく、レスポンスに渡すエラー詳細のマスクを行ったり、エラーの詳細をAPIのレスポンスに含めないようにすること、あるいはClient側でのエラー切り分けが実現できるような基盤を整えることができる。
utoipa
と ApiError
の連携をしたい場合は、こちらを参照。