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

davsclaus pushed a commit to branch fix/CAMEL-25264
in repository https://gitbox.apache.org/repos/asf/camel.git

commit cd7ed8041468547c77bf88e3c43f8b67b050eee4
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 2 12:51:51 2026 +0200

    CAMEL-25264: docs, camel-jbang - samples for the HTTP rungs: REST status 
codes, JsonPath YAML, calling a rest-openapi operation, the call-service 
intent; the validator accepts Simple inlined in a JsonPath
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel/catalog/docs/jsonpath-language.adoc      | 155 +++++++++++++++++++++
 .../camel/catalog/docs/rest-openapi-component.adoc | 102 ++++++++++++++
 .../src/main/docs/jsonpath-language.adoc           | 155 +++++++++++++++++++++
 .../src/main/docs/rest-openapi-component.adoc      | 102 ++++++++++++++
 docs/user-manual/modules/ROOT/pages/rest-dsl.adoc  | 118 ++++++++++++++--
 .../dsl/jbang/core/commands/ai/eip-samples.json    |  16 ++-
 .../dsl/jbang/core/commands/ai/CatalogSamples.java |  26 +++-
 .../jbang/core/commands/ai/CatalogSamplesTest.java |  18 +++
 .../camel/dsl/yaml/validator/YamlValidator.java    |  18 ++-
 .../validator/YamlValidatorPropertyHintTest.java   |  40 ++++++
 10 files changed, 731 insertions(+), 19 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jsonpath-language.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jsonpath-language.adoc
index cf5977c22a3e..24309d4a6c97 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jsonpath-language.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jsonpath-language.adoc
@@ -112,6 +112,33 @@ XML DSL::
 </route>
 ----
 
+YAML::
++
+[source,yaml]
+----
+- route:
+    from:
+      uri: direct:start
+      steps:
+        - choice:
+            when:
+              - expression:
+                  jsonpath:
+                    expression: "$.store.book[?(@.price < 10)]"
+                steps:
+                  - to:
+                      uri: mock:cheap
+              - expression:
+                  jsonpath:
+                    expression: "$.store.book[?(@.price < 30)]"
+                steps:
+                  - to:
+                      uri: mock:average
+            otherwise:
+              steps:
+                - to:
+                    uri: mock:expensive
+----
 ====
 
 ===  Supported message body types
@@ -227,6 +254,134 @@ XML DSL::
 </route>
 ----
 
+YAML::
++
+[source,yaml]
+----
+- route:
+    from:
+      uri: direct:start
+      steps:
+        - choice:
+            when:
+              - expression:
+                  jsonpath:
+                    expression: "$.store.book[?(@.price < ${header.cheap})]"
+                steps:
+                  - to:
+                      uri: mock:cheap
+              - expression:
+                  jsonpath:
+                    expression: "$.store.book[?(@.price < ${header.average})]"
+                steps:
+                  - to:
+                      uri: mock:average
+            otherwise:
+              steps:
+                - to:
+                    uri: mock:expensive
+----
+====
+
+The inlined Simple expressions also work when the result becomes the message 
body, for example to filter a JSON
+array down to the elements matching a header, such as the path parameter of a 
rest service. A filter returns a
+list (empty when nothing matches), so set `resultType` to `java.util.List` and 
check its size. The element found is
+a `Map`, which is marshalled back to JSON:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("direct:one-sku")
+    .setBody(constant("resource:file:stock.json"))
+    .setBody().jsonpath("$[?(@.sku == '${header.sku}')]", List.class)
+    .choice()
+        .when(simple("${body.size()} == 0"))
+            .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(404))
+            .setBody(simple("unknown SKU ${header.sku}"))
+        .otherwise()
+            .setBody(simple("${body[0]}"))
+            .marshal().json()
+    .end();
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+  <from uri="direct:one-sku"/>
+  <setBody>
+    <constant>resource:file:stock.json</constant>
+  </setBody>
+  <setBody>
+    <jsonpath resultType="java.util.List">$[?(@.sku == 
'${header.sku}')]</jsonpath>
+  </setBody>
+  <choice>
+    <when>
+      <simple>${body.size()} == 0</simple>
+      <setHeader name="CamelHttpResponseCode">
+        <constant>404</constant>
+      </setHeader>
+      <setBody>
+        <simple>unknown SKU ${header.sku}</simple>
+      </setBody>
+    </when>
+    <otherwise>
+      <setBody>
+        <simple>${body[0]}</simple>
+      </setBody>
+      <marshal>
+        <json/>
+      </marshal>
+    </otherwise>
+  </choice>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+    from:
+      uri: direct:one-sku
+      steps:
+        - setBody:
+            expression:
+              constant:
+                expression: resource:file:stock.json
+        - setBody:
+            expression:
+              jsonpath:
+                expression: "$[?(@.sku == '${header.sku}')]"
+                resultType: java.util.List
+        - choice:
+            when:
+              - expression:
+                  simple:
+                    expression: "${body.size()} == 0"
+                steps:
+                  - setHeader:
+                      name: CamelHttpResponseCode
+                      expression:
+                        constant:
+                          expression: "404"
+                  - setBody:
+                      expression:
+                        simple:
+                          expression: "unknown SKU ${header.sku}"
+            otherwise:
+              steps:
+                - setBody:
+                    expression:
+                      simple:
+                        expression: "${body[0]}"
+                - marshal:
+                    json: {}
+----
 ====
 
 You can turn off support for inlined Simple expression by setting the option 
