This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new 7ec5d50566b CAMEL-20979: if no operationId is specified, generate an id (#15257) 7ec5d50566b is described below commit 7ec5d50566b5afeb3377726b9866de82be46af81 Author: klease <38634989+kle...@users.noreply.github.com> AuthorDate: Wed Aug 21 20:07:52 2024 +0200 CAMEL-20979: if no operationId is specified, generate an id (#15257) * CAMEL-20979: if no operationId is specified, generate an id base on the path and the http method. This allows the user to add a route if the OpenAPI specification is not modifiable. * Lower log level and remove commented code. --- .../DefaultRestOpenapiProcessorStrategy.java | 23 ++++++- .../openapi/RestOpenapiProcessorStrategyTest.java | 76 ++++++++++++++++++++++ .../src/test/resources/missing-opid.yaml | 59 +++++++++++++++++ 3 files changed, 156 insertions(+), 2 deletions(-) diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java index 7d62f6a169c..c931dc20102 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem.HttpMethod; import io.swagger.v3.oas.models.examples.Example; import io.swagger.v3.oas.models.media.Content; import io.swagger.v3.oas.models.media.MediaType; @@ -64,6 +65,8 @@ public class DefaultRestOpenapiProcessorStrategy extends ServiceSupport private static final String BODY_VERBS = "DELETE,PUT,POST,PATCH"; + private static final String GEN_OPID = "GENOPID_"; + private CamelContext camelContext; private ProducerCache producerCache; private String component = "direct"; @@ -75,8 +78,9 @@ public class DefaultRestOpenapiProcessorStrategy extends ServiceSupport public void validateOpenApi(OpenAPI openAPI, PlatformHttpConsumerAware platformHttpConsumer) throws Exception { List<String> ids = new ArrayList<>(); for (var e : openAPI.getPaths().entrySet()) { - for (var o : e.getValue().readOperations()) { - String id = o.getOperationId(); + for (var o : e.getValue().readOperationsMap().entrySet()) { + Operation op = o.getValue(); + String id = op.getOperationId() != null ? op.getOperationId() : generateOperationId(e.getKey(), o.getKey()); ids.add(component + "://" + id); } } @@ -145,6 +149,21 @@ public class DefaultRestOpenapiProcessorStrategy extends ServiceSupport } } + /** + * If the operation has no operationId specified, generate one based on the path and the operation method. + * + * @param path The path for this operation, such as /users. + * @param httpMethod The operation to perform + * @return A generated operation id based on the path and the operation. Slashes and braces in the path + * are replaced with placeholder characters. + */ + private String generateOperationId(String path, HttpMethod httpMethod) { + final String sanitizedPath = path.replace('/', '.').replaceAll("[{}]", "_"); + final String opId = GEN_OPID + httpMethod.name() + sanitizedPath; + LOG.debug("Generated operationId {} for path {} and method {}", opId, path, httpMethod.name()); + return opId; + } + @Override public boolean processApiSpecification(String specificationUri, Exchange exchange, AsyncCallback callback) { try { diff --git a/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategyTest.java b/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategyTest.java new file mode 100644 index 00000000000..7c906b41106 --- /dev/null +++ b/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenapiProcessorStrategyTest.java @@ -0,0 +1,76 @@ +/* + * 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.camel.component.rest.openapi; + +import io.swagger.v3.oas.models.OpenAPI; +import org.apache.camel.CamelContext; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.platform.http.PlatformHttpComponent; +import org.apache.camel.component.platform.http.spi.PlatformHttpConsumerAware; +import org.apache.camel.impl.DefaultCamelContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +class RestOpenapiProcessorStrategyTest extends ManagedCamelTestSupport { + + private CamelContext camelContext; + + @BeforeEach + public void createMocks() throws Exception { + initializeContextForComponent("rest-openapi"); + } + + @Test + void testMissingOperationId() throws Exception { + RestOpenapiProcessorStrategy restOpenapiProcessorStrategy = new DefaultRestOpenapiProcessorStrategy(); + ((DefaultRestOpenapiProcessorStrategy) restOpenapiProcessorStrategy).setCamelContext(camelContext); + restOpenapiProcessorStrategy.setMissingOperation("fail"); + Exception ex = assertThrows(IllegalArgumentException.class, + () -> restOpenapiProcessorStrategy.validateOpenApi(getOpenApi(), mock(PlatformHttpConsumerAware.class))); + assertTrue(ex.getMessage().contains("direct:GENOPID_GET.users")); + assertTrue(ex.getMessage().contains("direct:GENOPID_GET.user._id_")); + + } + + private OpenAPI getOpenApi() { + return RestOpenApiEndpoint.loadSpecificationFrom(camelContext, "missing-opid.yaml"); + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:getUsers").to("mock:getUsers"); + } + }; + } + + @Override + protected CamelContext createCamelContext(String componentName) { + + camelContext = new DefaultCamelContext(); + PlatformHttpComponent httpCmpn = mock(PlatformHttpComponent.class); + camelContext.addComponent("platform-http", httpCmpn); + return camelContext; + } + +} diff --git a/components/camel-rest-openapi/src/test/resources/missing-opid.yaml b/components/camel-rest-openapi/src/test/resources/missing-opid.yaml new file mode 100644 index 00000000000..7fe589eb7f2 --- /dev/null +++ b/components/camel-rest-openapi/src/test/resources/missing-opid.yaml @@ -0,0 +1,59 @@ +# +# 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. +# + +openapi: 3.0.0 +info: + title: Sample API + description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. + version: 0.1.9 +servers: + - url: http://api.example.com/v1 + description: Optional server description, e.g. Main (production) server + - url: http://staging-api.example.com + description: Optional server description, e.g. Internal staging server for testing +paths: + /users: + get: + summary: Returns a list of users. + description: Optional extended description in CommonMark or HTML. + responses: + '200': # status code + description: A JSON array of user names + content: + application/json: + schema: + type: array + items: + type: string + + /user/{id}: + get: + summary: Return a user by id + parameters: + - name: id + in: path + description: ID of user to return + required: true + type: integer + format: int64 + responses: + '200': # status code + description: A JSON array of user names + content: + application/json: + schema: + type: string \ No newline at end of file