Commit 8ef937357b29243ed0ee368559d8b68ad32a4646

Authored by zichun
1 parent 02ac41c1

fix: GlobalExceptionHandler propagates ResponseStatusException status codes

platform/platform-bootstrap/src/main/kotlin/org/vibeerp/platform/bootstrap/web/GlobalExceptionHandler.kt
@@ -3,8 +3,10 @@ package org.vibeerp.platform.bootstrap.web @@ -3,8 +3,10 @@ package org.vibeerp.platform.bootstrap.web
3 import org.slf4j.LoggerFactory 3 import org.slf4j.LoggerFactory
4 import org.springframework.http.HttpStatus 4 import org.springframework.http.HttpStatus
5 import org.springframework.http.ProblemDetail 5 import org.springframework.http.ProblemDetail
  6 +import org.springframework.http.ResponseEntity
6 import org.springframework.web.bind.annotation.ExceptionHandler 7 import org.springframework.web.bind.annotation.ExceptionHandler
7 import org.springframework.web.bind.annotation.RestControllerAdvice 8 import org.springframework.web.bind.annotation.RestControllerAdvice
  9 +import org.springframework.web.server.ResponseStatusException
8 import org.vibeerp.platform.security.AuthenticationFailedException 10 import org.vibeerp.platform.security.AuthenticationFailedException
9 import org.vibeerp.platform.security.authz.PermissionDeniedException 11 import org.vibeerp.platform.security.authz.PermissionDeniedException
10 import java.time.Instant 12 import java.time.Instant
@@ -87,6 +89,23 @@ class GlobalExceptionHandler { @@ -87,6 +89,23 @@ class GlobalExceptionHandler {
87 problem(HttpStatus.FORBIDDEN, "permission denied: '${ex.permissionKey}'") 89 problem(HttpStatus.FORBIDDEN, "permission denied: '${ex.permissionKey}'")
88 90
89 /** 91 /**
  92 + * Spring's `ResponseStatusException` carries its own HTTP status code
  93 + * (e.g. 400, 403, 409). Handlers and filters that throw it expect the
  94 + * status to propagate verbatim. Without this handler the catch-all
  95 + * `Throwable` branch would swallow it as a generic 500.
  96 + */
  97 + @ExceptionHandler(ResponseStatusException::class)
  98 + fun handleResponseStatus(ex: ResponseStatusException): ResponseEntity<Map<String, Any?>> {
  99 + return ResponseEntity.status(ex.getStatusCode()).body(mapOf(
  100 + "type" to "about:blank",
  101 + "title" to ex.getStatusCode().toString(),
  102 + "status" to ex.getStatusCode().value(),
  103 + "detail" to (ex.reason ?: ex.message),
  104 + "timestamp" to Instant.now().toString(),
  105 + ))
  106 + }
  107 +
  108 + /**
90 * Last-resort fallback. Anything not handled above is logged with a 109 * Last-resort fallback. Anything not handled above is logged with a
91 * full stack trace and surfaced as a generic 500 to the caller. The 110 * full stack trace and surfaced as a generic 500 to the caller. The
92 * detail message is intentionally vague so we don't leak internals. 111 * detail message is intentionally vague so we don't leak internals.