`allowSimple` to `false` as shown:
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 1e845c121933..1b937e01f745 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
@@ -157,6 +157,108 @@ YAML::
 ----
 ====
 
+=== Calling an operation of an API
+
+To call an API by its contract, send to a `rest-openapi` endpoint with the 
contract and the _operation id_. Camel
+builds the HTTP request from the contract:
+
+* a path or query parameter, such as `{petId}` in `/pet/{petId}`, is taken 
from the message header of the same name;
+* the message body is the request body (a JSON string for a JSON API, with a 
`Content-Type` header);
+* the response body becomes the message body, as text: unmarshal it to read 
its fields.
+
+The server comes from the `servers` of the contract; set `host` to call 
another one (for example
+`http://localhost:8080` while developing). The HTTP call itself is done by an 
HTTP component on the classpath, such
+as `camel-http`; set `componentName` when there is more than one.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("timer:pets?repeatCount=1")
+    .setHeader("petId", constant(10))
+    .to("rest-openapi:petstore-v3.json#getPetById")
+    .unmarshal().json()
+    .log("Pet ${body[name]}")
+    .setBody(constant("{\"id\": 11, \"name\": \"Rex\"}"))
+    .setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
+    .to("rest-openapi:petstore-v3.json#addPet?host={{petstore.url}}")
+    .log("Added ${body}");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+  <from uri="timer:pets?repeatCount=1"/>
+  <setHeader name="petId">
+    <constant>10</constant>
+  </setHeader>
+  <to uri="rest-openapi:petstore-v3.json#getPetById"/>
+  <unmarshal>
+    <json/>
+  </unmarshal>
+  <log message="Pet ${body[name]}"/>
+  <setBody>
+    <constant>{"id": 11, "name": "Rex"}</constant>
+  </setBody>
+  <setHeader name="Content-Type">
+    <constant>application/json</constant>
+  </setHeader>
+  <to uri="rest-openapi:petstore-v3.json#addPet?host={{petstore.url}}"/>
+  <log message="Added ${body}"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+    from:
+      uri: timer:pets
+      parameters:
+        repeatCount: 1
+      steps:
+        - setHeader:
+            name: petId
+            expression:
+              constant:
+                expression: "10"
+        - to:
+            uri: rest-openapi
+            parameters:
+              specificationUri: petstore-v3.json
+              operationId: getPetById
+        - unmarshal:
+            json: {}
+        - log:
+            message: "Pet ${body[name]}"
+        - setBody:
+            expression:
+              constant:
+                expression: '{"id": 11, "name": "Rex"}'
+        - setHeader:
+            name: Content-Type
+            expression:
+              constant:
+                expression: application/json
+        - to:
+            uri: rest-openapi
+            parameters:
+              specificationUri: petstore-v3.json
+              operationId: addPet
+              host: "{{petstore.url}}"
+        - log:
+            message: "Added ${body}"
+----
+====
+
+An answer with an error status (a `404` or a `409`) fails the exchange with an 
`HttpOperationFailedException`; handle
+it with `onException` to read `${exception.statusCode}` and 
`${exception.responseBody}`.
+
 === 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-jsonpath/src/main/docs/jsonpath-language.adoc 
