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

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

commit 71aba9fdcd66bb2a05730068be4b6344bbc63395
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Sep 24 09:53:31 2026 +0200

    CAMEL-24986: a REST producer says which path parameter has no value
    
    The request went out with the placeholder still in the path, so the
    service answered 404 for a path holding a {name} and nothing said why:
    
      HTTP operation failed invoking
      http://localhost:8080/api/stock/%7Bsku%7D/reserve with statusCode: 404
    
    resolvePlaceholders reads a header and falls back to an exchange
    variable, and left the placeholder as it was when neither had a value.
    It now says which parameter it is and where the value comes from:
    
      The path parameter {sku} of /api/stock/{sku}/reserve has no value: set
      the header sku, or an exchange variable of that name, before the call.
    
    Only a name in the braces counts, so a uri that holds braces for another
    reason is untouched.
    
    This changes behaviour: RestProducerAdvancedTest pinned the old one - it
    was added as coverage and its comment describes what happened rather than
    why - and now asserts the message. A request with an unresolved
    placeholder cannot succeed, so failing at the producer is more useful
    than a 404 from the far end.
    
    Measured on an overnight local-model benchmark: this single mistake
    produced 2580 of the runtime errors, the most of any cause, across 13 of
    15 runs of the three HTTP examples.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj
---
 .../apache/camel/component/rest/RestProducer.java  | 34 ++++++++++++++++++++++
 .../component/rest/RestProducerAdvancedTest.java   | 13 +++++----
 2 files changed, 41 insertions(+), 6 deletions(-)

diff --git 
a/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestProducer.java
 
b/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestProducer.java
index 8b8abd47c963..9eb40e0e4294 100644
--- 
a/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestProducer.java
+++ 
b/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestProducer.java
@@ -29,6 +29,7 @@ import org.apache.camel.AsyncCallback;
 import org.apache.camel.AsyncProcessor;
 import org.apache.camel.AsyncProducer;
 import org.apache.camel.CamelContext;
+import org.apache.camel.CamelExchangeException;
 import org.apache.camel.Endpoint;
 import org.apache.camel.Exchange;
 import org.apache.camel.Producer;
@@ -167,6 +168,17 @@ public class RestProducer extends DefaultAsyncProducer {
                     }
                 }
                 resolvedUriTemplate = uriTemplateBuilder.toString();
+
+                // a placeholder with no value would be sent as it is, and the 
service answers 404 for a path that
+                // holds a {name}: say which parameter it is instead 
(CAMEL-24986)
+                String unresolved = firstPlaceholder(resolvedUriTemplate);
+                if (unresolved != null) {
+                    throw new CamelExchangeException(
+                            "The path parameter {" + unresolved + "} of " + 
resolvedUriTemplate + " has no value:"
+                                                     + " set the header " + 
unresolved + ", or an exchange variable of"
+                                                     + " that name, before the 
call.",
+                            exchange);
+                }
             }
         }
 
@@ -221,6 +233,28 @@ public class RestProducer extends DefaultAsyncProducer {
         }
     }
 
+    /**
+     * The name of the first {@code {name}} left in the template, or null when 
every one of them was resolved
+     * (CAMEL-24986).
+     */
+    private static String firstPlaceholder(String uriTemplate) {
+        int start = uriTemplate.indexOf('{');
+        while (start >= 0) {
+            int end = uriTemplate.indexOf('}', start);
+            if (end < 0) {
+                return null;
+            }
+            String name = uriTemplate.substring(start + 1, end);
+            // a name, not something else that happens to be in braces
+            if (!name.isEmpty() && name.chars().allMatch(c -> 
Character.isLetterOrDigit(c) || c == '_' || c == '-'
+                    || c == '.')) {
+                return name;
+            }
+            start = uriTemplate.indexOf('{', end);
+        }
+        return null;
+    }
+
     /**
      * Replaces placeholders "{}" with message header or exchange variable 
values.
      *
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerAdvancedTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerAdvancedTest.java
index 9680e1d5d00e..d288a2902cae 100644
--- 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerAdvancedTest.java
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestProducerAdvancedTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.rest;
 import java.util.HashMap;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.CamelExchangeException;
 import org.apache.camel.Exchange;
 import org.apache.camel.Producer;
 import org.apache.camel.impl.DefaultCamelContext;
@@ -32,6 +33,7 @@ import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 @ExtendWith(MockitoExtension.class)
 class RestProducerAdvancedTest {
@@ -84,12 +86,11 @@ class RestProducerAdvancedTest {
         RestProducer producer = new RestProducer(endpoint, mockProducer, 
config);
 
         Exchange exchange = new DefaultExchange(camelContext);
-        // Don't set the header, so placeholder won't be resolved
-        producer.prepareExchange(exchange);
-
-        // When placeholder is not resolved, REST_HTTP_URI should not be set
-        String uri = 
exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, String.class);
-        assertThat(uri).isNull();
+        // no header, so the placeholder has no value: the request would go 
out with {userId} in the path and the
+        // service would answer 404 for it, so it fails here instead and says 
which parameter it is (CAMEL-24986)
+        CamelExchangeException e = assertThrows(CamelExchangeException.class, 
() -> producer.prepareExchange(exchange));
+        assertThat(e.getMessage()).contains("The path parameter 
{userId}").contains("set the header userId");
+        
assertThat(exchange.getMessage().getHeader(RestConstants.REST_HTTP_URI, 
String.class)).isNull();
     }
 
     @Test

Reply via email to