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"
+            }
+          }
+        }
+      }
+    }
+  }
+}

Reply via email to