b/components/camel-jsonpath/src/main/docs/jsonpath-language.adoc
index cf5977c22a3e..24309d4a6c97 100644
--- a/components/camel-jsonpath/src/main/docs/jsonpath-language.adoc
+++ b/components/camel-jsonpath/src/main/docs/jsonpath-language.adoc
@@ -112,6 +112,33 @@ XML DSL::
 </route>
 ----
 
+YAML::
++
+[source,yaml]
+----
+- route:
+    from:
+      uri: direct:start
+      steps:
+        - choice:
+            when:
+              - expression:
+                  jsonpath:
+                    expression: "$.store.book[?(@.price < 10)]"
+                steps:
+                  - to:
+                      uri: mock:cheap
+              - expression:
+                  jsonpath:
+                    expression: "$.store.book[?(@.price < 30)]"
+                steps:
+                  - to:
+                      uri: mock:average
+            otherwise:
+              steps:
+                - to:
+                    uri: mock:expensive
+----
 ====
 
 ===  Supported message body types
@@ -227,6 +254,134 @@ XML DSL::
 </route>
 ----
 
+YAML::
++
+[source,yaml]
+----
+- route:
+    from:
+      uri: direct:start
+      steps:
+        - choice:
+            when:
+              - expression:
+                  jsonpath:
+                    expression: "$.store.book[?(@.price < ${header.cheap})]"
+                steps:
+                  - to:
+                      uri: mock:cheap
+              - expression:
+                  jsonpath:
+                    expression: "$.store.book[?(@.price < ${header.average})]"
+                steps:
+                  - to:
+                      uri: mock:average
+            otherwise:
+              steps:
+                - to:
+                    uri: mock:expensive
+----
+====
+
+The inlined Simple expressions also work when the result becomes the message 
body, for example to filter a JSON
+array down to the elements matching a header, such as the path parameter of a 
rest service. A filter returns a
+list (empty when nothing matches), so set `resultType` to `java.util.List` and 
check its size. The element found is
+a `Map`, which is marshalled back to JSON:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("direct:one-sku")
+    .setBody(constant("resource:file:stock.json"))
+    .setBody().jsonpath("$[?(@.sku == '${header.sku}')]", List.class)
+    .choice()
+        .when(simple("${body.size()} == 0"))
+            .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(404))
+            .setBody(simple("unknown SKU ${header.sku}"))
+        .otherwise()
+            .setBody(simple("${body[0]}"))
+            .marshal().json()
+    .end();
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+  <from uri="direct:one-sku"/>
+  <setBody>
+    <constant>resource:file:stock.json</constant>
+  </setBody>
+  <setBody>
+    <jsonpath resultType="java.util.List">$[?(@.sku == 
'${header.sku}')]</jsonpath>
+  </setBody>
+  <choice>
+    <when>
+      <simple>${body.size()} == 0</simple>
+      <setHeader name="CamelHttpResponseCode">
+        <constant>404</constant>
+      </setHeader>
+      <setBody>
+        <simple>unknown SKU ${header.sku}</simple>
+      </setBody>
+    </when>
+    <otherwise>
+      <setBody>
+        <simple>${body[0]}</simple>
+      </setBody>
+      <marshal>
+        <json/>
+      </marshal>
+    </otherwise>
+  </choice>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+    from:
+      uri: direct:one-sku
+      steps:
+        - setBody:
+            expression:
+              constant:
+                expression: resource:file:stock.json
+        - setBody:
+            expression:
+              jsonpath:
+                expression: "$[?(@.sku == '${header.sku}')]"
+                resultType: java.util.List
+        - choice:
+            when:
+              - expression:
+                  simple:
+                    expression: "${body.size()} == 0"
+                steps:
+                  - setHeader:
+                      name: CamelHttpResponseCode
+                      expression:
+                        constant:
+                          expression: "404"
+                  - setBody:
+                      expression:
+                        simple:
+                          expression: "unknown SKU ${header.sku}"
+            otherwise:
+              steps:
+                - setBody:
+                    expression:
+                      simple:
+                        expression: "${body[0]}"
+                - marshal:
+                    json: {}
+----
 ====
 
 You can turn off support for inlined Simple expression by setting the option 
