//! Render a `MizanError` as an axum `Response`: the JSON envelope //! `{"error": {"code": ..., "message": ..., "details": ...}}` under a //! `Cache-Control: no-store` header. use axum::http::{header, HeaderValue, StatusCode}; use axum::response::{IntoResponse, Response}; use axum::Json; use mizan_core::MizanError; pub struct ApiError(pub MizanError); impl From for ApiError { fn from(e: MizanError) -> Self { Self(e) } } /// Each variant's status spelled as an axum constant. Naming the constant /// rather than round-tripping a `u16` leaves no numeric value axum could /// reject, so the mapping is total. fn status_of(err: &MizanError) -> StatusCode { match err { MizanError::NotFound(_) => StatusCode::NOT_FOUND, MizanError::BadRequest(_) => StatusCode::BAD_REQUEST, MizanError::ValidationFailed { .. } => StatusCode::UNPROCESSABLE_ENTITY, MizanError::Unauthorized(_) => StatusCode::UNAUTHORIZED, MizanError::Forbidden(_) => StatusCode::FORBIDDEN, MizanError::NotImplementedYet(_) => StatusCode::NOT_IMPLEMENTED, MizanError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, } } impl IntoResponse for ApiError { fn into_response(self) -> Response { let mut resp = (status_of(&self.0), Json(self.0.to_json())).into_response(); resp.headers_mut() .insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store")); resp } }