From 8ef937357b29243ed0ee368559d8b68ad32a4646 Mon Sep 17 00:00:00 2001 From: zichun Date: Fri, 10 Apr 2026 17:10:33 +0800 Subject: [PATCH] fix: GlobalExceptionHandler propagates ResponseStatusException status codes --- platform/platform-bootstrap/src/main/kotlin/org/vibeerp/platform/bootstrap/web/GlobalExceptionHandler.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+), 0 deletions(-) diff --git a/platform/platform-bootstrap/src/main/kotlin/org/vibeerp/platform/bootstrap/web/GlobalExceptionHandler.kt b/platform/platform-bootstrap/src/main/kotlin/org/vibeerp/platform/bootstrap/web/GlobalExceptionHandler.kt index 7109c2b..98b2435 100644 --- a/platform/platform-bootstrap/src/main/kotlin/org/vibeerp/platform/bootstrap/web/GlobalExceptionHandler.kt +++ b/platform/platform-bootstrap/src/main/kotlin/org/vibeerp/platform/bootstrap/web/GlobalExceptionHandler.kt @@ -3,8 +3,10 @@ package org.vibeerp.platform.bootstrap.web import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.ProblemDetail +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.RestControllerAdvice +import org.springframework.web.server.ResponseStatusException import org.vibeerp.platform.security.AuthenticationFailedException import org.vibeerp.platform.security.authz.PermissionDeniedException import java.time.Instant @@ -87,6 +89,23 @@ class GlobalExceptionHandler { problem(HttpStatus.FORBIDDEN, "permission denied: '${ex.permissionKey}'") /** + * Spring's `ResponseStatusException` carries its own HTTP status code + * (e.g. 400, 403, 409). Handlers and filters that throw it expect the + * status to propagate verbatim. Without this handler the catch-all + * `Throwable` branch would swallow it as a generic 500. + */ + @ExceptionHandler(ResponseStatusException::class) + fun handleResponseStatus(ex: ResponseStatusException): ResponseEntity> { + return ResponseEntity.status(ex.getStatusCode()).body(mapOf( + "type" to "about:blank", + "title" to ex.getStatusCode().toString(), + "status" to ex.getStatusCode().value(), + "detail" to (ex.reason ?: ex.message), + "timestamp" to Instant.now().toString(), + )) + } + + /** * Last-resort fallback. Anything not handled above is logged with a * full stack trace and surfaced as a generic 500 to the caller. The * detail message is intentionally vague so we don't leak internals. -- libgit2 0.22.2