This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch quick-fix/rest-contract-first-samples
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 29fda2350444b6402ae192837c2c4a262c41b451
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 1 21:55:57 2026 +0200

    chore: camel_catalog_sample shows the contract-first rest next to the 
path-based one
    
    camel_catalog_sample rest returned only the path-based examples of
    rest-dsl.adoc; the contract-first form (rest: openApi: specification:)
    on rest-dsl-openapi.adoc was validated by the build but never sampled.
    
    - rest-dsl-openapi.adoc is an entry page of rest, so its "- rest:"
      examples are samples too
    - a name with samples from several pages takes them from each page in
      turn, so the default two show both forms (doTry now shows the
      try-catch-finally page second as well)
    - the rest-openapi component page gets a contract-first section, so
      camel_catalog_sample rest-openapi shows it after the consumer endpoint
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
---
 .../camel/catalog/docs/rest-openapi-component.adoc | 50 ++++++++++++++++++++++
 .../src/main/docs/rest-openapi-component.adoc      | 50 ++++++++++++++++++++++
 dsl/camel-jbang/camel-jbang-core/pom.xml           |  2 +
 .../dsl/jbang/core/commands/ai/eip-samples.json    | 24 +++++++++--
 .../jbang/core/commands/ai/CatalogSamplesTest.java | 12 ++++++
 .../dsl/yaml/validator/GenerateDocSamplesMojo.java | 28 +++++++++++-
 .../yaml/validator/GenerateDocSamplesMojoTest.java | 22 ++++++++++
 7 files changed, 182 insertions(+), 6 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/rest-openapi-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/rest-openapi-component.adoc
index 19e3c4824ff6..1e845c121933 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/rest-openapi-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/rest-openapi-component.adoc
@@ -107,6 +107,56 @@ include::partial$component-endpoint-headers.adoc[]
 
 == Usage
 
+=== Contract-first REST DSL
+
+To serve the operations of an OpenAPI contract, use the Rest DSL in 
_contract-first_ mode rather than a
+`rest-openapi` consumer endpoint. Camel loads the contract at startup and 
routes every operation to the
+route that starts from `direct:` and its _operation id_; see
+xref:manual::rest-dsl-openapi.adoc[REST DSL with contract-first OpenAPI].
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+rest().openApi("petstore-v3.json");
+
+from("direct:getPetById")
+    .setBody(simple("{\"id\": ${header.petId}, \"name\": \"Rex\"}"));
+----
+
+XML::
++
+[source,xml]
+----
+<rest>
+  <openApi specification="petstore-v3.json"/>
+</rest>
+<route>
+  <from uri="direct:getPetById"/>
+  <setBody>
+    <simple>{"id": ${header.petId}, "name": "Rex"}</simple>
+  </setBody>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- rest:
+    openApi:
+      specification: petstore-v3.json
+- route:
+    from:
+      uri: direct:getPetById
+      steps:
+        - setBody:
+            simple: '{"id": ${header.petId}, "name": "Rex"}'
+----
+====
+
 === Consumer OAuth Bearer token validation
 
 When `rest-openapi` is used as a consumer, HTTP requests are served by the 
selected
diff --git 
a/components/camel-rest-openapi/src/main/docs/rest-openapi-component.adoc 
b/components/camel-rest-openapi/src/main/docs/rest-openapi-component.adoc
index 19e3c4824ff6..1e845c121933 100644
--- a/components/camel-rest-openapi/src/main/docs/rest-openapi-component.adoc
+++ b/components/camel-rest-openapi/src/main/docs/rest-openapi-component.adoc
@@ -107,6 +107,56 @@ include::partial$component-endpoint-headers.adoc[]
 
 == Usage
 
+=== Contract-first REST DSL
+
+To serve the operations of an OpenAPI contract, use the Rest DSL in 
_contract-first_ mode rather than a
+`rest-openapi` consumer endpoint. Camel loads the contract at startup and 
routes every operation to the
+route that starts from `direct:` and its _operation id_; see
+xref:manual::rest-dsl-openapi.adoc[REST DSL with contract-first OpenAPI].
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+rest().openApi("petstore-v3.json");
+
+from("direct:getPetById")
+    .setBody(simple("{\"id\": ${header.petId}, \"name\": \"Rex\"}"));
+----
+
+XML::
++
+[source,xml]
+----
+<rest>
+  <openApi specification="petstore-v3.json"/>
+</rest>
+<route>
+  <from uri="direct:getPetById"/>
+  <setBody>
+    <simple>{"id": ${header.petId}, "name": "Rex"}</simple>
+  </setBody>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- rest:
+    openApi:
+      specification: petstore-v3.json
+- route:
+    from:
+      uri: direct:getPetById
+      steps:
+        - setBody:
+            simple: '{"id": ${header.petId}, "name": "Rex"}'
+----
+====
+
 === Consumer OAuth Bearer token validation
 
 When `rest-openapi` is used as a consumer, HTTP requests are served by the 