`allowSimple` to `false` as shown:
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 1e845c121933..1b937e01f745 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
@@ -157,6 +157,108 @@ YAML::
 ----
 ====
 
+=== Calling an operation of an API
+
+To call an API by its contract, send to a `rest-openapi` endpoint with the 
contract and the _operation id_. Camel
+builds the HTTP request from the contract:
+
+* a path or query parameter, such as `{petId}` in `/pet/{petId}`, is taken 
from the message header of the same name;
+* the message body is the request body (a JSON string for a JSON API, with a 
`Content-Type` header);
+* the response body becomes the message body, as text: unmarshal it to read 
its fields.
+
+The server comes from the `servers` of the contract; set `host` to call 
another one (for example
+`http://localhost:8080` while developing). The HTTP call itself is done by an 
HTTP component on the classpath, such
+as `camel-http`; set `componentName` when there is more than one.
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("timer:pets?repeatCount=1")
+    .setHeader("petId", constant(10))
+    .to("rest-openapi:petstore-v3.json#getPetById")
+    .unmarshal().json()
+    .log("Pet ${body[name]}")
+    .setBody(constant("{\"id\": 11, \"name\": \"Rex\"}"))
+    .setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
+    .to("rest-openapi:petstore-v3.json#addPet?host={{petstore.url}}")
+    .log("Added ${body}");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+  <from uri="timer:pets?repeatCount=1"/>
+  <setHeader name="petId">
+    <constant>10</constant>
+  </setHeader>
+  <to uri="rest-openapi:petstore-v3.json#getPetById"/>
+  <unmarshal>
+    <json/>
+  </unmarshal>
+  <log message="Pet ${body[name]}"/>
+  <setBody>
+    <constant>{"id": 11, "name": "Rex"}</constant>
+  </setBody>
+  <setHeader name="Content-Type">
+    <constant>application/json</constant>
+  </setHeader>
+  <to uri="rest-openapi:petstore-v3.json#addPet?host={{petstore.url}}"/>
+  <log message="Added ${body}"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+    from:
+      uri: timer:pets
+      parameters:
+        repeatCount: 1
+      steps:
+        - setHeader:
+            name: petId
+            expression:
+              constant:
+                expression: "10"
+        - to:
+            uri: rest-openapi
+            parameters:
+              specificationUri: petstore-v3.json
+              operationId: getPetById
+        - unmarshal:
+            json: {}
+        - log:
+            message: "Pet ${body[name]}"
+        - setBody:
+            expression:
+              constant:
+                expression: '{"id": 11, "name": "Rex"}'
+        - setHeader:
+            name: Content-Type
+            expression:
+              constant:
+                expression: application/json
+        - to:
+            uri: rest-openapi
+            parameters:
+              specificationUri: petstore-v3.json
+              operationId: addPet
+              host: "{{petstore.url}}"
+        - log:
+            message: "Added ${body}"
+----
+====
+
+An answer with an error status (a `404` or a `409`) fails the exchange with an 
`HttpOperationFailedException`; handle
+it with `onException` to read `${exception.statusCode}` and 
`${exception.responseBody}`.
+
 === Consumer OAuth Bearer token validation
 
 When `rest-openapi` is used as a consumer, HTTP requests are served by the 
selected
diff --git a/docs/user-manual/modules/ROOT/pages/rest-dsl.adoc 
b/docs/user-manual/modules/ROOT/pages/rest-dsl.adoc
index cf037a63c6aa..831d55dc3e2c 100644
--- a/docs/user-manual/modules/ROOT/pages/rest-dsl.adoc
+++ b/docs/user-manual/modules/ROOT/pages/rest-dsl.adoc
@@ -142,6 +142,103 @@ define the services as shown below:
 ----
 ====
 
