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 cb609b9d91b1 CAMEL-24844: Add YAML OpenAPI specification support and 
missing verbs to OpenApiVerbs
cb609b9d91b1 is described below

commit cb609b9d91b105f059f750841bd067b9b9eb14bd
Author: Brijesh Thakkar <[email protected]>
AuthorDate: Mon Sep 28 22:53:29 2026 +0530

    CAMEL-24844: Add YAML OpenAPI specification support and missing verbs to 
OpenApiVerbs
    
    OpenAPI rest bindings call direct: endpoints without a body for some
    verbs, and OpenApiVerbs finds those endpoints. It only read JSON
    specifications and skipped YAML ones. It now also parses .yaml and .yml
    specifications (with snakeyaml's SafeConstructor), and treats options and
    trace as bodyless verbs next to get, delete and head. An unreadable or
    unparseable specification is still skipped.
    
    Closes #26914
---
 .../dsl/jbang/core/commands/ai/OpenApiVerbs.java   | 43 +++++++++++++---------
 .../core/commands/ai/OpenApiBodyFlowTest.java      | 34 +++++++++++++++--
 2 files changed, 57 insertions(+), 20 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/OpenApiVerbs.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/OpenApiVerbs.java
index c50f10aee1dd..382900b4e724 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/OpenApiVerbs.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/OpenApiVerbs.java
@@ -24,8 +24,10 @@ import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
+import org.yaml.snakeyaml.LoaderOptions;
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.constructor.SafeConstructor;
 
 /**
  * What a {@code rest: openApi:} binding hides: which of its operations carry 
a body.
@@ -37,7 +39,7 @@ import org.apache.camel.util.json.Jsoner;
 public final class OpenApiVerbs {
 
     /** The verbs whose request carries no body. */
-    private static final Set<String> WITHOUT_BODY = Set.of("get", "delete", 
"head");
+    private static final Set<String> WITHOUT_BODY = Set.of("get", "delete", 
"head", "options", "trace");
 
     private static final Pattern SPECIFICATION = Pattern.compile(
             
"openApi:\\s*\\n\\s*(?:[a-zA-Z]+:[^\\n]*\\n\\s*)*?specification:\\s*[\"']?([^\"'\\s]+)[\"']?");
@@ -62,34 +64,41 @@ public final class OpenApiVerbs {
                 continue;
             }
             try {
-                String text = Files.readString(spec);
-                if (!text.stripLeading().startsWith("{")) {
-                    continue; // a YAML specification: not read here
-                }
-                JsonObject root = (JsonObject) Jsoner.deserialize(text);
-                JsonObject paths = root.getMap("paths");
+                Map<?, ?> paths = paths(Files.readString(spec));
                 if (paths == null) {
                     continue;
                 }
-                for (Map.Entry<String, Object> path : paths.entrySet()) {
-                    if (!(path.getValue() instanceof Map<?, ?> operations)) {
+                for (Object path : paths.values()) {
+                    if (!(path instanceof Map<?, ?> operations)) {
                         continue;
                     }
                     for (Map.Entry<?, ?> operation : operations.entrySet()) {
                         String verb = 
String.valueOf(operation.getKey()).toLowerCase(java.util.Locale.ROOT);
-                        if (!WITHOUT_BODY.contains(verb) || 
!(operation.getValue() instanceof Map<?, ?> details)) {
-                            continue;
-                        }
-                        Object id = details.get("operationId");
-                        if (id != null) {
-                            answer.add("direct:" + id);
+                        if (WITHOUT_BODY.contains(verb) && 
operation.getValue() instanceof Map<?, ?> details
+                                && details.get("operationId") != null) {
+                            answer.add("direct:" + details.get("operationId"));
                         }
                     }
                 }
             } catch (Exception e) {
-                // an unreadable or unparseable specification says nothing
+                // Catching Exception handles unreadable and unparseable JSON 
or YAML specifications.
             }
         }
         return answer;
     }
+
+    private static Map<?, ?> paths(String text) {
+        try {
+            Object document;
+            if (text.stripLeading().startsWith("{")) {
+                document = Jsoner.deserialize(text);
+            } else {
+                document = new Yaml(new SafeConstructor(new 
LoaderOptions())).load(text);
+            }
+            return document instanceof Map<?, ?> root && root.get("paths") 
instanceof Map<?, ?> paths ? paths : null;
+        } catch (Exception e) {
+            // Ignore unreadable or unparseable JSON or YAML specifications.
+            return null;
+        }
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/OpenApiBodyFlowTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/OpenApiBodyFlowTest.java
index e9d4fb1b39da..751aa29b2db1 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/OpenApiBodyFlowTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/OpenApiBodyFlowTest.java
@@ -29,8 +29,7 @@ import org.junit.jupiter.api.io.TempDir;
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
- * CAMEL-24844 phase B: the verb of a rest-openapi operation is in the 
specification beside the route, so a GET that
- * carries no body can be read from it - and a route it reaches that needs a 
body can be reported.
+ * Unit tests for OpenAPI specification parsing and path parameter analysis.
  */
 class OpenApiBodyFlowTest {
 
@@ -39,7 +38,13 @@ class OpenApiBodyFlowTest {
               "openapi": "3.0.2",
               "paths": {
                 "/stock/{sku}": {
-                  "get": { "operationId": "getStock", "responses": { "200": { 
"description": "ok" } } }
+                  "get": {
+                    "operationId": "getStock",
+                    "parameters": [
+                      { "name": "sku", "in": "path", "required": true }
+                    ],
+                    "responses": { "200": { "description": "ok" } }
+                  }
                 },
                 "/orders": {
                   "post": { "operationId": "createOrder", "responses": { 
"201": { "description": "created" } } }
@@ -48,6 +53,21 @@ class OpenApiBodyFlowTest {
             }
             """;
 
+    private static final String YAML_SPEC = """
+            openapi: 3.0.2
+            paths:
+              /stock/{sku}:
+                get:
+                  operationId: getStock
+                  parameters:
+                    - name: sku
+                      in: path
+                      required: true
+                  responses:
+                    '200':
+                      description: ok
+            """;
+
     private static final String ROUTES = """
             - rest:
                 openApi:
@@ -110,4 +130,12 @@ class OpenApiBodyFlowTest {
         assertThat(SourceValidator.validate("routes.camel.yaml", ROUTES, new 
DefaultCamelCatalog(), null, dir))
                 .noneMatch(m -> m.contains("reads the message body"));
     }
+
+    @Test
+    void yamlOpenApiSpecificationIsRead(@TempDir Path dir) throws Exception {
+        Files.writeString(dir.resolve("stock-api.yaml"), YAML_SPEC);
+
+        
assertThat(OpenApiVerbs.bodylessEndpoints(ROUTES.replace("stock-api.json", 
"stock-api.yaml"), dir))
+                .containsExactly("direct:getStock");
+    }
 }

Reply via email to