selected
diff --git a/dsl/camel-jbang/camel-jbang-core/pom.xml 
b/dsl/camel-jbang/camel-jbang-core/pom.xml
index 29fc9e7c400f..6e0765ef7b1a 100644
--- a/dsl/camel-jbang/camel-jbang-core/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-core/pom.xml
@@ -291,6 +291,8 @@
                             </pages>
                             <entryPages>
                                 
<entryPage>../../camel-yaml-dsl/camel-yaml-dsl/src/main/docs/yaml-dsl.adoc=beans</entryPage>
+                                <!-- the contract-first rest, next to the 
path-based examples of rest-dsl -->
+                                
<entryPage>../../../docs/user-manual/modules/ROOT/pages/rest-dsl-openapi.adoc=rest</entryPage>
                             </entryPages>
                             <!-- user manual pages that show old syntax or 
YAML that is not a Camel file -->
                             <excludes>
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json
index 71af218d0aae..6c0cb0b6dd20 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json
@@ -292,14 +292,14 @@
       "source": "doTry-eip.adoc",
       "yaml": "- route:\n    from:\n      uri: direct:start\n      steps:\n    
    - doTry:\n            steps:\n              - process:\n                  
ref: processorFail\n              - to:\n                  uri: mock:result\n   
           - doCatch:\n                  exception:\n                    - 
java.io.IOException\n                    - java.lang.IllegalStateException\n    
              steps:\n                    - to:\n                        uri: 
mock:catch\n           [...]
     },
-    {
-      "source": "doTry-eip.adoc",
-      "yaml": "- route:\n    from:\n      uri: direct:start\n      steps:\n    
    - doTry:\n            steps:\n              - process:\n                  
ref: processorFail\n              - to:\n                  uri: mock:result\n   
           - doCatch:\n                  exception:\n                    - 
java.io.IOException\n                    - java.lang.IllegalStateException\n    
              onWhen:\n                    expression:\n                      
simple:\n              [...]
-    },
     {
       "source": "try-catch-finally.adoc",
       "yaml": "- route:\n    from:\n      uri: direct:start\n      steps:\n    
    - doTry:\n            steps:\n              - process:\n                  
ref: processorFail\n              - to:\n                  uri: mock:result\n   
           - doCatch:\n                  exception:\n                    - 
java.io.IOException\n                    - java.lang.IllegalStateException\n    
              steps:\n                    - to:\n                        uri: 
mock:catch\n           [...]
     },
+    {
+      "source": "doTry-eip.adoc",
+      "yaml": "- route:\n    from:\n      uri: direct:start\n      steps:\n    
    - doTry:\n            steps:\n              - process:\n                  
ref: processorFail\n              - to:\n                  uri: mock:result\n   
           - doCatch:\n                  exception:\n                    - 
java.io.IOException\n                    - java.lang.IllegalStateException\n    
              onWhen:\n                    expression:\n                      
simple:\n              [...]
+    },
     {
       "source": "try-catch-finally.adoc",
       "yaml": "- route:\n    from:\n      uri: direct:start\n      steps:\n    
    - doTry:\n            steps:\n              - process:\n                  
ref: processorFail\n              - to:\n                  uri: mock:result\n   
           - doCatch:\n                  exception:\n                    - 
java.io.IOException\n                    - java.lang.IllegalStateException\n    
              onWhen:\n                    simple: \"${exception.message} 
contains 'Damn'\"\n        [...]
@@ -966,18 +966,34 @@
       "source": "rest-dsl.adoc",
       "yaml": "- rest:\n    path: \"\/say\"\n    get:\n      - path: 
\"\/hello\"\n        to: \"direct:hello\"\n      - path: \"\/bye\"\n        
consumes: \"application\/json\"\n        to: \"direct:bye\"\n    post:\n      - 
path: \"\/bye\"\n        to: \"direct:update\"\n- route:\n    from:\n      uri: 
direct:hello\n      steps:\n        - transform:\n            expression:\n     
         constant:\n                expression: Hello World\n- route:\n    
from:\n      uri: direct:bye\n   [...]
     },