+== Returning an HTTP status code
+
+A rest service answers with the HTTP status `200` by default (or `204` when 
there is no body). To answer with
+another status, such as a `404` for something that is not found or a `409` for 
a conflict, set the
+`CamelHttpResponseCode` header (`Exchange.HTTP_RESPONSE_CODE` in Java) in the 
route the service calls; the message
+body becomes the response body. A path parameter such as `{sku}` arrives as a 
header of the same name:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+rest("/stock")
+    .get("/{sku}").to("direct:one-sku");
+
+from("direct:one-sku")
+    .choice()
+        .when(simple("${header.sku} == 'CAMEL-MUG'"))
+            .setBody(constant("{\"sku\": \"CAMEL-MUG\", \"qty\": 42}"))
+        .otherwise()
+            .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(404))
+            .setBody(simple("{\"error\": \"unknown SKU ${header.sku}\"}"))
+    .end();
+----
+
+XML::
++
+[source,xml]
+----
+<rest path="/stock">
+  <get path="/{sku}">
+    <to uri="direct:one-sku"/>
+  </get>
+</rest>
+
+<route>
+  <from uri="direct:one-sku"/>
+  <choice>
+    <when>
+      <simple>${header.sku} == 'CAMEL-MUG'</simple>
+      <setBody>
+        <constant>{"sku": "CAMEL-MUG", "qty": 42}</constant>
+      </setBody>
+    </when>
+    <otherwise>
+      <setHeader name="CamelHttpResponseCode">
+        <constant>404</constant>
+      </setHeader>
+      <setBody>
+        <simple>{"error": "unknown SKU ${header.sku}"}</simple>
+      </setBody>
+    </otherwise>
+  </choice>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- rest:
+    path: /stock
+    get:
+      - path: "/{sku}"
+        to: direct:one-sku
+- route:
+    from:
+      uri: direct:one-sku
+      steps:
+        - choice:
+            when:
+              - expression:
+                  simple:
+                    expression: "${header.sku} == 'CAMEL-MUG'"
+                steps:
+                  - setBody:
+                      expression:
+                        constant:
+                          expression: '{"sku": "CAMEL-MUG", "qty": 42}'
+            otherwise:
+              steps:
+                - setHeader:
+                    name: CamelHttpResponseCode
+                    expression:
+                      constant:
+                        expression: "404"
+                - setBody:
+                    expression:
+                      simple:
+                        expression: '{"error": "unknown SKU ${header.sku}"}'
+----
+====
+
+When the route throws an exception that is not handled, the client gets a 
`500`. To answer with a specific
+status for an exception, handle it in `onException` and set 
`CamelHttpResponseCode` there.
+
 == Using a base path
 
 The REST DSL allows defining a base path to help applying the _"don't repeat 
yourself"_ (DRY) practice.
@@ -186,9 +283,9 @@ YAML::
 - rest:
     path: "/customers/"
     get:
-      - path: "/{id}}"
+      - path: "/{id}"
         to: "direct:customerDetails"
-      - path: "/{id}/orders}"
+      - path: "/{id}/orders"
         to: "direct:customerOrders"
     post:
       - path: "/neworder"
@@ -241,9 +338,9 @@ YAML::
 ----
 - rest:
     get:
-      - path: "/customers/{id}}"
+      - path: "/customers/{id}"
         to: "direct:customerDetails"
-      - path: "/customers/{id}/orders}"
+      - path: "/customers/{id}/orders"
         to: "direct:customerOrders"
     post:
       - path: "/customers/neworder"
@@ -290,7 +387,6 @@ YAML::
 ----
 ====
 
-
 == Managing Rest services
 
 Each of the rest services becomes a Camel route, so in the first example,
@@ -376,9 +472,9 @@ YAML::
     inlineRoutes: true
 - rest:
     get:
-      - path: "/customers/{id}}"
+      - path: "/customers/{id}"
         to: "direct:customerDetails"
-      - path: "/customers/{id}/orders}"
+      - path: "/customers/{id}/orders"
         to: "direct:customerOrders"
     post:
       - path: "/customers/neworder"
