This is an automated email from the ASF dual-hosted git repository.
jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.3 by this push:
new f2e5906a4b [Cherry-pick to branch-1.3] [#12879] fix(server): return
accurate HTTP statuses for unsupported operations (#12880) (#12924)
f2e5906a4b is described below
commit f2e5906a4b105f7b146535a1dc5ca2418c375cf4
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Sep 4 19:17:18 2026 +0800
[Cherry-pick to branch-1.3] [#12879] fix(server): return accurate HTTP
statuses for unsupported operations (#12880) (#12924)
**Cherry-pick Information:**
- Original commit: 8e41cedffb42064b240f93312e98610ee1788628
- Target branch: `branch-1.3`
- Status: ⚠️ **Has conflicts - manual resolution required**
**Do not merge** until conflict markers are resolved and the
`cherry-pick-conflict` label is removed.
Please review and resolve the conflicts before merging.
---------
Co-authored-by: Qi Yu <[email protected]>
---
.../gravitino/client/TestRelationalCatalog.java | 4 +-
.../client/TestSupportsPartitionStatistics.java | 11 +--
.../gravitino/client/TestSupportsStatistics.java | 11 +--
docs/open-api/idp/idp.yaml | 8 +-
docs/open-api/statistics.yaml | 62 +++++++++++---
.../idp/storage/service/IdpGroupMetaService.java | 6 +-
.../org/apache/gravitino/idp/web/IdpRESTUtils.java | 10 ++-
.../gravitino/idp/TestIdpUserGroupManager.java | 3 +-
.../idp/integration/test/IdpRESTApiIT.java | 2 +-
.../storage/service/TestIdpGroupMetaService.java | 3 +-
.../apache/gravitino/idp/web/TestIdpRESTUtils.java | 70 ++++++++++++++++
.../org/apache/gravitino/server/web/Utils.java | 48 ++++++++++-
.../org/apache/gravitino/server/web/TestUtils.java | 29 ++++++-
.../web/filter/AccessControlNotAllowedFilter.java | 2 +-
.../server/web/rest/ExceptionHandlers.java | 19 +++++
.../server/web/rest/StatisticOperations.java | 12 +--
.../filter/TestAccessControlNotAllowedFilter.java | 9 +-
.../server/web/rest/TestExceptionHandlers.java | 54 ++++++++++++
.../server/web/rest/TestStatisticOperations.java | 95 +++++++++++++++++++++-
web-v2/web/src/lib/store/metalakes/index.js | 9 +-
.../src/lib/utils/axios/unsupportedOperation.js | 22 ++---
.../lib/utils/axios/unsupportedOperation.test.js | 43 ++++++++++
22 files changed, 455 insertions(+), 77 deletions(-)
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestRelationalCatalog.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestRelationalCatalog.java
index b10e9f981e..64feaa121c 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestRelationalCatalog.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestRelationalCatalog.java
@@ -25,8 +25,8 @@ import static
org.apache.gravitino.rel.expressions.sorts.SortDirection.DESCENDIN
import static org.apache.hc.core5.http.HttpStatus.SC_BAD_REQUEST;
import static org.apache.hc.core5.http.HttpStatus.SC_CONFLICT;
import static org.apache.hc.core5.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
-import static org.apache.hc.core5.http.HttpStatus.SC_METHOD_NOT_ALLOWED;
import static org.apache.hc.core5.http.HttpStatus.SC_NOT_FOUND;
+import static org.apache.hc.core5.http.HttpStatus.SC_NOT_IMPLEMENTED;
import static org.apache.hc.core5.http.HttpStatus.SC_OK;
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -1186,7 +1186,7 @@ public class TestRelationalCatalog extends TestBase {
// Test with exception
ErrorResponse errorResp = ErrorResponse.unsupportedOperation("Unsupported
operation");
- buildMockResource(Method.DELETE, tablePath, null, errorResp,
SC_METHOD_NOT_ALLOWED);
+ buildMockResource(Method.DELETE, tablePath, null, errorResp,
SC_NOT_IMPLEMENTED);
TableCatalog tableCatalog = catalog.asTableCatalog();
Assertions.assertThrows(
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsPartitionStatistics.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsPartitionStatistics.java
index d19aaed1a6..fca28cae0d 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsPartitionStatistics.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsPartitionStatistics.java
@@ -19,8 +19,8 @@
package org.apache.gravitino.client;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
+import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
-import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -209,12 +209,7 @@ public class TestSupportsPartitionStatistics extends
TestBase {
Collections.emptyList()));
ErrorResponse unmodifiableError = MAPPER.readValue(unmodifiableErrorJson,
ErrorResponse.class);
buildMockResource(
- Method.PUT,
- path,
- Collections.emptyMap(),
- expectedRequest,
- unmodifiableError,
- SC_METHOD_NOT_ALLOWED);
+ Method.PUT, path, Collections.emptyMap(), expectedRequest,
unmodifiableError, SC_CONFLICT);
Assertions.assertThrows(
UnmodifiableStatisticException.class,
@@ -274,7 +269,7 @@ public class TestSupportsPartitionStatistics extends
TestBase {
Collections.emptyList()));
ErrorResponse unmodifiableError = MAPPER.readValue(unmodifiableErrorJson,
ErrorResponse.class);
buildMockResource(
- Method.POST, path, Collections.emptyMap(), null, unmodifiableError,
SC_METHOD_NOT_ALLOWED);
+ Method.POST, path, Collections.emptyMap(), null, unmodifiableError,
SC_CONFLICT);
Assertions.assertThrows(
UnmodifiableStatisticException.class,
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsStatistics.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsStatistics.java
index ab1c1c1bee..c588deeb2f 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsStatistics.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportsStatistics.java
@@ -19,8 +19,8 @@
package org.apache.gravitino.client;
import static org.apache.hc.core5.http.HttpStatus.SC_BAD_REQUEST;
+import static org.apache.hc.core5.http.HttpStatus.SC_CONFLICT;
import static org.apache.hc.core5.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
-import static org.apache.hc.core5.http.HttpStatus.SC_METHOD_NOT_ALLOWED;
import static org.apache.hc.core5.http.HttpStatus.SC_OK;
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -184,12 +184,7 @@ public class TestSupportsStatistics extends TestBase {
Collections.emptyList()));
ErrorResponse unmodifiableError = MAPPER.readValue(unmodifiableErrorJson,
ErrorResponse.class);
buildMockResource(
- Method.PUT,
- path,
- Collections.emptyMap(),
- expectedRequest,
- unmodifiableError,
- SC_METHOD_NOT_ALLOWED);
+ Method.PUT, path, Collections.emptyMap(), expectedRequest,
unmodifiableError, SC_CONFLICT);
Assertions.assertThrows(
UnmodifiableStatisticException.class,
@@ -246,7 +241,7 @@ public class TestSupportsStatistics extends TestBase {
Collections.emptyList()));
ErrorResponse unmodifiableError = MAPPER.readValue(unmodifiableErrorJson,
ErrorResponse.class);
buildMockResource(
- Method.POST, path, Collections.emptyMap(), null, unmodifiableError,
SC_METHOD_NOT_ALLOWED);
+ Method.POST, path, Collections.emptyMap(), null, unmodifiableError,
SC_CONFLICT);
Assertions.assertThrows(
UnmodifiableStatisticException.class,
diff --git a/docs/open-api/idp/idp.yaml b/docs/open-api/idp/idp.yaml
index 2437519f9d..a3c793f414 100644
--- a/docs/open-api/idp/idp.yaml
+++ b/docs/open-api/idp/idp.yaml
@@ -264,8 +264,8 @@ paths:
$ref: "../openapi.yaml#/components/responses/RemoveResponse"
"403":
$ref: "#/components/responses/IdpForbiddenErrorResponse"
- "405":
- description: Method Not Allowed - The group is not empty and force
is false
+ "409":
+ description: Conflict - The group is not empty and force is false
content:
application/vnd.gravitino.v1+json:
schema:
@@ -627,8 +627,8 @@ components:
IdpGroupNotEmptyException:
value: {
- "code": 1006,
- "type": "IllegalStateException",
+ "code": 1005,
+ "type": "NonEmptyEntityException",
"message": "Failed to operate local user store group [engineers]
operation [REMOVE], reason [IdP group engineers is not empty, use force=true to
delete it]"
}
diff --git a/docs/open-api/statistics.yaml b/docs/open-api/statistics.yaml
index 91e422be45..826a4b7ec1 100644
--- a/docs/open-api/statistics.yaml
+++ b/docs/open-api/statistics.yaml
@@ -41,6 +41,8 @@ paths:
examples:
StatisticListResponse:
$ref: "#/components/examples/StatisticListResponse"
+ "400":
+ $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
"404":
description: Not Found - The specified metadata object doesn't exist
content:
@@ -50,6 +52,12 @@ paths:
examples:
NoSuchMetadataObjectException:
$ref: "#/components/examples/NoSuchMetadataObjectException"
+ "501":
+ description: Not Implemented - Statistics are not supported for the
specified object
+ content:
+ application/vnd.gravitino.v1+json:
+ schema:
+ $ref: "./openapi.yaml#/components/schemas/ErrorModel"
"5xx":
$ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
@@ -71,7 +79,7 @@ paths:
"200":
$ref: "./openapi.yaml#/components/responses/DropResponse"
"400":
- description: Bad Request - The request contains illegal statistic
names
+ description: Bad Request - The object type or statistic names are
invalid
content:
application/vnd.gravitino.v1+json:
schema:
@@ -88,8 +96,8 @@ paths:
examples:
NoSuchMetadataObjectException:
$ref: "#/components/examples/NoSuchMetadataObjectException"
- "405":
- description: Method Not Allowed - The specified statistic is
unmodifiable
+ "409":
+ description: Conflict - The specified statistic is unmodifiable
content:
application/vnd.gravitino.v1+json:
schema:
@@ -97,6 +105,12 @@ paths:
examples:
UnmodifiableStatisticException:
$ref: "#/components/examples/UnmodifiableStatisticException"
+ "501":
+ description: Not Implemented - The requested statistics operation is
not supported
+ content:
+ application/vnd.gravitino.v1+json:
+ schema:
+ $ref: "./openapi.yaml#/components/schemas/ErrorModel"
"5xx":
$ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
@@ -118,7 +132,7 @@ paths:
"200":
$ref: "./openapi.yaml#/components/responses/BaseResponse"
"400":
- description: Bad Request - The request contains illegal statistic
names
+ description: Bad Request - The object type or statistic names are
invalid
content:
application/vnd.gravitino.v1+json:
schema:
@@ -135,8 +149,8 @@ paths:
examples:
NoSuchMetadataObjectException:
$ref: "#/components/examples/NoSuchMetadataObjectException"
- "405":
- description: Method Not Allowed - The specified statistic is
unmodifiable
+ "409":
+ description: Conflict - The specified statistic is unmodifiable
content:
application/vnd.gravitino.v1+json:
schema:
@@ -144,6 +158,12 @@ paths:
examples:
UnmodifiableStatisticException:
$ref: "#/components/examples/UnmodifiableStatisticException"
+ "501":
+ description: Not Implemented - The requested statistics operation is
not supported
+ content:
+ application/vnd.gravitino.v1+json:
+ schema:
+ $ref: "./openapi.yaml#/components/schemas/ErrorModel"
"5xx":
$ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
@@ -184,6 +204,12 @@ paths:
examples:
NoSuchMetadataObjectException:
$ref: "#/components/examples/NoSuchMetadataObjectException"
+ "501":
+ description: Not Implemented - Partition statistics are not
supported for the specified object
+ content:
+ application/vnd.gravitino.v1+json:
+ schema:
+ $ref: "./openapi.yaml#/components/schemas/ErrorModel"
"5xx":
$ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
@@ -205,7 +231,7 @@ paths:
"200":
$ref: "./openapi.yaml#/components/responses/DropResponse"
"400":
- description: Bad Request - The request contains illegal statistic
names
+ description: Bad Request - The object type or statistic names are
invalid
content:
application/vnd.gravitino.v1+json:
schema:
@@ -222,8 +248,8 @@ paths:
examples:
NoSuchMetadataObjectException:
$ref: "#/components/examples/NoSuchMetadataObjectException"
- "405":
- description: Method Not Allowed - The specified statistic is
unmodifiable
+ "409":
+ description: Conflict - The specified statistic is unmodifiable
content:
application/vnd.gravitino.v1+json:
schema:
@@ -231,6 +257,12 @@ paths:
examples:
UnmodifiableStatisticException:
$ref: "#/components/examples/UnmodifiableStatisticException"
+ "501":
+ description: Not Implemented - The requested partition statistics
operation is not supported
+ content:
+ application/vnd.gravitino.v1+json:
+ schema:
+ $ref: "./openapi.yaml#/components/schemas/ErrorModel"
"5xx":
$ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
@@ -252,7 +284,7 @@ paths:
"200":
$ref: "./openapi.yaml#/components/responses/BaseResponse"
"400":
- description: Bad Request - The request contains illegal statistic
names
+ description: Bad Request - The object type or statistic names are
invalid
content:
application/vnd.gravitino.v1+json:
schema:
@@ -269,8 +301,8 @@ paths:
examples:
NoSuchMetadataObjectException:
$ref: "#/components/examples/NoSuchMetadataObjectException"
- "405":
- description: Method Not Allowed - The specified statistic is
unmodifiable
+ "409":
+ description: Conflict - The specified statistic is unmodifiable
content:
application/vnd.gravitino.v1+json:
schema:
@@ -278,6 +310,12 @@ paths:
examples:
UnmodifiableStatisticException:
$ref: "#/components/examples/UnmodifiableStatisticException"
+ "501":
+ description: Not Implemented - The requested partition statistics
operation is not supported
+ content:
+ application/vnd.gravitino.v1+json:
+ schema:
+ $ref: "./openapi.yaml#/components/schemas/ErrorModel"
"5xx":
$ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
index c4bfdd3601..018b20fe1a 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.apache.gravitino.exceptions.NonEmptyEntityException;
import org.apache.gravitino.exceptions.NotFoundException;
import org.apache.gravitino.idp.model.IdpGroup;
import org.apache.gravitino.idp.storage.mapper.IdpGroupMetaMapper;
@@ -104,14 +105,15 @@ public class IdpGroupMetaService {
* @param force when false, rejects deletion if the group still has members;
when true, removes
* memberships and deletes the group
* @return true if the group was deleted
+ * @throws NonEmptyEntityException if the group has members and force is
false
*/
@Monitored(
metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
baseMetricName = "deleteIdpGroup")
public boolean deleteIdpGroup(String groupName, boolean force) {
if (!force && !listUsernamesByGroupName(groupName).isEmpty()) {
- throw new IllegalStateException(
- String.format("IdP group %s is not empty, use force=true to delete
it", groupName));
+ throw new NonEmptyEntityException(
+ "IdP group %s is not empty, use force=true to delete it", groupName);
}
int[] deletedCount = new int[] {0};
diff --git
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/IdpRESTUtils.java
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/IdpRESTUtils.java
index 75ab056738..95aaa1b9fd 100644
---
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/IdpRESTUtils.java
+++
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/IdpRESTUtils.java
@@ -27,6 +27,7 @@ import org.apache.gravitino.UserPrincipal;
import org.apache.gravitino.auth.AuthConstants;
import org.apache.gravitino.dto.responses.ErrorResponse;
import org.apache.gravitino.exceptions.AlreadyExistsException;
+import org.apache.gravitino.exceptions.NonEmptyEntityException;
import org.apache.gravitino.exceptions.NotFoundException;
import org.apache.gravitino.utils.PrincipalUtils;
import org.slf4j.Logger;
@@ -103,7 +104,7 @@ public final class IdpRESTUtils {
public static Response unsupportedOperation(String message, Throwable
throwable) {
return json(
- Response.Status.METHOD_NOT_ALLOWED,
ErrorResponse.unsupportedOperation(message, throwable));
+ Response.Status.NOT_IMPLEMENTED,
ErrorResponse.unsupportedOperation(message, throwable));
}
public static Response internalError(String message, Throwable throwable) {
@@ -121,7 +122,12 @@ public final class IdpRESTUtils {
if (e instanceof AlreadyExistsException) {
return alreadyExists(errorMsg, e);
}
- if (e instanceof IllegalStateException || e instanceof
UnsupportedOperationException) {
+ if (e instanceof NonEmptyEntityException) {
+ return json(
+ Response.Status.CONFLICT,
+ ErrorResponse.nonEmpty(e.getClass().getSimpleName(), errorMsg, e));
+ }
+ if (e instanceof UnsupportedOperationException) {
return unsupportedOperation(errorMsg, e);
}
return internalError(errorMsg, e);
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/TestIdpUserGroupManager.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/TestIdpUserGroupManager.java
index 419b688dcf..2ce53469d0 100644
---
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/TestIdpUserGroupManager.java
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/TestIdpUserGroupManager.java
@@ -40,6 +40,7 @@ import java.util.stream.Stream;
import org.apache.gravitino.Config;
import org.apache.gravitino.auth.AuthenticatorType;
import org.apache.gravitino.exceptions.AlreadyExistsException;
+import org.apache.gravitino.exceptions.NonEmptyEntityException;
import org.apache.gravitino.exceptions.NotFoundException;
import org.apache.gravitino.idp.basic.IdpCredentialValidator;
import org.apache.gravitino.idp.model.IdpGroup;
@@ -193,7 +194,7 @@ public class TestIdpUserGroupManager {
manager.changeGroupMembership("testRemoveGroup",
Lists.newArrayList("groupMember"), null);
Assertions.assertThrows(
- IllegalStateException.class, () ->
manager.removeGroup("testRemoveGroup", false));
+ NonEmptyEntityException.class, () ->
manager.removeGroup("testRemoveGroup", false));
manager.changeGroupMembership("testRemoveGroup", null,
Lists.newArrayList("groupMember"));
Assertions.assertTrue(manager.removeGroup("testRemoveGroup", false));
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
index ca18bfcf6c..aaa3d91d5a 100644
---
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
@@ -208,7 +208,7 @@ public class IdpRESTApiIT extends BaseIT {
putMembership(GROUP1, new GroupMembershipChangeRequest(new String[]
{USER1, USER2}, null));
Assertions.assertEquals(Set.of(USER1, USER2),
Set.copyOf(group.getGroup().users()));
- assertError(405, deleteGroupResponse(GROUP1, false),
ErrorConstants.UNSUPPORTED_OPERATION_CODE);
+ assertError(409, deleteGroupResponse(GROUP1, false),
ErrorConstants.NON_EMPTY_CODE);
group =
putMembership(GROUP1, new GroupMembershipChangeRequest(null, new
String[] {USER1, USER2}));
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpGroupMetaService.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpGroupMetaService.java
index b0b22e7f21..70067d66ac 100644
---
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpGroupMetaService.java
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/storage/service/TestIdpGroupMetaService.java
@@ -28,6 +28,7 @@ import java.io.IOException;
import java.util.List;
import java.util.Set;
import org.apache.gravitino.exceptions.AlreadyExistsException;
+import org.apache.gravitino.exceptions.NonEmptyEntityException;
import org.apache.gravitino.exceptions.NotFoundException;
import org.apache.gravitino.idp.storage.po.IdpGroupPO;
import org.junit.jupiter.api.Tag;
@@ -121,7 +122,7 @@ class TestIdpGroupMetaService extends
AbstractIdpMetaServiceTest {
IdpGroupMetaService groupMetaService = IdpGroupMetaService.getInstance();
assertThrows(
- IllegalStateException.class, () ->
groupMetaService.deleteIdpGroup("group1", false));
+ NonEmptyEntityException.class, () ->
groupMetaService.deleteIdpGroup("group1", false));
runServiceCall(() -> assertTrue(groupMetaService.deleteIdpGroup("group1",
true)));
assertNull(idpGroupMetaMapper.selectIdpGroup("group1"));
diff --git
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/TestIdpRESTUtils.java
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/TestIdpRESTUtils.java
new file mode 100644
index 0000000000..3b8840fd14
--- /dev/null
+++
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/TestIdpRESTUtils.java
@@ -0,0 +1,70 @@
+/*
+ * 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.gravitino.idp.web;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.dto.responses.ErrorConstants;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.exceptions.NonEmptyEntityException;
+import org.junit.jupiter.api.Test;
+
+class TestIdpRESTUtils {
+
+ @Test
+ void testUnsupportedOperationReturnsNotImplemented() {
+ Response response =
+ IdpRESTUtils.handleException(
+ "group",
+ IdpOperationType.GET,
+ "group",
+ new UnsupportedOperationException("unsupported"));
+
+ assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(),
response.getStatus());
+ ErrorResponse error = (ErrorResponse) response.getEntity();
+ assertEquals(ErrorConstants.UNSUPPORTED_OPERATION_CODE, error.getCode());
+ }
+
+ @Test
+ void testNonEmptyEntityReturnsConflict() {
+ Response response =
+ IdpRESTUtils.handleException(
+ "group",
+ IdpOperationType.REMOVE,
+ "group",
+ new NonEmptyEntityException("Group is not empty"));
+
+ assertEquals(Response.Status.CONFLICT.getStatusCode(),
response.getStatus());
+ ErrorResponse error = (ErrorResponse) response.getEntity();
+ assertEquals(ErrorConstants.NON_EMPTY_CODE, error.getCode());
+ assertEquals(NonEmptyEntityException.class.getSimpleName(),
error.getType());
+ }
+
+ @Test
+ void testUnexpectedIllegalStateReturnsInternalError() {
+ Response response =
+ IdpRESTUtils.handleException(
+ "group", IdpOperationType.GET, "group", new
IllegalStateException("invalid state"));
+
+ assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
response.getStatus());
+ ErrorResponse error = (ErrorResponse) response.getEntity();
+ assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, error.getCode());
+ }
+}
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/web/Utils.java
b/server-common/src/main/java/org/apache/gravitino/server/web/Utils.java
index 4ef45b6a50..4942be851e 100644
--- a/server-common/src/main/java/org/apache/gravitino/server/web/Utils.java
+++ b/server-common/src/main/java/org/apache/gravitino/server/web/Utils.java
@@ -173,17 +173,63 @@ public class Utils {
.build();
}
+ /**
+ * Returns an HTTP 501 response for functionality that the server does not
implement.
+ *
+ * @param message the error message
+ * @return the HTTP response
+ */
public static Response unsupportedOperation(String message) {
return unsupportedOperation(message, null);
}
+ /**
+ * Returns an HTTP 501 response for functionality that the server does not
implement.
+ *
+ * @param message the error message
+ * @param throwable the exception that caused the error
+ * @return the HTTP response
+ */
public static Response unsupportedOperation(String message, Throwable
throwable) {
- return Response.status(Response.Status.METHOD_NOT_ALLOWED)
+ return Response.status(Response.Status.NOT_IMPLEMENTED)
+ .entity(ErrorResponse.unsupportedOperation(message, throwable))
+ .type(MediaType.APPLICATION_JSON)
+ .build();
+ }
+
+ /**
+ * Returns an HTTP 409 response when an operation conflicts with the target
object's state.
+ *
+ * <p>The unsupported-operation error payload is retained so existing
clients can reconstruct
+ * domain exceptions such as {@code UnmodifiableStatisticException}.
+ *
+ * @param message the error message
+ * @param throwable the exception that caused the error
+ * @return the HTTP response
+ */
+ public static Response operationConflict(String message, Throwable
throwable) {
+ return Response.status(Response.Status.CONFLICT)
.entity(ErrorResponse.unsupportedOperation(message, throwable))
.type(MediaType.APPLICATION_JSON)
.build();
}
+ /**
+ * Returns an HTTP 405 response when the target resource does not allow the
request method.
+ *
+ * <p>The unsupported-operation error payload is retained for compatibility
with clients that
+ * identify this response by its application error code.
+ *
+ * @param message the error message
+ * @return the HTTP response
+ */
+ public static Response methodNotAllowed(String message) {
+ return Response.status(Response.Status.METHOD_NOT_ALLOWED)
+ .entity(ErrorResponse.unsupportedOperation(message))
+ .type(MediaType.APPLICATION_JSON)
+ .build();
+ }
+
public static Response forbidden(String message, Throwable throwable) {
return Response.status(Response.Status.FORBIDDEN)
.entity(ErrorResponse.forbidden(message, throwable))
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/web/TestUtils.java
b/server-common/src/test/java/org/apache/gravitino/server/web/TestUtils.java
index c495a6275b..0e8d5023b3 100644
--- a/server-common/src/test/java/org/apache/gravitino/server/web/TestUtils.java
+++ b/server-common/src/test/java/org/apache/gravitino/server/web/TestUtils.java
@@ -30,7 +30,9 @@ import javax.ws.rs.core.Response;
import org.apache.gravitino.audit.FilesetAuditConstants;
import org.apache.gravitino.audit.FilesetDataOperation;
import org.apache.gravitino.audit.InternalClientType;
+import org.apache.gravitino.dto.responses.ErrorConstants;
import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.exceptions.UnmodifiableStatisticException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@@ -168,12 +170,37 @@ public class TestUtils {
public void testUnsupportedOperation() {
Response response = Utils.unsupportedOperation("Unsupported operation");
assertNotNull(response);
- assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(),
response.getStatus());
+ assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(),
response.getStatus());
assertEquals(MediaType.APPLICATION_JSON,
response.getMediaType().toString());
ErrorResponse errorResponse = (ErrorResponse) response.getEntity();
assertEquals("Unsupported operation", errorResponse.getMessage());
}
+ @Test
+ public void testOperationConflict() {
+ UnmodifiableStatisticException exception =
+ new UnmodifiableStatisticException("Unmodifiable statistic");
+ Response response = Utils.operationConflict(exception.getMessage(),
exception);
+
+ assertNotNull(response);
+ assertEquals(Response.Status.CONFLICT.getStatusCode(),
response.getStatus());
+ assertEquals(MediaType.APPLICATION_JSON,
response.getMediaType().toString());
+ ErrorResponse errorResponse = (ErrorResponse) response.getEntity();
+ assertEquals(ErrorConstants.UNSUPPORTED_OPERATION_CODE,
errorResponse.getCode());
+ assertEquals(UnmodifiableStatisticException.class.getSimpleName(),
errorResponse.getType());
+ }
+
+ @Test
+ public void testMethodNotAllowed() {
+ Response response = Utils.methodNotAllowed("Method not allowed");
+
+ assertNotNull(response);
+ assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(),
response.getStatus());
+ assertEquals(MediaType.APPLICATION_JSON,
response.getMediaType().toString());
+ ErrorResponse errorResponse = (ErrorResponse) response.getEntity();
+ assertEquals(ErrorConstants.UNSUPPORTED_OPERATION_CODE,
errorResponse.getCode());
+ }
+
@Test
public void testFilterFilesetAuditHeaders() {
// test invalid internal client type
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/filter/AccessControlNotAllowedFilter.java
b/server/src/main/java/org/apache/gravitino/server/web/filter/AccessControlNotAllowedFilter.java
index 42c001f766..9a4b6761e2 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/filter/AccessControlNotAllowedFilter.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/filter/AccessControlNotAllowedFilter.java
@@ -40,7 +40,7 @@ public class AccessControlNotAllowedFilter implements
ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws
IOException {
requestContext.abortWith(
- Utils.unsupportedOperation(
+ Utils.methodNotAllowed(
String.format(
"You should set '%s' to true in the server side
`gravitino.conf`"
+ " to enable the authorization of the system, otherwise
these interfaces can't work.",
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
index a4efa56771..a6e3e38a43 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/ExceptionHandlers.java
@@ -52,6 +52,7 @@ import
org.apache.gravitino.exceptions.TableAlreadyExistsException;
import org.apache.gravitino.exceptions.TagAlreadyAssociatedException;
import org.apache.gravitino.exceptions.TagAlreadyExistsException;
import org.apache.gravitino.exceptions.TopicAlreadyExistsException;
+import org.apache.gravitino.exceptions.UnmodifiableStatisticException;
import org.apache.gravitino.exceptions.UserAlreadyExistsException;
import org.apache.gravitino.exceptions.ViewAlreadyExistsException;
import org.apache.gravitino.server.web.Utils;
@@ -1054,6 +1055,9 @@ public class ExceptionHandlers {
} else if (e instanceof NotFoundException) {
return Utils.notFound(errorMsg, e);
+ } else if (e instanceof UnmodifiableStatisticException) {
+ return Utils.operationConflict(errorMsg, e);
+
} else if (e instanceof UnsupportedOperationException) {
return Utils.unsupportedOperation(errorMsg, e);
@@ -1086,6 +1090,9 @@ public class ExceptionHandlers {
} else if (e instanceof NotFoundException) {
return Utils.notFound(errorMsg, e);
+ } else if (e instanceof UnmodifiableStatisticException) {
+ return Utils.operationConflict(errorMsg, e);
+
} else if (e instanceof UnsupportedOperationException) {
return Utils.unsupportedOperation(errorMsg, e);
@@ -1114,6 +1121,18 @@ public class ExceptionHandlers {
String errorMsg =
getBaseErrorMsg(formattedObject, op.name(), formattedParent,
getErrorMsg(e));
+ // Classify domain-specific UnsupportedOperationException subclasses
before the generic
+ // capability fallback below.
+ if (e instanceof UnmodifiableStatisticException) {
+ LOG.warn(errorMsg, e);
+ return Utils.operationConflict(errorMsg, e);
+ }
+
+ if (e instanceof UnsupportedOperationException) {
+ LOG.warn(errorMsg, e);
+ return Utils.unsupportedOperation(errorMsg, e);
+ }
+
LOG.error(errorMsg, e);
return Utils.internalError(errorMsg, e);
}
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/StatisticOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/StatisticOperations.java
index f94af2f44d..d698185fdd 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/StatisticOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/StatisticOperations.java
@@ -119,7 +119,7 @@ public class StatisticOperations {
MetadataObjects.parse(
fullName,
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
if (object.type() != MetadataObject.Type.TABLE) {
- throw new UnsupportedOperationException(
+ throw new IllegalArgumentException(
"Listing statistics is only supported for tables now.");
}
@@ -170,7 +170,7 @@ public class StatisticOperations {
MetadataObjects.parse(
fullName,
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
if (object.type() != MetadataObject.Type.TABLE) {
- throw new UnsupportedOperationException(
+ throw new IllegalArgumentException(
"Update statistics is only supported for tables now.");
}
@@ -227,7 +227,7 @@ public class StatisticOperations {
MetadataObjects.parse(
fullName,
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
if (object.type() != MetadataObject.Type.TABLE) {
- throw new UnsupportedOperationException(
+ throw new IllegalArgumentException(
"Dropping statistics is only supported for tables now.");
}
@@ -280,7 +280,7 @@ public class StatisticOperations {
MetadataObjects.parse(
fullName,
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
if (object.type() != MetadataObject.Type.TABLE) {
- throw new UnsupportedOperationException(
+ throw new IllegalArgumentException(
"Listing partition statistics is only supported for tables
now.");
}
@@ -364,7 +364,7 @@ public class StatisticOperations {
MetadataObjects.parse(
fullName,
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
if (object.type() != MetadataObject.Type.TABLE) {
- throw new UnsupportedOperationException(
+ throw new IllegalArgumentException(
"Updating partition statistics is only supported for tables
now.");
}
@@ -430,7 +430,7 @@ public class StatisticOperations {
MetadataObjects.parse(
fullName,
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
if (object.type() != MetadataObject.Type.TABLE) {
- throw new UnsupportedOperationException(
+ throw new IllegalArgumentException(
"Dropping partition statistics is only supported for tables
now.");
}
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
b/server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
index 54b3f3c669..178884a3f2 100644
---
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
+++
b/server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
@@ -18,13 +18,15 @@
*/
package org.apache.gravitino.server.web.filter;
-import static org.mockito.ArgumentMatchers.any;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Response;
import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
public class TestAccessControlNotAllowedFilter {
@Test
@@ -32,6 +34,9 @@ public class TestAccessControlNotAllowedFilter {
AccessControlNotAllowedFilter filter = new AccessControlNotAllowedFilter();
ContainerRequestContext requestContext =
mock(ContainerRequestContext.class);
filter.filter(requestContext);
- verify(requestContext).abortWith(any());
+ ArgumentCaptor<Response> responseCaptor =
ArgumentCaptor.forClass(Response.class);
+ verify(requestContext).abortWith(responseCaptor.capture());
+ assertEquals(
+ Response.Status.METHOD_NOT_ALLOWED.getStatusCode(),
responseCaptor.getValue().getStatus());
}
}
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestExceptionHandlers.java
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestExceptionHandlers.java
index bdc43c4fc2..60140578cf 100644
---
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestExceptionHandlers.java
+++
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestExceptionHandlers.java
@@ -18,6 +18,11 @@
*/
package org.apache.gravitino.server.web.rest;
+import java.util.List;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.dto.responses.ErrorConstants;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.exceptions.UnmodifiableStatisticException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -50,4 +55,53 @@ public class TestExceptionHandlers {
String msg6 = ExceptionHandlers.BaseExceptionHandler.getErrorMsg(e6);
Assertions.assertEquals("", msg6);
}
+
+ @Test
+ void testUnsupportedOperationReturnsNotImplemented() {
+ UnsupportedOperationException exception =
+ new UnsupportedOperationException("Operation is not supported");
+ List<Response> responses =
+ List.of(
+ ExceptionHandlers.handleTableException(
+ OperationType.ALTER, "table", "schema", exception),
+ ExceptionHandlers.handlePolicyException(
+ OperationType.LIST, "policy", "metalake", exception),
+ new ExceptionHandlers.BaseExceptionHandler()
+ .handle(OperationType.LIST, "object", "parent", exception));
+
+ responses.forEach(
+ response -> {
+ Assertions.assertEquals(
+ Response.Status.NOT_IMPLEMENTED.getStatusCode(),
response.getStatus());
+ ErrorResponse errorResponse = (ErrorResponse) response.getEntity();
+ Assertions.assertEquals(
+ ErrorConstants.UNSUPPORTED_OPERATION_CODE,
errorResponse.getCode());
+ Assertions.assertEquals(
+ UnsupportedOperationException.class.getSimpleName(),
errorResponse.getType());
+ });
+ }
+
+ @Test
+ void testUnmodifiableOperationReturnsConflict() {
+ UnmodifiableStatisticException exception =
+ new UnmodifiableStatisticException("Statistic is unmodifiable");
+ List<Response> responses =
+ List.of(
+ ExceptionHandlers.handleStatisticException(
+ OperationType.ALTER, "statistic", "table", exception),
+ ExceptionHandlers.handlePartitionStatsException(
+ OperationType.ALTER, "partition", "table", exception),
+ new ExceptionHandlers.BaseExceptionHandler()
+ .handle(OperationType.ALTER, "statistic", "table", exception));
+
+ responses.forEach(
+ response -> {
+ Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(),
response.getStatus());
+ ErrorResponse errorResponse = (ErrorResponse) response.getEntity();
+ Assertions.assertEquals(
+ ErrorConstants.UNSUPPORTED_OPERATION_CODE,
errorResponse.getCode());
+ Assertions.assertEquals(
+ UnmodifiableStatisticException.class.getSimpleName(),
errorResponse.getType());
+ });
+ }
}
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestStatisticOperations.java
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestStatisticOperations.java
index f8951a9966..751ee690c2 100644
---
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestStatisticOperations.java
+++
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestStatisticOperations.java
@@ -236,6 +236,93 @@ public class TestStatisticOperations extends
BaseOperationsTest {
ErrorResponse errorResp2 = resp2.readEntity(ErrorResponse.class);
Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResp2.getCode());
Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResp2.getType());
+
+ // Test throw UnsupportedOperationException
+ doThrow(new UnsupportedOperationException("mock error"))
+ .when(manager)
+ .listStatistics(any(), any());
+ Response resp3 =
+ target(
+ "/metalakes/"
+ + metalake
+ + "/objects/"
+ + tableObject.type()
+ + "/"
+ + tableObject.fullName()
+ + "/statistics")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(),
resp3.getStatus());
+ ErrorResponse errorResp3 = resp3.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.UNSUPPORTED_OPERATION_CODE,
errorResp3.getCode());
+ Assertions.assertEquals(
+ UnsupportedOperationException.class.getSimpleName(),
errorResp3.getType());
+ }
+
+ /** Tests that every statistics endpoint rejects non-table object types as
invalid arguments. */
+ @Test
+ public void testStatisticsEndpointsRejectNonTableObject() {
+ MetadataObject catalogObject = MetadataObjects.parse(catalog,
MetadataObject.Type.CATALOG);
+ String path =
+ "/metalakes/"
+ + metalake
+ + "/objects/"
+ + catalogObject.type()
+ + "/"
+ + catalogObject.fullName()
+ + "/statistics";
+ Map<String, StatisticValue<?>> statistics =
+ Map.of(Statistic.CUSTOM_PREFIX + "test",
StatisticValues.longValue(1L));
+ StatisticsUpdateRequest updateRequest = new
StatisticsUpdateRequest(statistics);
+ StatisticsDropRequest dropRequest =
+ new StatisticsDropRequest(new String[] {Statistic.CUSTOM_PREFIX +
"test"});
+ PartitionStatisticsUpdateRequest partitionUpdateRequest =
+ new PartitionStatisticsUpdateRequest(
+ List.of(PartitionStatisticsUpdateDTO.of("partition", statistics)));
+ PartitionStatisticsDropRequest partitionDropRequest =
+ new PartitionStatisticsDropRequest(
+ List.of(
+ PartitionStatisticsDropDTO.of(
+ "partition", List.of(Statistic.CUSTOM_PREFIX + "test"))));
+
+ List<Response> responses =
+ List.of(
+ target(path)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get(),
+ target(path)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .put(entity(updateRequest, MediaType.APPLICATION_JSON_TYPE)),
+ target(path)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .post(entity(dropRequest, MediaType.APPLICATION_JSON_TYPE)),
+ target(path + "/partitions")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get(),
+ target(path + "/partitions")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .put(entity(partitionUpdateRequest,
MediaType.APPLICATION_JSON_TYPE)),
+ target(path + "/partitions")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .post(entity(partitionDropRequest,
MediaType.APPLICATION_JSON_TYPE)));
+
+ responses.forEach(
+ response -> {
+ Assertions.assertEquals(
+ Response.Status.BAD_REQUEST.getStatusCode(),
response.getStatus());
+ ErrorResponse errorResponse =
response.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.ILLEGAL_ARGUMENTS_CODE,
errorResponse.getCode());
+ Assertions.assertEquals(
+ IllegalArgumentException.class.getSimpleName(),
errorResponse.getType());
+ });
}
@Test
@@ -363,7 +450,7 @@ public class TestStatisticOperations extends
BaseOperationsTest {
.accept("application/vnd.gravitino.v1+json")
.put(entity(req, MediaType.APPLICATION_JSON_TYPE));
-
Assertions.assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(),
resp4.getStatus());
+ Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(),
resp4.getStatus());
Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp4.getMediaType());
ErrorResponse errorResp4 = resp4.readEntity(ErrorResponse.class);
@@ -619,7 +706,7 @@ public class TestStatisticOperations extends
BaseOperationsTest {
.accept("application/vnd.gravitino.v1+json")
.post(entity(req, MediaType.APPLICATION_JSON_TYPE));
-
Assertions.assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(),
resp3.getStatus());
+ Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(),
resp3.getStatus());
Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp3.getMediaType());
ErrorResponse errorResp3 = resp3.readEntity(ErrorResponse.class);
@@ -878,7 +965,7 @@ public class TestStatisticOperations extends
BaseOperationsTest {
.accept("application/vnd.gravitino.v1+json")
.put(entity(req, MediaType.APPLICATION_JSON_TYPE));
-
Assertions.assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(),
resp4.getStatus());
+ Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(),
resp4.getStatus());
Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp4.getMediaType());
ErrorResponse errorResp4 = resp4.readEntity(ErrorResponse.class);
@@ -1050,7 +1137,7 @@ public class TestStatisticOperations extends
BaseOperationsTest {
.accept("application/vnd.gravitino.v1+json")
.post(entity(req, MediaType.APPLICATION_JSON_TYPE));
-
Assertions.assertEquals(Response.Status.METHOD_NOT_ALLOWED.getStatusCode(),
resp3.getStatus());
+ Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(),
resp3.getStatus());
Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp3.getMediaType());
ErrorResponse errorResp3 = resp3.readEntity(ErrorResponse.class);
diff --git a/web-v2/web/src/lib/store/metalakes/index.js
b/web-v2/web/src/lib/store/metalakes/index.js
index 55566d6973..49283333f9 100644
--- a/web-v2/web/src/lib/store/metalakes/index.js
+++ b/web-v2/web/src/lib/store/metalakes/index.js
@@ -20,6 +20,7 @@
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import { to, extractPlaceholder, updateTreeData, findInTree } from
'@/lib/utils'
+import { isUnsupportedOperationError } from
'@/lib/utils/axios/unsupportedOperation'
import toast from 'react-hot-toast'
import _ from 'lodash-es'
@@ -2250,8 +2251,8 @@ export const fetchViews = createAsyncThunk(
const [err, res] = await to(getViewsApi({ metalake, catalog, schema }, {
errorMessageMode: 'none' }))
if (err || !res) {
- // Catalog doesn't support views (HTTP 405) — return empty views silently
- if (err?.response?.status === 405) {
+ // Catalog doesn't support views (HTTP 501, or legacy 405 with code
1006) — return empty views silently
+ if (isUnsupportedOperationError(err)) {
return { views: [], init }
}
if (init) {
@@ -2304,8 +2305,8 @@ export const getViewDetails = createAsyncThunk(
const [err, res] = await to(getViewDetailsApi({ metalake, catalog, schema,
view }, { errorMessageMode: 'none' }))
if (err || !res) {
- // Catalog doesn't support views (HTTP 405) — return empty result
silently
- if (err?.response?.status === 405) {
+ // Catalog doesn't support views (HTTP 501, or legacy 405 with code
1006) — return empty result silently
+ if (isUnsupportedOperationError(err)) {
return { view: null, init }
}
throw new Error(err)
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
b/web-v2/web/src/lib/utils/axios/unsupportedOperation.js
similarity index 54%
copy from
server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
copy to web-v2/web/src/lib/utils/axios/unsupportedOperation.js
index 54b3f3c669..bc9fb5c616 100644
---
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestAccessControlNotAllowedFilter.java
+++ b/web-v2/web/src/lib/utils/axios/unsupportedOperation.js
@@ -16,22 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.gravitino.server.web.filter;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
+const UNSUPPORTED_OPERATION_CODE = 1006
-import java.io.IOException;
-import javax.ws.rs.container.ContainerRequestContext;
-import org.junit.jupiter.api.Test;
-
-public class TestAccessControlNotAllowedFilter {
- @Test
- public void testAccessControlNotAllowedFilter() throws IOException {
- AccessControlNotAllowedFilter filter = new AccessControlNotAllowedFilter();
- ContainerRequestContext requestContext =
mock(ContainerRequestContext.class);
- filter.filter(requestContext);
- verify(requestContext).abortWith(any());
- }
-}
+/** Returns whether an HTTP client error represents an unsupported server
operation. */
+export const isUnsupportedOperationError = error =>
+ error?.response?.status === 501 ||
+ (error?.response?.status === 405 && error?.response?.data?.code ===
UNSUPPORTED_OPERATION_CODE)
diff --git a/web-v2/web/src/lib/utils/axios/unsupportedOperation.test.js
b/web-v2/web/src/lib/utils/axios/unsupportedOperation.test.js
new file mode 100644
index 0000000000..d8ef382d9b
--- /dev/null
+++ b/web-v2/web/src/lib/utils/axios/unsupportedOperation.test.js
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+import { describe, expect, it } from 'vitest'
+import { isUnsupportedOperationError } from
'@/lib/utils/axios/unsupportedOperation'
+
+describe('isUnsupportedOperationError', () => {
+ it('recognizes HTTP 501 as an unsupported operation', () => {
+ expect(isUnsupportedOperationError({ response: { status: 501 }
})).toBe(true)
+ })
+
+ it('recognizes the legacy HTTP 405 response by its application error code',
() => {
+ expect(isUnsupportedOperationError({ response: { status: 405, data: {
code: 1006 } } })).toBe(true)
+ })
+
+ it('does not hide an actual HTTP method mismatch', () => {
+ expect(isUnsupportedOperationError({ response: { status: 405, data: {
code: 1000 } } })).toBe(false)
+ })
+
+ it('does not treat an HTTP conflict as an unsupported operation', () => {
+ expect(isUnsupportedOperationError({ response: { status: 409, data: {
code: 1006 } } })).toBe(false)
+ })
+
+ it('handles errors without an HTTP response', () => {
+ expect(isUnsupportedOperationError(new Error('network error'))).toBe(false)
+ })
+})