use axum::{ Json, http::StatusCode, response::{IntoResponse, Response}, }; use serde::Serialize; use thiserror::Error; #[derive(Debug, Error)] pub enum ApiError { #[error("{0}")] BadRequest(String), #[error("{0}")] NotFound(String), #[error("{0}")] Conflict(String), #[error("internal server error")] Internal(#[from] anyhow::Error), } #[derive(Serialize)] struct ErrorBody { error: String, } impl IntoResponse for ApiError { fn into_response(self) -> Response { let status = match self { Self::BadRequest(_) => StatusCode::BAD_REQUEST, Self::NotFound(_) => StatusCode::NOT_FOUND, Self::Conflict(_) => StatusCode::CONFLICT, Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, }; if let Self::Internal(ref error) = self { tracing::error!(?error, "request failed"); } ( status, Json(ErrorBody { error: self.to_string(), }), ) .into_response() } } impl From for ApiError { fn from(error: sqlx::Error) -> Self { match error { sqlx::Error::RowNotFound => Self::NotFound("resource not found".into()), other => Self::Internal(other.into()), } } } impl From for ApiError { fn from(error: std::io::Error) -> Self { Self::Internal(error.into()) } }