use axum::Json; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use holzleitner_application::error::ApplicationError; use holzleitner_application::ports::AuthError; use serde_json::json; /// HTTP-Adapter für Application-Fehler. Mappt jede Variante auf /// einen Statuscode + JSON-Body. Verhindert, dass interne Details /// (z. B. SQL-Fehler) versehentlich in die Antwort durchschlagen — /// das eigentliche Detail wird über `tracing` geloggt. pub struct ApiError(pub ApplicationError); impl From for ApiError { fn from(value: ApplicationError) -> Self { Self(value) } } impl From for ApiError { fn from(err: AuthError) -> Self { // Auth-Detail für Debugging loggen, aber NICHT an den Client geben — // dort sehen wir nur „Unauthorized" / „internal error". tracing::debug!(error = %err, "auth verification failed"); ApiError(err.into()) } } impl IntoResponse for ApiError { fn into_response(self) -> Response { let (status, code) = match &self.0 { ApplicationError::NotFound => (StatusCode::NOT_FOUND, "not_found"), ApplicationError::Unauthorized => (StatusCode::UNAUTHORIZED, "unauthorized"), ApplicationError::Forbidden => (StatusCode::FORBIDDEN, "forbidden"), ApplicationError::Validation(_) => (StatusCode::BAD_REQUEST, "validation"), ApplicationError::Conflict(_) => (StatusCode::CONFLICT, "conflict"), ApplicationError::Repository(_) | ApplicationError::External(_) | ApplicationError::Unexpected(_) => { tracing::error!(error = %self.0, "internal error"); (StatusCode::INTERNAL_SERVER_ERROR, "internal_error") } }; let body = Json(json!({ "error": code, "message": self.0.to_string(), })); (status, body).into_response() } }