This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 78739716c1 [api] Stop reading a server error message as a format
string (#9454)
78739716c1 is described below
commit 78739716c1d5b93cf543297dd6f0c9470df8dbdb
Author: Jiajia Li <[email protected]>
AuthorDate: Sat Aug 29 20:54:50 2026 +0800
[api] Stop reading a server error message as a format string (#9454)
---
.../apache/paimon/rest/DefaultErrorHandler.java | 4 +-
.../main/java/org/apache/paimon/rest/RESTApi.java | 3 +-
.../rest/exceptions/BadRequestException.java | 4 ++
.../rest/exceptions/NotAuthorizedException.java | 2 +-
.../rest/exceptions/NotImplementedException.java | 2 +-
.../rest/exceptions/ServiceFailureException.java | 2 +-
.../exceptions/ServiceUnavailableException.java | 2 +-
.../paimon/rest/RESTApiFunctionNameTest.java | 51 ++++++++++++++++++++++
.../java/org/apache/paimon/rest/RESTCatalog.java | 2 +-
.../paimon/rest/DefaultErrorHandlerTest.java | 20 +++++++++
.../apache/paimon/rest/MockRESTCatalogTest.java | 14 ++++++
11 files changed, 98 insertions(+), 8 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 c9f4ae0df8..67ce6ced18 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
@@ -52,7 +52,7 @@ public class DefaultErrorHandler extends ErrorHandler {
}
switch (code) {
case 400:
- throw new BadRequestException(String.format("%s", message));
+ throw new BadRequestException("%s", message);
case 401:
throw new NotAuthorizedException("Not authorized: %s",
message);
case 403:
@@ -69,7 +69,7 @@ public class DefaultErrorHandler extends ErrorHandler {
case 500:
throw new ServiceFailureException("Server error: %s", message);
case 501:
- throw new NotImplementedException(message);
+ throw new NotImplementedException("%s", message);
case 503:
throw new ServiceUnavailableException("Service unavailable:
%s", message);
default:
diff --git a/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
b/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
index b4b110cfec..9fb56cca26 100644
--- a/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
+++ b/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
@@ -1487,7 +1487,8 @@ public class RESTApi {
throw new NoSuchResourceException(
ErrorResponse.RESOURCE_TYPE_FUNCTION,
identifier.getObjectName(),
- "Invalid function name: " + identifier.getObjectName());
+ "Invalid function name: %s",
+ identifier.getObjectName());
}
return client.get(
resourcePaths.function(identifier.getDatabaseName(),
identifier.getObjectName()),
diff --git
a/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/BadRequestException.java
b/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/BadRequestException.java
index 301f3bd63f..9095d01651 100644
---
a/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/BadRequestException.java
+++
b/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/BadRequestException.java
@@ -24,4 +24,8 @@ public class BadRequestException extends RESTException {
public BadRequestException(String message, Object... args) {
super(message, args);
}
+
+ public BadRequestException(Throwable cause, String message, Object...
args) {
+ super(cause, message, args);
+ }
}
diff --git
a/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/NotAuthorizedException.java
b/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/NotAuthorizedException.java
index 79c9aa4e67..cd4f0a27da 100644
---
a/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/NotAuthorizedException.java
+++
b/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/NotAuthorizedException.java
@@ -22,6 +22,6 @@ package org.apache.paimon.rest.exceptions;
public class NotAuthorizedException extends RESTException {
public NotAuthorizedException(String message, Object... args) {
- super(String.format(message, args));
+ super(message, args);
}
}
diff --git
a/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/NotImplementedException.java
b/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/NotImplementedException.java
index a6a3454343..a70fa7828a 100644
---
a/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/NotImplementedException.java
+++
b/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/NotImplementedException.java
@@ -22,6 +22,6 @@ package org.apache.paimon.rest.exceptions;
public class NotImplementedException extends RESTException {
public NotImplementedException(String message, Object... args) {
- super(String.format(message, args));
+ super(message, args);
}
}
diff --git
a/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/ServiceFailureException.java
b/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/ServiceFailureException.java
index 1df196d90f..4e6ecc3a25 100644
---
a/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/ServiceFailureException.java
+++
b/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/ServiceFailureException.java
@@ -22,6 +22,6 @@ package org.apache.paimon.rest.exceptions;
public class ServiceFailureException extends RESTException {
public ServiceFailureException(String message, Object... args) {
- super(String.format(message, args));
+ super(message, args);
}
}
diff --git
a/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/ServiceUnavailableException.java
b/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/ServiceUnavailableException.java
index c466b4c901..f9e300d031 100644
---
a/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/ServiceUnavailableException.java
+++
b/paimon-api/src/main/java/org/apache/paimon/rest/exceptions/ServiceUnavailableException.java
@@ -22,6 +22,6 @@ package org.apache.paimon.rest.exceptions;
public class ServiceUnavailableException extends RESTException {
public ServiceUnavailableException(String message, Object... args) {
- super(String.format(message, args));
+ super(message, args);
}
}
diff --git
a/paimon-api/src/test/java/org/apache/paimon/rest/RESTApiFunctionNameTest.java
b/paimon-api/src/test/java/org/apache/paimon/rest/RESTApiFunctionNameTest.java
new file mode 100644
index 0000000000..0580ae1273
--- /dev/null
+++
b/paimon-api/src/test/java/org/apache/paimon/rest/RESTApiFunctionNameTest.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.rest;
+
+import org.apache.paimon.catalog.Identifier;
+import org.apache.paimon.options.Options;
+import org.apache.paimon.rest.exceptions.NoSuchResourceException;
+
+import org.junit.jupiter.api.Test;
+
+import static org.apache.paimon.rest.RESTCatalogInternalOptions.PREFIX;
+import static org.apache.paimon.rest.RESTCatalogOptions.TOKEN;
+import static org.apache.paimon.rest.RESTCatalogOptions.TOKEN_PROVIDER;
+import static org.apache.paimon.rest.RESTCatalogOptions.URI;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** Tests for the name check {@link RESTApi} makes before it sends anything. */
+public class RESTApiFunctionNameTest {
+
+ @Test
+ public void testARejectedFunctionNameIsReportedAndNotFormatted() {
+ Options options = new Options();
+ options.set(URI, "http://127.0.0.1:1");
+ options.set(TOKEN_PROVIDER, "bear");
+ options.set(TOKEN, "secret");
+ options.set(PREFIX, "catalog");
+ RESTApi api = new RESTApi(options, false);
+
+ // the name is why the request is refused, so it belongs in the
message as data -- the
+ // validator rejects '%', which is also what java.util.Formatter reads
as a conversion
+ assertThatThrownBy(() -> api.getFunction(Identifier.create("db",
"function%")))
+ .isInstanceOf(NoSuchResourceException.class)
+ .hasMessageContaining("function%");
+ }
+}
diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java
b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java
index 9a21f3fa41..50fe373d6a 100644
--- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java
+++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java
@@ -322,7 +322,7 @@ public class RESTCatalog implements Catalog {
return SystemTableLoader.loadGlobalTableNamesPaged(
context.options(), maxResults, pageToken,
tableNamePattern, tableType);
} catch (IllegalArgumentException e) {
- throw new BadRequestException(e.getMessage());
+ throw new BadRequestException(e, "%s", e.getMessage());
}
}
diff --git
a/paimon-core/src/test/java/org/apache/paimon/rest/DefaultErrorHandlerTest.java
b/paimon-core/src/test/java/org/apache/paimon/rest/DefaultErrorHandlerTest.java
index c46c596287..9f8c690466 100644
---
a/paimon-core/src/test/java/org/apache/paimon/rest/DefaultErrorHandlerTest.java
+++
b/paimon-core/src/test/java/org/apache/paimon/rest/DefaultErrorHandlerTest.java
@@ -36,6 +36,7 @@ import java.io.IOException;
import static
org.apache.paimon.rest.interceptor.LoggingInterceptor.DEFAULT_REQUEST_ID;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/** Test for {@link DefaultErrorHandler}. */
public class DefaultErrorHandlerTest {
@@ -83,6 +84,25 @@ public class DefaultErrorHandlerTest {
() -> defaultErrorHandler.accept(generateErrorResponse(503),
DEFAULT_REQUEST_ID));
}
+ @Test
+ public void testErrorMessageIsNotReadAsAFormatString() {
+ // a server message is data, and this one holds what
java.util.Formatter reads as syntax
+ String message = "quota 80% exceeded";
+ for (int code : new int[] {400, 401, 403, 404, 405, 406, 409, 500,
501, 502, 503}) {
+ RESTException exception =
+ assertThrows(
+ RESTException.class,
+ () ->
+ defaultErrorHandler.accept(
+ new ErrorResponse("table", "t",
message, code),
+ DEFAULT_REQUEST_ID),
+ "status " + code);
+ assertTrue(
+ exception.getMessage().contains(message),
+ "status " + code + " lost the message: " +
exception.getMessage());
+ }
+ }
+
private ErrorResponse generateErrorResponse(int code) {
return new ErrorResponse(null, null, "message", code);
}
diff --git
a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
index bd7fedce3a..7c55035345 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java
@@ -85,6 +85,7 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
+import static org.apache.paimon.catalog.Catalog.SYSTEM_DATABASE_NAME;
import static org.apache.paimon.catalog.Catalog.TABLE_DEFAULT_OPTION_PREFIX;
import static org.apache.paimon.rest.RESTApi.HEADER_PREFIX;
import static org.apache.paimon.rest.RESTApi.READ_VIA_HEADER;
@@ -142,6 +143,19 @@ class MockRESTCatalogTest extends RESTCatalogTest {
.isInstanceOf(NotAuthorizedException.class);
}
+ @Test
+ void testRejectedSystemTablePatternKeepsWhatRaisedIt() {
+ // the message is the argument, never the format, and the cause is
what says where it
+ // came from -- the caller sees only what this exception carries
+ assertThatThrownBy(
+ () ->
+ catalog.listTablesPaged(
+ SYSTEM_DATABASE_NAME, null, null,
"a%b", null))
+ .isInstanceOf(BadRequestException.class)
+ .hasMessageContaining("prefix sql like pattern")
+ .hasCauseInstanceOf(IllegalArgumentException.class);
+ }
+
@Test
void testInvalidManagementDtoReturnsBadRequest() throws Exception {
String database = "invalid_management_dto";