+    {
+      "source": "rest-dsl-openapi.adoc",
+      "yaml": "- rest:\n    openApi:\n      specification: petstore-v3.json\n"
+    },
     {
       "source": "rest-dsl.adoc",
       "yaml": "- rest:\n    path: \"\/customers\/\"\n    get:\n      - path: 
\"\/{id}}\"\n        to: \"direct:customerDetails\"\n      - path: 
\"\/{id}\/orders}\"\n        to: \"direct:customerOrders\"\n    post:\n      - 
path: \"\/neworder\"\n        to: \"direct:customerNewOrder\"\n"
     },
+    {
+      "source": "rest-dsl-openapi.adoc",
+      "yaml": "- rest:\n    openApi:\n      specification: petstore-v3.json\n- 
route:\n    from:\n      uri: direct:getUserByName\n      steps:\n        - 
log:\n            message: \"do something here\"\n"
+    },
     {
       "source": "rest-dsl.adoc",
       "yaml": "- rest:\n    get:\n      - path: \"\/customers\/{id}}\"\n       
 to: \"direct:customerDetails\"\n      - path: \"\/customers\/{id}\/orders}\"\n 
       to: \"direct:customerOrders\"\n    post:\n      - path: 
\"\/customers\/neworder\"\n        to: \"direct:customerNewOrder\"\n"
     },
+    {
+      "source": "rest-dsl-openapi.adoc",
+      "yaml": "- rest:\n    openApi:\n      specification: petstore-v3.json\n  
    missingOperation: ignore\n"
+    },
     {
       "source": "rest-dsl.adoc",
       "yaml": "- rest:\n    path: \"items\/\"\n    get:\n      - path: 
\"{id}\/{filename}.{content-type}\"\n        to: \"direct:item\"\n"
     },