@@ -502,9 +598,9 @@ YAML::
 - rest:
     path: "/customers/"
     get:
-      - path: "/{id}}"
+      - path: "/{id}"
         to: "direct:customerDetails"
-      - path: "/{id}/orders}"
+      - path: "/{id}/orders"
         to: "direct:customerOrders"
     post:
       - path: "/neworder"
@@ -574,9 +670,9 @@ YAML::
 ----
 - rest:
     get:
-      - path: "/customers/{id}}"
+      - path: "/customers/{id}"
         to: "direct:customerDetails"
-      - path: "/customers/{id}/orders}"
+      - path: "/customers/{id}/orders"
         to: "direct:customerOrders"
         disabled: "{{ordersEnabled}}"
     post:
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 6c0cb0b6dd20..b2ba3ff34354 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
@@ -972,7 +972,7 @@
     },
     {
       "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"
+      "yaml": "- rest:\n    path: \/stock\n    get:\n      - path: 
\"\/{sku}\"\n        to: direct:one-sku\n- route:\n    from:\n      uri: 
direct:one-sku\n      steps:\n        - choice:\n            when:\n            
  - expression:\n                  simple:\n                    expression: 
\"${header.sku} == 'CAMEL-MUG'\"\n                steps:\n                  - 
setBody:\n                      expression:\n                        
constant:\n                          expression:  [...]
     },
     {
       "source": "rest-dsl-openapi.adoc",
@@ -980,7 +980,7 @@
     },
     {
       "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"
+      "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",
@@ -988,7 +988,7 @@
     },
     {
       "source": "rest-dsl.adoc",
-      "yaml": "- rest:\n    path: \"items\/\"\n    get:\n      - path: 
\"{id}\/{filename}.{content-type}\"\n        to: \"direct:item\"\n"
+      "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",
@@ -996,7 +996,11 @@
     },
     {
       "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"
+      "yaml": "- rest:\n    path: \"items\/\"\n    get:\n      - path: 
\"{id}\/{filename}.{content-type}\"\n        to: \"direct:item\"\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"
     },
     {
       "source": "rest-dsl.adoc",
@@ -1004,11 +1008,11 @@
     },
     {
       "source": "rest-dsl.adoc",
-      "yaml": "- restConfiguration:\n    inlineRoutes: true\n- 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- route:\n    from:\n      uri: 
direct:customerNewOrder\n      steps:\n        - to:\n            uri: 
direct:commonCustomerNewOrder\n- route:\n    from:\n      [...]
+      "yaml": "- restConfiguration:\n    inlineRoutes: true\n- 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- route:\n    from:\n      uri: 
direct:customerNewOrder\n      steps:\n        - to:\n            uri: 
direct:commonCustomerNewOrder\n- route:\n    from:\n      u [...]
     },
     {
       "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        disabled: \"{{ordersEnabled}}\"\n 
   post:\n      - path: \"\/customers\/neworder\"\n        to: 
\"direct:customerNewOrder\"\n        disabled: \"true\"\n"
+      "yaml": "- rest:\n    get:\n      - path: \"\/customers\/{id}\"\n        
to: \"direct:customerDetails\"\n      - path: \"\/customers\/{id}\/orders\"\n   
     to: \"direct:customerOrders\"\n        disabled: \"{{ordersEnabled}}\"\n   
 post:\n      - path: \"\/customers\/neworder\"\n        to: 
\"direct:customerNewOrder\"\n        disabled: \"true\"\n"
     }
   ],
   "returnAddress": [
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamples.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamples.java
index bd1b71e6e402..42f9856ca1e5 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamples.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamples.java
@@ -99,11 +99,17 @@ public final class CatalogSamples {
      * is here: a task (read file, call service), a wording of the outcome 
(retry, batch) or a technology (json, cron).
      * A protocol or a product (mqtt, s3) is what camel_catalog_find turns 
into a component, so it is not here.
      */
+    private static final String[] HTTP_CALL = {
+            "http", "a to: with the http component (toD: when the URI is built 
from the message, such as a path"
+                    + " parameter); the response becomes the message body" };
+    private static final String[] OPENAPI_CALL = {
+            "rest-openapi", "a to: with the rest-openapi component, its 
specificationUri and operationId; path and"
+                            + " query parameters come from headers of the same 
name, the body is the request body" };
+
     static final Map<String, String> INTENTS = Map.ofEntries(
             Map.entry("read file", "poll"), Map.entry("readfile", "poll"), 
Map.entry("load file", "poll"),
             Map.entry("read a file", "poll"), Map.entry("read", "poll"), 
Map.entry("fetch", "poll"),
             Map.entry("consume once", "poll"), Map.entry("poll once", "poll"),
-            Map.entry("call service", "enrich"), Map.entry("call", "enrich"), 
Map.entry("http call", "enrich"),
             Map.entry("lookup", "enrich"),
             Map.entry("batch", "aggregate"), Map.entry("collect", 
"aggregate"), Map.entry("group", "aggregate"),
             Map.entry("retry", "onException"), Map.entry("error handling", 
"onException"),
@@ -116,6 +122,17 @@ public final class CatalogSamples {
             Map.entry("convert", "convertBodyTo"), Map.entry("json", 
"marshal"),
             Map.entry("schedule", "from"), Map.entry("cron", "from"));
 
+    /**
+     * What a request is about, to the component that does it, with how. 
Calling a service is a to: (or a toD: when the
+     * URI is built from the message) on the http component, not the enrich 
EIP, which also merges the answer into the
+     * message with an aggregation strategy.
+     */
+    static final Map<String, String[]> COMPONENT_INTENTS = Map.ofEntries(
+            Map.entry("call service", HTTP_CALL), Map.entry("call", 
HTTP_CALL), Map.entry("http call", HTTP_CALL),
+            Map.entry("call api", HTTP_CALL), Map.entry("http client", 
HTTP_CALL), Map.entry("call http", HTTP_CALL),
+            Map.entry("openapi client", OPENAPI_CALL), Map.entry("call by 
contract", OPENAPI_CALL),
+            Map.entry("call operation", OPENAPI_CALL));
+
     private static volatile Map<String, List<Map<String, String>>> samples;
 
     private CatalogSamples() {
@@ -408,6 +425,13 @@ public final class CatalogSamples {
         int max = Math.max(1, Math.min(MAX_LIMIT, limit <= 0 ? DEFAULT_LIMIT : 
limit));
         boolean eips = wanted == null || wanted.equals("eip");
 
+        // what to do that a component does (call service)
+        String[] componentIntent = 
COMPONENT_INTENTS.get(given.toLowerCase(Locale.ROOT));
+        if (componentIntent != null && catalog != null && (wanted == null || 
wanted.equals("component"))) {
+            JsonObject found = catalogAnswer(catalog, "component", 
componentIntent[0], max);
+            found.put("note", "'" + given + "' is done with " + 
componentIntent[1]);
+            return found;
+        }
         // an EIP or a file entry by its name, a part of it, or what to do
         if (eips) {
             String key = resolveExact(given);
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 6a10cc9d54e2..2c464cf51140 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
@@ -112,6 +112,24 @@ public class CatalogSamplesTest {
         assertThat(CatalogSamples.sample(catalog, "lookup", 
1).getString("name")).isEqualTo("enrich");
     }
 
+    @Test
+    void callingAServiceIsTheHttpComponentNotEnrich() {
+        CamelCatalog catalog = new DefaultCamelCatalog();
+        for (String term : List.of("call service", "http call", "Call API")) {
+            JsonObject o = CatalogSamples.sample(catalog, term, 2);
+            assertThat(o.getString("name")).as(term).isEqualTo("http");
+            assertThat(o.getString("note")).as(term).contains("to:", "toD:");
+            assertThat(((JsonObject) ((JsonArray) 
o.get("samples")).get(0)).getString("yaml")).contains("uri: http");
+        }
+        // calling an API by its contract: the rest-openapi producer
+        JsonObject o = CatalogSamples.sample(catalog, "openapi client", 5);
+        assertThat(o.getString("name")).isEqualTo("rest-openapi");
+        assertThat(((JsonArray) o.get("samples")).stream().map(s -> 
((JsonObject) s).getString("yaml")))
+                .anyMatch(y -> y.contains("operationId: getPetById"));
+        // asked for an EIP: no component
+        assertThat(CatalogSamples.sample(catalog, "eip", "call service", 
1).getString("name")).isNotEqualTo("http");
+    }
+
     @Test
     void unknownNameGetsSuggestions() {
         JsonObject o = CatalogSamples.sample("aggregat", 2);
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java
index 876015336b5a..85c19f190020 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java
@@ -809,7 +809,8 @@ public class YamlValidator {
             }
             String outsideQuotes
                     = text != null ? 
text.replaceAll("\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'", "\"\"") : 
null;
-            if (outsideQuotes != null && outsideQuotes.contains("${") && 
!name.equals("js") && !name.equals("quickjs")) {
+            if (outsideQuotes != null && outsideQuotes.contains("${") && 
!name.equals("js") && !name.equals("quickjs")
+                    && !jsonPathWithInlineSimple(name, value, outsideQuotes)) {
                 // "${x}" inside quotes is a groovy GString (or a JS template) 
and is fine; ${...} outside quotes is simple
                 String example;
                 if (name.equals("groovy") || name.equals("mvel") || 
name.equals("ognl") || name.equals("jactl")) {
@@ -835,6 +836,21 @@ public class YamlValidator {
         }
     }
 
+    /**
+     * A JsonPath with Simple inlined, such as $.store.book[?(@.price < 
${header.max})]: the jsonpath language evaluates
+     * the ${...} first (allowSimple, on by default), so it is valid when the 
expression has a JsonPath root of its own.
+     * A whole ${...} (jsonpath: ${body.x}) has none and is simple syntax.
+     */
+    private static boolean jsonPathWithInlineSimple(String name, JsonNode 
value, String outsideQuotes) {
+        if (!name.equals("jsonpath")) {
+            return false;
+        }
+        if (value.isObject() && value.has("allowSimple") && 
"false".equals(value.get("allowSimple").asText())) {
+            return false;
+        }
+        return outsideQuotes.replaceAll("\\$\\{[^}]*}", "").contains("$");
+    }
+
     /**
      * to: http://host/stock/${header.sku}: the endpoint of a to: is resolved 
once when the route starts, so an
      * expression in its path is never evaluated - it is sent as the text it 
is, url-encoded. That is what toD: is for
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorPropertyHintTest.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorPropertyHintTest.java
index e41a7b1d7e7a..d90380a8a37d 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorPropertyHintTest.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorPropertyHintTest.java
@@ -706,6 +706,46 @@ public class YamlValidatorPropertyHintTest {
                 .contains("or use simple: {expression: \"${body.value} < 
1\"}");
     }
 
+    @Test
+    public void testJsonPathWithInlinedSimpleIsValid() throws Exception {
+        // the jsonpath language evaluates ${...} inside a JsonPath first 
(allowSimple, on by default)
+        List<Error> errors = validator.validate("""
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - setBody:
+                            expression:
+                              jsonpath:
+                                expression: "$.store.book[?(@.price < 
${header.max})]"
+                        - setBody:
+                            expression:
+                              jsonpath:
+                                expression: "$[?(@.sku == '${header.sku}')]"
+                                resultType: java.util.List
+                """);
+        assertThat(errors).isEmpty();
+
+        // a whole ${...} is simple syntax, and so is any ${...} when 
allowSimple is off
+        errors = validator.validate("""
+                - route:
+                    from:
+                      uri: direct:start
+                      steps:
+                        - setBody:
+                            expression:
+                              jsonpath:
+                                expression: "${body.price}"
+                        - setBody:
+                            expression:
+                              jsonpath:
+                                expression: "$.store.book[?(@.price < 
${header.max})]"
+                                allowSimple: false
+                """);
+        assertThat(errors).hasSize(2);
+        assertThat(errors.get(0).getMessage()).startsWith("jsonpath: ${...} is 
simple syntax, not jsonpath");
+    }
+
     @Test
     public void testOnExceptionAsAListSaysMap() throws Exception {
         List<Error> errors = validator.validate("""

Reply via email to