This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-25747 in repository https://gitbox.apache.org/repos/asf/camel.git
commit fb2c581b3b24ad55f19ae87d7721d366da927041 Author: Claus Ibsen <[email protected]> AuthorDate: Sun Oct 18 09:08:52 2026 +0200 CAMEL-25747: camel-rest-openapi - log the problems the OpenAPI parser finds in the specification The parser ignores the parts of a specification it cannot read and reports them only in the parse result, which camel-rest-openapi dropped. A path nested inside another path item then answered 404, with nothing in the log. - The parser messages are logged as a WARN when the specification is loaded, for the consumer (the contract-first Rest DSL) and the producer. - When the parser cannot read the specification at all, the exception now includes its messages, such as "attribute openapi is missing" for a Swagger 2.0 document. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m --- .../rest/openapi/RestOpenApiEndpoint.java | 21 +++-- .../RestOpenApiSpecificationProblemsTest.java | 96 ++++++++++++++++++++++ .../src/test/resources/nested-path.json | 39 +++++++++ 3 files changed, 151 insertions(+), 5 deletions(-) diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java index 142eb1d1f992..d4c39cf24d1c 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java @@ -998,7 +998,8 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint { /** * Loads the OpenApi definition model from the given path. This delegates directly to the OpenAPI parser. If the - * specification can't be read there is no OpenAPI object in the result. + * specification can't be read there is no OpenAPI object in the result. The problems the parser reports are logged + * as a warning, because the parser ignores the parts it cannot read, such as a path nested inside another path. * * @param uri the specification uri * @param camelContext context to use @@ -1010,6 +1011,7 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint { options.setResolveFully(true); InputStream is = null; + List<String> messages = null; try { String location = null; String content = null; @@ -1028,8 +1030,16 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint { } else if (content != null) { openApi = openApiParser.readContents(content, null, options); } - if (openApi != null && openApi.getOpenAPI() != null) { - return openApi.getOpenAPI(); + if (openApi != null) { + messages = openApi.getMessages(); + if (openApi.getOpenAPI() != null) { + if (messages != null && !messages.isEmpty()) { + LOG.warn( + "The OpenAPI specification {} has problems, and the parser ignores the parts it cannot read: {}", + uri, String.join("; ", messages)); + } + return openApi.getOpenAPI(); + } } } catch (Exception e) { throw new IllegalArgumentException( @@ -1038,9 +1048,10 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint { IOHelper.close(is); } - // In theory there should be a message in the parse result, but it has disappeared... throw new IllegalArgumentException( - "Could not deserialize OpenAPI specification from: " + uri); + "Could not deserialize OpenAPI specification from: " + uri + + (messages != null && !messages.isEmpty() + ? " (" + String.join("; ", messages) + ")" : "")); } static String pickBestScheme(final String specificationScheme, final List<String> schemes) { diff --git a/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiSpecificationProblemsTest.java b/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiSpecificationProblemsTest.java new file mode 100644 index 000000000000..f6f2bf679952 --- /dev/null +++ b/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiSpecificationProblemsTest.java @@ -0,0 +1,96 @@ +/* + * 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 java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import io.swagger.v3.oas.models.OpenAPI; +import org.apache.camel.CamelContext; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class RestOpenApiSpecificationProblemsTest { + + private final List<String> warnings = new CopyOnWriteArrayList<>(); + private final AbstractAppender appender + = new AbstractAppender("SpecificationProblems", null, null, true, Property.EMPTY_ARRAY) { + @Override + public void append(LogEvent event) { + if (event.getLevel() == Level.WARN) { + warnings.add(event.getMessage().getFormattedMessage()); + } + } + }; + private final CamelContext camelContext = new DefaultCamelContext(); + + @BeforeEach + void captureWarnings() { + appender.start(); + endpointLogger().addAppender(appender); + // addAppender creates a non-additive logger config, which would hide this logger from the root appenders + endpointLogger().setAdditive(true); + } + + @AfterEach + void stopCapture() { + endpointLogger().removeAppender(appender); + appender.stop(); + } + + @Test + void warnsAboutThePathTheParserIgnores() { + OpenAPI openApi = RestOpenApiEndpoint.loadSpecificationFrom(camelContext, "nested-path.json"); + + // the parser ignores the path nested inside /stock, so there is no getStock operation + assertThat(openApi.getPaths()).containsOnlyKeys("/stock"); + assertThat(warnings).singleElement().asString() + .contains("nested-path.json") + .contains("attribute paths.'/stock'./stock/{sku} is unexpected"); + } + + @Test + void noWarningForValidSpecification() { + RestOpenApiEndpoint.loadSpecificationFrom(camelContext, "openapi-v3.json"); + + assertThat(warnings).isEmpty(); + } + + @Test + void errorSaysWhatTheParserCannotRead() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> RestOpenApiEndpoint.loadSpecificationFrom(camelContext, "openapi.json")); + + // openapi.json is a Swagger 2.0 document, which has no openapi attribute + assertThat(e.getMessage()).contains("openapi.json").contains("attribute openapi is missing"); + } + + private static Logger endpointLogger() { + return (Logger) LogManager.getLogger(RestOpenApiEndpoint.class); + } +} diff --git a/components/camel-rest-openapi/src/test/resources/nested-path.json b/components/camel-rest-openapi/src/test/resources/nested-path.json new file mode 100644 index 000000000000..778e40cc5525 --- /dev/null +++ b/components/camel-rest-openapi/src/test/resources/nested-path.json @@ -0,0 +1,39 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Stock API", + "version": "1.0.0" + }, + "paths": { + "/stock": { + "get": { + "operationId": "listStock", + "responses": { + "200": { + "description": "The stock levels" + } + } + }, + "/stock/{sku}": { + "get": { + "operationId": "getStock", + "parameters": [ + { + "name": "sku", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The stock item" + } + } + } + } + } + } +}
