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 51cfc77abea1 CAMEL-25321: camel-rest-openapi - client request 
validation checks the path parameters too (#27333)
51cfc77abea1 is described below

commit 51cfc77abea19e1a2cba340da5627c2b92766daa
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 4 15:39:25 2026 +0200

    CAMEL-25321: camel-rest-openapi - client request validation checks the path 
parameters too (#27333)
    
    With requestValidationEnabled the producer validated the content type, a
    required body, JSON syntax, required query and header parameters, but not
    the path parameters: a call without a value for {sku} passed and went out
    with the literal {sku} in the path. Every path parameter is required in
    OpenAPI, so each placeholder of the operation's path template (which covers
    a parameter declared on the path item too) now needs a value from a header,
    an exchange variable or an endpoint parameter, else: "Path parameter 'sku'
    is required but none found: set the header sku, or an exchange variable of
    that name, before the call."
    
    
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../camel/catalog/docs/rest-openapi-component.adoc |  4 ++
 .../src/main/docs/rest-openapi-component.adoc      |  4 ++
 .../openapi/validator/DefaultRequestValidator.java | 32 +++++++++++
 .../openapi/RestOpenApiRequestValidationTest.java  | 63 ++++++++++++++++++++++
 4 files changed, 103 insertions(+)

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 010f86b443a4..b7138eab3a7d 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
@@ -350,6 +350,10 @@ expected to be present among the Camel message exchange 
headers.
 * query parameters - Validates whether an HTTP query parameter required by the 
API operation is present. The query parameter is
 expected to be present among the Camel message exchange headers.
 
+* path parameters - Validates that every parameter in the path of the API 
operation, such as `sku` in
+`/stock/\{sku}/reserve`, has a value: a Camel message exchange header or an 
exchange variable of that name. Without
+the check, a missing value is sent as the literal `\{sku}` in the path.
+
 If any of the validation checks fail, then a `RestOpenApiValidationException` 
is thrown. The exception object
 has a `getValidationErrors` method that returns the error messages from the 
validator.
 
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 010f86b443a4..b7138eab3a7d 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
@@ -350,6 +350,10 @@ expected to be present among the Camel message exchange 
headers.
 * query parameters - Validates whether an HTTP query parameter required by the 
API operation is present. The query parameter is
 expected to be present among the Camel message exchange headers.
 
+* path parameters - Validates that every parameter in the path of the API 
operation, such as `sku` in
+`/stock/\{sku}/reserve`, has a value: a Camel message exchange header or an 
exchange variable of that name. Without
+the check, a missing value is sent as the literal `\{sku}` in the path.
+
 If any of the validation checks fail, then a `RestOpenApiValidationException` 
is thrown. The exception object
 has a `getValidationErrors` method that returns the error messages from the 
validator.
 
diff --git 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/validator/DefaultRequestValidator.java
 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/validator/DefaultRequestValidator.java
index 112b951da96d..89c42ea061c5 100644
--- 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/validator/DefaultRequestValidator.java
+++ 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/validator/DefaultRequestValidator.java
@@ -21,6 +21,8 @@ import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.databind.json.JsonMapper;
@@ -34,6 +36,8 @@ import static 
org.apache.camel.support.http.RestUtil.isValidOrAcceptedContentTyp
 
 public class DefaultRequestValidator implements RequestValidator {
 
+    private static final Pattern PATH_PARAMETER = 
Pattern.compile("\\{([^}/]+)}");
+
     private RestOpenApiOperation operation;
     private Map<String, Object> endpointParameters = Collections.emptyMap();
 
@@ -119,6 +123,23 @@ public class DefaultRequestValidator implements 
RequestValidator {
                     }
                 });
 
+        // Validate path parameters: every placeholder of the path is required 
(OpenAPI), and one without a value would
+        // be sent as the literal {name}. Taken from the path template, so a 
parameter declared on the path item counts
+        // too (CAMEL-25321)
+        for (String name : pathParameters(o.getUriTemplate())) {
+            Object value = message.getHeader(name);
+            if (ObjectHelper.isEmpty(value)) {
+                value = exchange.getVariable(name);
+            }
+            if (ObjectHelper.isEmpty(value)) {
+                value = endpointParameters.get(name);
+            }
+            if (ObjectHelper.isEmpty(value)) {
+                validationErrors.add("Path parameter '" + name + "' is 
required but none found: set the header " + name
+                                     + ", or an exchange variable of that 
name, before the call.");
+            }
+        }
+
         // Validate operation required headers
         o.getHeaders()
                 .stream()
@@ -136,4 +157,15 @@ public class DefaultRequestValidator implements 
RequestValidator {
         return Collections.unmodifiableSet(validationErrors);
     }
 
+    /** The names of the placeholders of a path template: {@code 
/stock/{sku}/reserve} gives {@code sku}. */
+    static Set<String> pathParameters(String uriTemplate) {
+        Set<String> names = new LinkedHashSet<>();
+        if (uriTemplate != null) {
+            Matcher m = PATH_PARAMETER.matcher(uriTemplate);
+            while (m.find()) {
+                names.add(m.group(1));
+            }
+        }
+        return names;
+    }
 }
