//! API errors and their wire representation. use axum::{ Json, http::StatusCode, response::{IntoResponse, Response}, }; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("unauthorized")] Unauthorized, #[error("forbidden: missing scope {0}")] Forbidden(&'static str), #[error("not found")] NotFound, #[error("bad request: {0}")] BadRequest(String), #[error("not implemented")] NotImplemented, #[error(transparent)] Store(#[from] openmail_store::Error), } impl IntoResponse for Error { fn into_response(self) -> Response { let status = match &self { Self::Unauthorized => StatusCode::UNAUTHORIZED, Self::Forbidden(_) => StatusCode::FORBIDDEN, Self::NotFound | Self::Store(openmail_store::Error::NotFound) => StatusCode::NOT_FOUND, Self::BadRequest(_) => StatusCode::BAD_REQUEST, Self::NotImplemented => StatusCode::NOT_IMPLEMENTED, Self::Store(_) => StatusCode::INTERNAL_SERVER_ERROR, }; // Internal errors are logged in full and reported as a bare string. A // database error message can carry schema and connection detail, and // this endpoint is reachable by anyone with a key. let message = match &self { Self::Store(e) if status == StatusCode::INTERNAL_SERVER_ERROR => { tracing::error!(error = ?e, "internal error"); "internal error".to_owned() } other => other.to_string(), }; (status, Json(serde_json::json!({ "error": message }))).into_response() } }