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 8287506b6120 CAMEL-25747: camel-rest-openapi - log the problems the
OpenAPI parser finds in the specification (#28043)
8287506b6120 is described below
commit 8287506b61208649d8b49139f709a33c7478c6fe
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 18 18:05:06 2026 +0200
CAMEL-25747: camel-rest-openapi - log the problems the OpenAPI parser finds
in the specification (#28043)
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
.../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"
+ }
+ }
+ }
+ }
+ }
+ }
+}