use axum::{Json, http::StatusCode, response::IntoResponse}; use serde::Serialize; #[derive(Debug, thiserror::Error)] pub enum AppError { #[error("bad request: {0}")] BadRequest(String), #[error("pokemon not found")] NotFound, #[error("upstream service returned invalid data: {0}")] InvalidUpstreamData(String), #[error("upstream service failed: {0}")] Upstream(String), #[error("request timed out")] Timeout, #[error("internal server error")] Internal, } impl AppError { #[must_use] pub const fn status_code(&self) -> StatusCode { match self { Self::BadRequest(_) => StatusCode::BAD_REQUEST, Self::NotFound => StatusCode::NOT_FOUND, Self::InvalidUpstreamData(_) | Self::Upstream(_) | Self::Timeout => { StatusCode::BAD_GATEWAY } Self::Internal => StatusCode::INTERNAL_SERVER_ERROR, } } } impl IntoResponse for AppError { fn into_response(self) -> axum::response::Response { let status = self.status_code(); let body = ErrorBody { error: self.public_message(), }; (status, Json(body)).into_response() } } impl AppError { fn public_message(&self) -> String { match self { Self::BadRequest(message) => message.clone(), Self::NotFound => "pokemon not found".to_owned(), Self::InvalidUpstreamData(_) => "upstream service returned invalid data".to_owned(), Self::Upstream(_) | Self::Timeout => "upstream service failed".to_owned(), Self::Internal => "internal server error".to_owned(), } } } #[derive(Serialize)] struct ErrorBody { error: String, }