+    {
+      "source": "rest-dsl-openapi.adoc",
+      "yaml": "- rest:\n    openApi:\n      specification: petstore-v3.json\n  
    missingOperation: mock\n"
+    },
     {
       "source": "rest-dsl.adoc",
       "yaml": "- restConfiguration:\n    inlineRoutes: true\n- rest:\n    
get:\n      - path: \"\/customers\/{id}}\"\n        to: 
\"direct:customerDetails\"\n      - path: \"\/customers\/{id}\/orders}\"\n      
  to: \"direct:customerOrders\"\n    post:\n      - path: 
\"\/customers\/neworder\"\n        to: \"direct:customerNewOrder\"\n"
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamplesTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamplesTest.java
index 716eda767050..6a10cc9d54e2 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamplesTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamplesTest.java
@@ -146,6 +146,10 @@ public class CatalogSamplesTest {
         assertThat(((JsonObject) ((JsonArray) CatalogSamples.sample(catalog, 
"rest", 1).get("samples")).get(0))
                 .getString("source"))
                 .isEqualTo("rest-dsl.adoc");
+        // the default two show the path-based and the contract-first rest
+        JsonArray rest = (JsonArray) CatalogSamples.sample(catalog, "rest", 
0).get("samples");
+        assertThat(((JsonObject) 
rest.get(1)).getString("source")).isEqualTo("rest-dsl-openapi.adoc");
+        assertThat(((JsonObject) 
rest.get(1)).getString("yaml")).contains("openApi:");
     }
 
     @Test
@@ -223,6 +227,14 @@ public class CatalogSamplesTest {
         assertThat(CatalogSamples.sample("kafka", 
1).getString("error")).contains("kafka");
     }
 
+    @Test
+    void restOpenApiShowsTheConsumerEndpointAndTheContractFirstRest() {
+        CamelCatalog catalog = new DefaultCamelCatalog();
+        JsonObject o = CatalogSamples.sample(catalog, "component", 
"rest-openapi", 2);
+        assertThat(yaml(o, 0)).contains("rest-openapi:");
+        assertThat(yaml(o, 
1)).contains("openApi:").contains("direct:getPetById");
+    }
+
     @Test
     void theSubPagesOfAComponentBelongToIt() {
         CamelCatalog catalog = new DefaultCamelCatalog();
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/main/java/org/apache/camel/dsl/yaml/validator/GenerateDocSamplesMojo.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/main/java/org/apache/camel/dsl/yaml/validator/GenerateDocSamplesMojo.java
index c31b0054ca63..711819f95e78 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/main/java/org/apache/camel/dsl/yaml/validator/GenerateDocSamplesMojo.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/main/java/org/apache/camel/dsl/yaml/validator/GenerateDocSamplesMojo.java
@@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
@@ -54,8 +55,10 @@ import org.apache.maven.project.MavenProject;
  * ({@code yaml-dsl.adoc} is {@code beans})</li>
  * </ul>
  * A route example is a {@code [source,yaml]} block whose first line starts 
with {@code "- "}; other YAML blocks are
- * fragments and are neither validated nor sampled. The output is a JSON 
object keyed by the sample name, each value a
- * list of {@code {source, yaml}} objects, written only when its content 
changed.
+ * fragments and are neither validated nor sampled. A name with samples from 
several pages takes them from each page in
+ * turn, so the first few (what the AI tools return by default) show every 
page ({@code rest} is path-based from
+ * {@code rest-dsl.adoc} and contract-first from {@code 
rest-dsl-openapi.adoc}). The output is a JSON object keyed by
+ * the sample name, each value a list of {@code {source, yaml}} objects, 
written only when its content changed.
  */
 @Mojo(name = "generate-doc-samples", threadSafe = true)
 public class GenerateDocSamplesMojo extends AbstractMojo {
@@ -223,9 +226,30 @@ public class GenerateDocSamplesMojo extends AbstractMojo {
             }
         }
 
+        samples.replaceAll((key, list) -> interleave(list));
         return samples;
     }
 
+    /** The samples taken from each page in turn, in page order, keeping the 
order of the samples of a page. */
+    static List<Sample> interleave(List<Sample> list) {
+        Map<String, List<Sample>> bySource = new LinkedHashMap<>();
+        for (Sample s : list) {
+            bySource.computeIfAbsent(s.source(), k -> new 
ArrayList<>()).add(s);
+        }
+        if (bySource.size() < 2) {
+            return list;
+        }
+        List<Sample> answer = new ArrayList<>(list.size());
+        for (int i = 0; answer.size() < list.size(); i++) {
+            for (List<Sample> page : bySource.values()) {
+                if (i < page.size()) {
+                    answer.add(page.get(i));
+                }
+            }
+        }
+        return answer;
+    }
+
     private static List<File> pages(File dir) {
         File[] files = dir.listFiles(f -> f.isFile() && 
f.getName().endsWith(".adoc"));
         if (files == null) {
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/test/java/org/apache/camel/dsl/yaml/validator/GenerateDocSamplesMojoTest.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/test/java/org/apache/camel/dsl/yaml/validator/GenerateDocSamplesMojoTest.java
index ea1b6cb4040e..56a4efc3c66e 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/test/java/org/apache/camel/dsl/yaml/validator/GenerateDocSamplesMojoTest.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/test/java/org/apache/camel/dsl/yaml/validator/GenerateDocSamplesMojoTest.java
@@ -126,6 +126,28 @@ class GenerateDocSamplesMojoTest {
         assertEquals(ROUTE, first.getString("yaml"));
     }
 
+    @Test
+    void aNameWithSamplesFromSeveralPagesTakesThemFromEachPageInTurn() throws 
Exception {
+        Path eips = Files.createDirectories(tempDir.resolve("eips"));
+        Path manual = Files.createDirectories(tempDir.resolve("manual"));
+        String path = "- rest:\n    path: /say\n    get:\n      - path: 
/hello\n        to: direct:hello\n";
+        Files.writeString(manual.resolve("rest-dsl.adoc"), block(path) + 
block(path) + block(path));
+        Path openApi = manual.resolve("rest-dsl-openapi.adoc");
+        String contract = "- rest:\n    openApi:\n      specification: 
petstore-v3.json\n";
+        Files.writeString(openApi, block("- restConfiguration:\n    
contextPath: /api\n") + block(contract) + block(contract));
+
+        List<String> failures = new ArrayList<>();
+        Map<String, List<GenerateDocSamplesMojo.Sample>> samples = 
GenerateDocSamplesMojo.generate(
+                VALIDATOR, eips.toFile(), manual.toFile(), Map.of("rest-dsl", 
"rest"),
+                Map.of(openApi.toFile(), "rest"), null, failures);
+
+        assertTrue(failures.isEmpty(), failures.toString());
+        assertEquals(List.of("rest-dsl.adoc", "rest-dsl-openapi.adoc", 
"rest-dsl.adoc", "rest-dsl-openapi.adoc",
+                "rest-dsl.adoc"),
+                
samples.get("rest").stream().map(GenerateDocSamplesMojo.Sample::source).toList());
+        assertEquals(contract, samples.get("rest").get(1).yaml());
+    }
+
     @Test
     void anExampleThatDoesNotValidateIsAFailure() throws Exception {
         Path eips = Files.createDirectories(tempDir.resolve("eips"));

Reply via email to