diff --git a/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/error/GlobalExceptionHandler.java b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/error/GlobalExceptionHandler.java index 1c3ebc6..0634b82 100644 --- a/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/error/GlobalExceptionHandler.java +++ b/ordinis-rest-api/src/main/java/cloud/nstart/terravault/ordinis/restapi/error/GlobalExceptionHandler.java @@ -11,6 +11,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.server.ResponseStatusException; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.resource.NoResourceFoundException; @@ -112,6 +113,33 @@ public class GlobalExceptionHandler { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body); } + /** + * Preserve status code from {@link ResponseStatusException} (Spring's standard + * way чтобы throw a specific HTTP status from controller). Without explicit + * handler the generic fallback below catches it и turns 401/403/409 в 500 — + * каждый use of throw new ResponseStatusException(...) silently downgraded. + * + *

Observed bug: MeNotificationsPreferencesController.requireSub() throws + * 401 anonymous, но клиент получал 500 пока этот handler не появился. + */ + @ExceptionHandler(ResponseStatusException.class) + public ResponseEntity onResponseStatusException(ResponseStatusException e) { + int status = e.getStatusCode().value(); + String reason = e.getReason() != null ? e.getReason() : e.getStatusCode().toString(); + // Lower-case-snake_case code from status reason — best-effort, falls back + // на 'http_' если reason missing/multi-word. + String code = (e.getReason() != null && !e.getReason().contains(" ")) + ? e.getReason().toLowerCase().replace(' ', '_') + : "http_" + status; + if (status >= 500) { + log.error("ResponseStatusException 5xx: {}", reason, e); + } else { + log.debug("ResponseStatusException {}: {}", status, reason); + } + var body = ApiErrorResponse.of(status, code, reason, currentTraceId()); + return ResponseEntity.status(e.getStatusCode()).body(body); + } + @ExceptionHandler(Exception.class) public ResponseEntity onGeneric(Exception e) { log.error("Unhandled exception in REST API", e);