diff --git 
a/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiRequestValidationTest.java
 
b/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiRequestValidationTest.java
index 3a4a2b951c79..23b75c789fb1 100644
--- 
a/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiRequestValidationTest.java
+++ 
b/components/camel-rest-openapi/src/test/java/org/apache/camel/component/rest/openapi/RestOpenApiRequestValidationTest.java
@@ -296,6 +296,47 @@ public class RestOpenApiRequestValidationTest extends 
CamelTestSupport {
         assertEquals("Pet deleted", result);
     }
 
+    @ParameterizedTest
+    @MethodSource("petStoreVersions")
+    void requestValidationWithMissingPathParameter(String petStoreVersion) {
+        // no petId: without the check the request went out as /pet/{petId} 
(CAMEL-25321)
+        Exchange exchange = template.request("direct:validateDelete", new 
Processor() {
+            @Override
+            public void process(Exchange exchange) throws Exception {
+                exchange.getMessage().setHeader("petStoreVersion", 
petStoreVersion);
+                exchange.getMessage().setHeader("api_key", "foo");
+            }
+        });
+
+        Exception exception = exchange.getException();
+        assertNotNull(exception);
+        assertInstanceOf(RestOpenApiValidationException.class, exception);
+        Set<String> errors = ((RestOpenApiValidationException) 
exception).getValidationErrors();
+        assertEquals(1, errors.size());
+        assertEquals("Path parameter 'petId' is required but none found: set 
the header petId, or an exchange variable "
+                     + "of that name, before the call.",
+                errors.iterator().next());
+    }
+
+    @ParameterizedTest
+    @MethodSource("petStoreVersions")
+    void requestValidationWithPathParameterFromVariable(String 
petStoreVersion) {
+        Exchange exchange = template.request("direct:validateDelete", new 
Processor() {
+            @Override
+            public void process(Exchange exchange) throws Exception {
+                exchange.getMessage().setHeader("petStoreVersion", 
petStoreVersion);
+                exchange.getMessage().setHeader("api_key", "foo");
+                exchange.setVariable("petId", 10);
+            }
+        });
+
+        Exception exception = exchange.getException();
+        if (exception != null) {
+            throw new AssertionError("Unexpected validation failure", 
exception);
+        }
+        assertEquals("Pet deleted", 
exchange.getMessage().getBody(String.class));
+    }
+
     @ParameterizedTest
     @MethodSource("petStoreVersions")
     @SuppressWarnings("unchecked")
@@ -364,6 +405,8 @@ public class RestOpenApiRequestValidationTest extends 
CamelTestSupport {
             @Override
             public void process(Exchange exchange) throws Exception {
                 exchange.getMessage().setHeader("fruitsApiVersion", 
fruitsApiVersion);
+                // the path parameter is given, so only the missing header 
fails the validation
+                exchange.getMessage().setHeader("id", 1);
             }
         });
 
@@ -377,6 +420,26 @@ public class RestOpenApiRequestValidationTest extends 
CamelTestSupport {
         assertTrue(errors.iterator().next().startsWith("Header parameter 
'deletionReason' is required"));
     }
 
+    @ParameterizedTest
+    @MethodSource("fruitsApiVersions")
+    void requestValidationReportsEveryMissingParameter(String 
fruitsApiVersion) {
+        // neither the path parameter id nor the required header 
deletionReason: both are reported, not only the first
+        Exchange exchange = template.request("direct:headerParam", new 
Processor() {
+            @Override
+            public void process(Exchange exchange) throws Exception {
+                exchange.getMessage().setHeader("fruitsApiVersion", 
fruitsApiVersion);
+            }
+        });
+
+        Exception exception = exchange.getException();
+        assertInstanceOf(RestOpenApiValidationException.class, exception);
+        Set<String> errors = ((RestOpenApiValidationException) 
exception).getValidationErrors();
+        assertEquals(2, errors.size());
+        assertTrue(errors.stream().anyMatch(e -> e.startsWith("Path parameter 
'id' is required")), errors.toString());
+        assertTrue(errors.stream().anyMatch(e -> e.startsWith("Header 
parameter 'deletionReason' is required")),
+                errors.toString());
+    }
+
     @ParameterizedTest
     @MethodSource("fruitsApiVersions")
     void requestValidationRequiredHeaderParamsPresent(String fruitsApiVersion) 
{

Reply via email to