From 96bcfda2531dca1270d02bc81a98636f71b8a72c Mon Sep 17 00:00:00 2001 From: lining Date: Thu, 12 Dec 2024 17:02:19 +0800 Subject: [PATCH] [core] Update drop Database API and remove api in URL (#4691) --- .../org/apache/paimon/rest/HttpClient.java | 38 +++------ .../org/apache/paimon/rest/RESTCatalog.java | 14 ++-- .../org/apache/paimon/rest/RESTClient.java | 2 +- .../org/apache/paimon/rest/ResourcePaths.java | 6 +- .../rest/requests/DropDatabaseRequest.java | 56 ------------- .../apache/paimon/rest/HttpClientTest.java | 5 +- .../apache/paimon/rest/MockRESTMessage.java | 12 ++- .../apache/paimon/rest/RESTCatalogTest.java | 81 ++++++++++++++++--- .../paimon/rest/RESTObjectMapperTest.java | 10 --- paimon-open-api/rest-catalog-open-api.yaml | 60 ++++++-------- .../open/api/RESTCatalogController.java | 40 ++++----- .../paimon/open/api/config/OpenAPIConfig.java | 36 +++++++++ .../src/main/resources/application.properties | 4 + 13 files changed, 180 insertions(+), 184 deletions(-) delete mode 100644 paimon-core/src/main/java/org/apache/paimon/rest/requests/DropDatabaseRequest.java diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java index 97696aef09ed..87f3fad9b2fd 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java @@ -65,17 +65,9 @@ public HttpClient(HttpClientOptions httpClientOptions) { @Override public T get( String path, Class responseType, Map headers) { - try { - Request request = - new Request.Builder() - .url(uri + path) - .get() - .headers(Headers.of(headers)) - .build(); - return exec(request, responseType); - } catch (Exception e) { - throw new RuntimeException(e); - } + Request request = + new Request.Builder().url(uri + path).get().headers(Headers.of(headers)).build(); + return exec(request, responseType); } @Override @@ -90,26 +82,16 @@ public T post( .headers(Headers.of(headers)) .build(); return exec(request, responseType); - } catch (Exception e) { - throw new RuntimeException(e); + } catch (JsonProcessingException e) { + throw new RESTException(e, "build request failed."); } } @Override - public T delete( - String path, RESTRequest body, Map headers) { - try { - RequestBody requestBody = buildRequestBody(body); - Request request = - new Request.Builder() - .url(uri + path) - .delete(requestBody) - .headers(Headers.of(headers)) - .build(); - return exec(request, null); - } catch (Exception e) { - throw new RuntimeException(e); - } + public T delete(String path, Map headers) { + Request request = + new Request.Builder().url(uri + path).delete().headers(Headers.of(headers)).build(); + return exec(request, null); } @Override @@ -135,6 +117,8 @@ private T exec(Request request, Class responseType) } else { throw new RESTException("response body is null."); } + } catch (RESTException e) { + throw e; } catch (Exception e) { throw new RESTException(e, "rest exception"); } 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 3c2538df0ca2..03b257efbf86 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 @@ -32,7 +32,6 @@ import org.apache.paimon.rest.exceptions.AlreadyExistsException; import org.apache.paimon.rest.exceptions.NoSuchResourceException; import org.apache.paimon.rest.requests.CreateDatabaseRequest; -import org.apache.paimon.rest.requests.DropDatabaseRequest; import org.apache.paimon.rest.responses.ConfigResponse; import org.apache.paimon.rest.responses.CreateDatabaseResponse; import org.apache.paimon.rest.responses.DatabaseName; @@ -47,6 +46,7 @@ import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.ObjectMapper; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -160,11 +160,15 @@ public Database getDatabase(String name) throws DatabaseNotExistException { @Override public void dropDatabase(String name, boolean ignoreIfNotExists, boolean cascade) throws DatabaseNotExistException, DatabaseNotEmptyException { - DropDatabaseRequest request = new DropDatabaseRequest(ignoreIfNotExists, cascade); try { - client.delete(resourcePaths.database(name), request, headers()); + if (!cascade && !this.listTables(name).isEmpty()) { + throw new DatabaseNotEmptyException(name); + } + client.delete(resourcePaths.database(name), headers()); } catch (NoSuchResourceException e) { - throw new DatabaseNotExistException(name); + if (!ignoreIfNotExists) { + throw new DatabaseNotExistException(name); + } } } @@ -180,7 +184,7 @@ public Path getTableLocation(Identifier identifier) { @Override public List listTables(String databaseName) throws DatabaseNotExistException { - throw new UnsupportedOperationException(); + return new ArrayList(); } @Override diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTClient.java b/paimon-core/src/main/java/org/apache/paimon/rest/RESTClient.java index d0244f309ef4..a255d688bc52 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTClient.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTClient.java @@ -29,5 +29,5 @@ public interface RESTClient extends Closeable { T post( String path, RESTRequest body, Class responseType, Map headers); - T delete(String path, RESTRequest body, Map headers); + T delete(String path, Map headers); } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java b/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java index a6d0000a225b..b58053374daa 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java @@ -23,7 +23,7 @@ /** Resource paths for REST catalog. */ public class ResourcePaths { - public static final String V1_CONFIG = "/api/v1/config"; + public static final String V1_CONFIG = "/v1/config"; private static final StringJoiner SLASH = new StringJoiner("/"); public static ResourcePaths forCatalogProperties(String prefix) { @@ -37,10 +37,10 @@ public ResourcePaths(String prefix) { } public String databases() { - return SLASH.add("api").add("v1").add(prefix).add("databases").toString(); + return SLASH.add("v1").add(prefix).add("databases").toString(); } public String database(String databaseName) { - return SLASH.add("api").add("v1").add(prefix).add("databases").add(databaseName).toString(); + return SLASH.add("v1").add(prefix).add("databases").add(databaseName).toString(); } } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/requests/DropDatabaseRequest.java b/paimon-core/src/main/java/org/apache/paimon/rest/requests/DropDatabaseRequest.java deleted file mode 100644 index d97f211c1caa..000000000000 --- a/paimon-core/src/main/java/org/apache/paimon/rest/requests/DropDatabaseRequest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.requests; - -import org.apache.paimon.rest.RESTRequest; - -import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator; -import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonGetter; -import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonProperty; - -/** Request for DropDatabase. */ -public class DropDatabaseRequest implements RESTRequest { - - private static final String FIELD_IGNORE_IF_EXISTS = "ignoreIfExists"; - private static final String FIELD_CASCADE = "cascade"; - - @JsonProperty(FIELD_IGNORE_IF_EXISTS) - private final boolean ignoreIfNotExists; - - @JsonProperty(FIELD_CASCADE) - private final boolean cascade; - - @JsonCreator - public DropDatabaseRequest( - @JsonProperty(FIELD_IGNORE_IF_EXISTS) boolean ignoreIfNotExists, - @JsonProperty(FIELD_CASCADE) boolean cascade) { - this.ignoreIfNotExists = ignoreIfNotExists; - this.cascade = cascade; - } - - @JsonGetter(FIELD_IGNORE_IF_EXISTS) - public boolean getIgnoreIfNotExists() { - return ignoreIfNotExists; - } - - @JsonGetter(FIELD_CASCADE) - public boolean getCascade() { - return cascade; - } -} 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 f12af12a9d35..a3b06b8ce3a9 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 @@ -43,6 +43,7 @@ /** Test for {@link HttpClient}. */ public class HttpClientTest { + private MockWebServer mockWebServer; private HttpClient httpClient; private ObjectMapper objectMapper = RESTObjectMapper.create(); @@ -113,14 +114,14 @@ public void testPostFail() { @Test public void testDeleteSuccess() { mockHttpCallWithCode(mockResponseDataStr, 200); - MockRESTData response = httpClient.delete(MOCK_PATH, mockResponseData, headers); + MockRESTData response = httpClient.delete(MOCK_PATH, headers); verify(errorHandler, times(0)).accept(any()); } @Test public void testDeleteFail() { mockHttpCallWithCode(mockResponseDataStr, 400); - httpClient.delete(MOCK_PATH, mockResponseData, headers); + httpClient.delete(MOCK_PATH, headers); verify(errorHandler, times(1)).accept(any()); } diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java index f111c41f6ada..a605e5e77c2a 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java @@ -19,9 +19,9 @@ package org.apache.paimon.rest; import org.apache.paimon.rest.requests.CreateDatabaseRequest; -import org.apache.paimon.rest.requests.DropDatabaseRequest; import org.apache.paimon.rest.responses.CreateDatabaseResponse; import org.apache.paimon.rest.responses.DatabaseName; +import org.apache.paimon.rest.responses.ErrorResponse; import org.apache.paimon.rest.responses.GetDatabaseResponse; import org.apache.paimon.rest.responses.ListDatabasesResponse; @@ -46,12 +46,6 @@ public static CreateDatabaseRequest createDatabaseRequest(String name) { return new CreateDatabaseRequest(name, ignoreIfExists, options); } - public static DropDatabaseRequest dropDatabaseRequest() { - boolean ignoreIfNotExists = true; - boolean cascade = true; - return new DropDatabaseRequest(ignoreIfNotExists, cascade); - } - public static CreateDatabaseResponse createDatabaseResponse(String name) { Map options = new HashMap<>(); options.put("a", "b"); @@ -71,4 +65,8 @@ public static ListDatabasesResponse listDatabasesResponse(String name) { databaseNameList.add(databaseName); return new ListDatabasesResponse(databaseNameList); } + + public static ErrorResponse noSuchResourceExceptionErrorResponse() { + return new ErrorResponse("message", 404, new ArrayList<>()); + } } diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java index cffac6046623..0fff81afdcde 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java @@ -18,10 +18,12 @@ package org.apache.paimon.rest; +import org.apache.paimon.catalog.Catalog; import org.apache.paimon.catalog.Database; import org.apache.paimon.options.CatalogOptions; import org.apache.paimon.options.Options; import org.apache.paimon.rest.responses.CreateDatabaseResponse; +import org.apache.paimon.rest.responses.ErrorResponse; import org.apache.paimon.rest.responses.GetDatabaseResponse; import org.apache.paimon.rest.responses.ListDatabasesResponse; @@ -35,6 +37,7 @@ import org.junit.Test; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,13 +45,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** Test for REST Catalog. */ public class RESTCatalogTest { - private ObjectMapper mapper = RESTObjectMapper.create(); + private final ObjectMapper mapper = RESTObjectMapper.create(); private MockWebServer mockWebServer; private RESTCatalog restCatalog; + private RESTCatalog mockRestCatalog; @Before public void setUp() throws IOException { @@ -64,8 +73,9 @@ public void setUp() throws IOException { String.format( "{\"defaults\": {\"%s\": \"%s\"}}", RESTCatalogInternalOptions.PREFIX.key(), "prefix"); - mockResponse(mockResponse); + mockResponse(mockResponse, 200); restCatalog = new RESTCatalog(options); + mockRestCatalog = spy(restCatalog); } @After @@ -85,7 +95,7 @@ public void testGetConfig() { String key = "a"; String value = "b"; String mockResponse = String.format("{\"defaults\": {\"%s\": \"%s\"}}", key, value); - mockResponse(mockResponse); + mockResponse(mockResponse, 200); Map header = new HashMap<>(); Map response = restCatalog.fetchOptionsFromServer(header, new HashMap<>()); assertEquals(value, response.get(key)); @@ -95,7 +105,7 @@ public void testGetConfig() { public void testListDatabases() throws JsonProcessingException { String name = MockRESTMessage.databaseName(); ListDatabasesResponse response = MockRESTMessage.listDatabasesResponse(name); - mockResponse(mapper.writeValueAsString(response)); + mockResponse(mapper.writeValueAsString(response), 200); List result = restCatalog.listDatabases(); assertEquals(response.getDatabases().size(), result.size()); assertEquals(name, result.get(0)); @@ -105,7 +115,7 @@ public void testListDatabases() throws JsonProcessingException { public void testCreateDatabase() throws Exception { String name = MockRESTMessage.databaseName(); CreateDatabaseResponse response = MockRESTMessage.createDatabaseResponse(name); - mockResponse(mapper.writeValueAsString(response)); + mockResponse(mapper.writeValueAsString(response), 200); assertDoesNotThrow(() -> restCatalog.createDatabase(name, false, response.getOptions())); } @@ -113,7 +123,7 @@ public void testCreateDatabase() throws Exception { public void testGetDatabase() throws Exception { String name = MockRESTMessage.databaseName(); GetDatabaseResponse response = MockRESTMessage.getDatabaseResponse(name); - mockResponse(mapper.writeValueAsString(response)); + mockResponse(mapper.writeValueAsString(response), 200); Database result = restCatalog.getDatabase(name); assertEquals(name, result.name()); assertEquals(response.getOptions().size(), result.options().size()); @@ -121,15 +131,64 @@ public void testGetDatabase() throws Exception { } @Test - public void testDropDatabase() { - String name = "name"; - mockResponse(""); - assertDoesNotThrow(() -> restCatalog.dropDatabase(name, false, false)); + public void testDropDatabase() throws Exception { + String name = MockRESTMessage.databaseName(); + mockResponse("", 200); + assertDoesNotThrow(() -> mockRestCatalog.dropDatabase(name, false, true)); + verify(mockRestCatalog, times(1)).dropDatabase(eq(name), eq(false), eq(true)); + verify(mockRestCatalog, times(0)).listTables(eq(name)); + } + + @Test + public void testDropDatabaseWhenNoExistAndIgnoreIfNotExistsIsFalse() throws Exception { + String name = MockRESTMessage.databaseName(); + ErrorResponse response = MockRESTMessage.noSuchResourceExceptionErrorResponse(); + mockResponse(mapper.writeValueAsString(response), 404); + assertThrows( + Catalog.DatabaseNotExistException.class, + () -> mockRestCatalog.dropDatabase(name, false, true)); + } + + @Test + public void testDropDatabaseWhenNoExistAndIgnoreIfNotExistsIsTrue() throws Exception { + String name = MockRESTMessage.databaseName(); + ErrorResponse response = MockRESTMessage.noSuchResourceExceptionErrorResponse(); + mockResponse(mapper.writeValueAsString(response), 404); + assertDoesNotThrow(() -> mockRestCatalog.dropDatabase(name, true, true)); + verify(mockRestCatalog, times(1)).dropDatabase(eq(name), eq(true), eq(true)); + verify(mockRestCatalog, times(0)).listTables(eq(name)); + } + + @Test + public void testDropDatabaseWhenCascadeIsFalseAndNoTables() throws Exception { + String name = MockRESTMessage.databaseName(); + boolean cascade = false; + mockResponse("", 200); + when(mockRestCatalog.listTables(name)).thenReturn(new ArrayList<>()); + assertDoesNotThrow(() -> mockRestCatalog.dropDatabase(name, false, cascade)); + verify(mockRestCatalog, times(1)).dropDatabase(eq(name), eq(false), eq(cascade)); + verify(mockRestCatalog, times(1)).listTables(eq(name)); + } + + @Test + public void testDropDatabaseWhenCascadeIsFalseAndTablesExist() throws Exception { + String name = MockRESTMessage.databaseName(); + boolean cascade = false; + mockResponse("", 200); + List tables = new ArrayList<>(); + tables.add("t1"); + when(mockRestCatalog.listTables(name)).thenReturn(tables); + assertThrows( + Catalog.DatabaseNotEmptyException.class, + () -> mockRestCatalog.dropDatabase(name, false, cascade)); + verify(mockRestCatalog, times(1)).dropDatabase(eq(name), eq(false), eq(cascade)); + verify(mockRestCatalog, times(1)).listTables(eq(name)); } - private void mockResponse(String mockResponse) { + private void mockResponse(String mockResponse, int httpCode) { MockResponse mockResponseObj = new MockResponse() + .setResponseCode(httpCode) .setBody(mockResponse) .addHeader("Content-Type", "application/json"); mockWebServer.enqueue(mockResponseObj); diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java index 622a98993692..7fee81ef1024 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java @@ -19,7 +19,6 @@ package org.apache.paimon.rest; import org.apache.paimon.rest.requests.CreateDatabaseRequest; -import org.apache.paimon.rest.requests.DropDatabaseRequest; import org.apache.paimon.rest.responses.ConfigResponse; import org.apache.paimon.rest.responses.CreateDatabaseResponse; import org.apache.paimon.rest.responses.ErrorResponse; @@ -73,15 +72,6 @@ public void createDatabaseRequestParseTest() throws Exception { assertEquals(request.getOptions().size(), parseData.getOptions().size()); } - @Test - public void dropDatabaseRequestParseTest() throws Exception { - DropDatabaseRequest request = MockRESTMessage.dropDatabaseRequest(); - String requestStr = mapper.writeValueAsString(request); - DropDatabaseRequest parseData = mapper.readValue(requestStr, DropDatabaseRequest.class); - assertEquals(request.getIgnoreIfNotExists(), parseData.getIgnoreIfNotExists()); - assertEquals(request.getCascade(), parseData.getCascade()); - } - @Test public void createDatabaseResponseParseTest() throws Exception { String name = MockRESTMessage.databaseName(); diff --git a/paimon-open-api/rest-catalog-open-api.yaml b/paimon-open-api/rest-catalog-open-api.yaml index 2a5d1dc58418..9b69b3de2776 100644 --- a/paimon-open-api/rest-catalog-open-api.yaml +++ b/paimon-open-api/rest-catalog-open-api.yaml @@ -28,7 +28,7 @@ servers: - url: http://localhost:8080 description: Server URL in Development environment paths: - /api/v1/{prefix}/databases: + /v1/{prefix}/databases: get: tags: - database @@ -66,21 +66,21 @@ paths: schema: $ref: '#/components/schemas/CreateDatabaseRequest' responses: - "500": - description: Internal Server Error - "409": - description: Resource has exist - content: - '*/*': - schema: - $ref: '#/components/schemas/ErrorResponse' "200": description: OK content: application/json: schema: $ref: '#/components/schemas/CreateDatabaseResponse' - /api/v1/{prefix}/databases/{database}: + "409": + description: Resource has exist + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + "500": + description: Internal Server Error + /v1/{prefix}/databases/{database}: get: tags: - database @@ -107,7 +107,7 @@ paths: "404": description: Resource not found content: - '*/*': + application/json: schema: $ref: '#/components/schemas/ErrorResponse' "500": @@ -128,35 +128,30 @@ paths: required: true schema: type: string - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/DropDatabaseRequest' responses: "404": description: Resource not found content: - '*/*': + application/json: schema: $ref: '#/components/schemas/ErrorResponse' "500": description: Internal Server Error - /api/v1/config: + /v1/config: get: tags: - config summary: Get Config operationId: getConfig responses: - "500": - description: Internal Server Error "200": description: OK content: application/json: schema: $ref: '#/components/schemas/ConfigResponse' + "500": + description: Internal Server Error components: schemas: CreateDatabaseRequest: @@ -170,6 +165,15 @@ components: type: object additionalProperties: type: string + CreateDatabaseResponse: + type: object + properties: + name: + type: string + options: + type: object + additionalProperties: + type: string ErrorResponse: type: object properties: @@ -182,15 +186,6 @@ components: type: array items: type: string - CreateDatabaseResponse: - type: object - properties: - name: - type: string - options: - type: object - additionalProperties: - type: string DatabaseName: type: object properties: @@ -223,10 +218,3 @@ components: type: object additionalProperties: type: string - DropDatabaseRequest: - type: object - properties: - ignoreIfNotExists: - type: boolean - cascade: - type: boolean diff --git a/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java b/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java index 364cc5adbb2c..19f6f8cdf673 100644 --- a/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java +++ b/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java @@ -20,7 +20,6 @@ import org.apache.paimon.rest.ResourcePaths; import org.apache.paimon.rest.requests.CreateDatabaseRequest; -import org.apache.paimon.rest.requests.DropDatabaseRequest; import org.apache.paimon.rest.responses.ConfigResponse; import org.apache.paimon.rest.responses.CreateDatabaseResponse; import org.apache.paimon.rest.responses.DatabaseName; @@ -57,11 +56,7 @@ public class RESTCatalogController { @ApiResponses({ @ApiResponse( responseCode = "200", - content = { - @Content( - schema = @Schema(implementation = ConfigResponse.class), - mediaType = "application/json") - }), + content = {@Content(schema = @Schema(implementation = ConfigResponse.class))}), @ApiResponse( responseCode = "500", content = {@Content(schema = @Schema())}) @@ -80,15 +75,13 @@ public ConfigResponse getConfig() { @ApiResponse( responseCode = "200", content = { - @Content( - schema = @Schema(implementation = ListDatabasesResponse.class), - mediaType = "application/json") + @Content(schema = @Schema(implementation = ListDatabasesResponse.class)) }), @ApiResponse( responseCode = "500", content = {@Content(schema = @Schema())}) }) - @GetMapping("/api/v1/{prefix}/databases") + @GetMapping("/v1/{prefix}/databases") public ListDatabasesResponse listDatabases(@PathVariable String prefix) { return new ListDatabasesResponse(ImmutableList.of(new DatabaseName("account"))); } @@ -100,19 +93,21 @@ public ListDatabasesResponse listDatabases(@PathVariable String prefix) { @ApiResponse( responseCode = "200", content = { - @Content( - schema = @Schema(implementation = CreateDatabaseResponse.class), - mediaType = "application/json") + @Content(schema = @Schema(implementation = CreateDatabaseResponse.class)) }), @ApiResponse( responseCode = "409", description = "Resource has exist", - content = {@Content(schema = @Schema(implementation = ErrorResponse.class))}), + content = { + @Content( + schema = @Schema(implementation = ErrorResponse.class), + mediaType = "application/json") + }), @ApiResponse( responseCode = "500", content = {@Content(schema = @Schema())}) }) - @PostMapping("/api/v1/{prefix}/databases") + @PostMapping("/v1/{prefix}/databases") public CreateDatabaseResponse createDatabases( @PathVariable String prefix, @RequestBody CreateDatabaseRequest request) { Map properties = new HashMap<>(); @@ -125,11 +120,7 @@ public CreateDatabaseResponse createDatabases( @ApiResponses({ @ApiResponse( responseCode = "200", - content = { - @Content( - schema = @Schema(implementation = GetDatabaseResponse.class), - mediaType = "application/json") - }), + content = {@Content(schema = @Schema(implementation = GetDatabaseResponse.class))}), @ApiResponse( responseCode = "404", description = "Resource not found", @@ -138,7 +129,7 @@ public CreateDatabaseResponse createDatabases( responseCode = "500", content = {@Content(schema = @Schema())}) }) - @GetMapping("/api/v1/{prefix}/databases/{database}") + @GetMapping("/v1/{prefix}/databases/{database}") public GetDatabaseResponse getDatabases( @PathVariable String prefix, @PathVariable String database) { Map options = new HashMap<>(); @@ -157,9 +148,6 @@ public GetDatabaseResponse getDatabases( responseCode = "500", content = {@Content(schema = @Schema())}) }) - @DeleteMapping("/api/v1/{prefix}/databases/{database}") - public void dropDatabases( - @PathVariable String prefix, - @PathVariable String database, - @RequestBody DropDatabaseRequest request) {} + @DeleteMapping("/v1/{prefix}/databases/{database}") + public void dropDatabases(@PathVariable String prefix, @PathVariable String database) {} } diff --git a/paimon-open-api/src/main/java/org/apache/paimon/open/api/config/OpenAPIConfig.java b/paimon-open-api/src/main/java/org/apache/paimon/open/api/config/OpenAPIConfig.java index 0e28cd95f9d2..71ac066d4a70 100644 --- a/paimon-open-api/src/main/java/org/apache/paimon/open/api/config/OpenAPIConfig.java +++ b/paimon-open-api/src/main/java/org/apache/paimon/open/api/config/OpenAPIConfig.java @@ -21,17 +21,21 @@ import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.responses.ApiResponses; import io.swagger.v3.oas.models.servers.Server; +import org.springdoc.core.customizers.OpenApiCustomiser; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; /** Config for OpenAPI. */ @Configuration public class OpenAPIConfig { + @Value("${openapi.url}") private String devUrl; @@ -56,4 +60,36 @@ public OpenAPI restCatalogOpenAPI() { servers.add(server); return new OpenAPI().info(info).servers(servers); } + + /** Sort response alphabetically. So the api generate will in same order everytime. */ + @Bean + public OpenApiCustomiser sortResponseAlphabetically() { + return openApi -> { + openApi.getPaths() + .values() + .forEach( + path -> + path.readOperations() + .forEach( + operation -> { + ApiResponses responses = + operation.getResponses(); + if (responses != null) { + ApiResponses sortedResponses = + new ApiResponses(); + List keys = + new ArrayList<>( + responses.keySet()); + keys.sort(Comparator.naturalOrder()); + + for (String key : keys) { + sortedResponses.addApiResponse( + key, responses.get(key)); + } + + operation.setResponses(sortedResponses); + } + })); + }; + } } diff --git a/paimon-open-api/src/main/resources/application.properties b/paimon-open-api/src/main/resources/application.properties index 58a975161145..1e7a987c9d40 100644 --- a/paimon-open-api/src/main/resources/application.properties +++ b/paimon-open-api/src/main/resources/application.properties @@ -19,4 +19,8 @@ springdoc.api-docs.path=/swagger-api-docs springdoc.swagger-ui.deepLinking=true springdoc.swagger-ui.tryItOutEnabled=true springdoc.swagger-ui.filter=true +springdoc.swagger-ui.tagsSorter=alpha +springdoc.swagger-ui.operations-sorter=alpha +# define response default media type +springdoc.default-produces-media-type=application/json openapi.url=http://localhost:8080