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
