From 24a1767af313a07345f00374f87f189bfd582a56 Mon Sep 17 00:00:00 2001 From: jackylee-ch Date: Mon, 7 Sep 2026 19:13:59 +0800 Subject: [PATCH] [api] Fall back to the HTTP status when the error body omits code The creator parameter for "code" was primitive, so Jackson filled 0 for an absent value and getCode() was never null. That made the HTTP-status fallback in HttpClient unreachable and turned a 404 without a body code into a generic RESTException reading "(HTTP 0)". --- .../apache/paimon/rest/DefaultErrorHandler.java | 5 ++++- .../paimon/rest/responses/ErrorResponse.java | 3 ++- .../org/apache/paimon/rest/HttpClientTest.java | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/paimon-api/src/main/java/org/apache/paimon/rest/DefaultErrorHandler.java b/paimon-api/src/main/java/org/apache/paimon/rest/DefaultErrorHandler.java index 67ce6ced18f0..c1fd9297362c 100644 --- a/paimon-api/src/main/java/org/apache/paimon/rest/DefaultErrorHandler.java +++ b/paimon-api/src/main/java/org/apache/paimon/rest/DefaultErrorHandler.java @@ -42,7 +42,10 @@ public static ErrorHandler getInstance() { @Override public void accept(ErrorResponse error, String requestId) { - int code = error.getCode(); + Integer errorCode = error.getCode(); + // HttpClient always resolves the code before calling this, but the response may also be + // deserialized directly, and then "code" is absent whenever the server omits it. + int code = errorCode == null ? 0 : errorCode; String message; if (DEFAULT_REQUEST_ID.equals(requestId)) { message = error.getMessage(); diff --git a/paimon-api/src/main/java/org/apache/paimon/rest/responses/ErrorResponse.java b/paimon-api/src/main/java/org/apache/paimon/rest/responses/ErrorResponse.java index 4fe52b1d0b22..0335eb4f9bc8 100644 --- a/paimon-api/src/main/java/org/apache/paimon/rest/responses/ErrorResponse.java +++ b/paimon-api/src/main/java/org/apache/paimon/rest/responses/ErrorResponse.java @@ -71,6 +71,7 @@ public class ErrorResponse implements RESTResponse { @JsonProperty(FIELD_MESSAGE) private final String message; + @Nullable @JsonProperty(FIELD_CODE) private final Integer code; @@ -79,7 +80,7 @@ public ErrorResponse( @Nullable @JsonProperty(FIELD_RESOURCE_TYPE) String resourceType, @Nullable @JsonProperty(FIELD_RESOURCE_NAME) String resourceName, @JsonProperty(FIELD_MESSAGE) String message, - @JsonProperty(FIELD_CODE) int code) { + @Nullable @JsonProperty(FIELD_CODE) Integer code) { this.resourceType = resourceType; this.resourceName = resourceName; this.message = message; diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java index 5bdc553fdf6a..da8d45e50d44 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java @@ -23,6 +23,7 @@ import org.apache.paimon.rest.auth.RESTAuthFunction; import org.apache.paimon.rest.auth.RESTAuthParameter; import org.apache.paimon.rest.exceptions.BadRequestException; +import org.apache.paimon.rest.exceptions.NoSuchResourceException; import org.apache.paimon.rest.exceptions.RESTException; import org.apache.paimon.rest.responses.ErrorResponse; @@ -45,6 +46,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; /** Test for {@link HttpClient}. */ @@ -255,6 +257,17 @@ public void testUrl() { assertEquals(restAuthParameter.parameters().get(queryKey), queryParameters.get(queryKey)); } + @Test + public void testErrorCodeFallsBackToHttpStatus() throws Exception { + // "code" is optional in the error schema, so an error body may omit it. The HTTP status + // has to be used then, otherwise a 404 no longer maps to NoSuchResourceException. + assertNull(RESTApi.fromJson("{\"message\":\"x\"}", ErrorResponse.class).getCode()); + server.enqueueResponse("{\"message\":\"Table t does not exist\"}", 404); + assertThrows( + NoSuchResourceException.class, + () -> httpClient.get(MOCK_PATH, MockRESTData.class, restAuthFunction)); + } + private Map getParameters(String path) { String[] paths = path.split("\\?"); if (paths.length == 1) { @@ -293,6 +306,10 @@ public void testGetWithUnparsableJsonErrorResponse() { Assertions.assertTrue( e.getMessage().contains("Empty error message"), "Parsed-but-empty message must not be labelled unparseable"); + Assertions.assertTrue( + e.getMessage().contains("403"), + "The HTTP status must be reported, not the absent body code: " + + e.getMessage()); } }