This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch pr/CAMEL-24698-source-checks in repository https://gitbox.apache.org/repos/asf/camel.git
commit d2ceb28a3daa9d17d18b0ceba47563b815c61b42 Author: Claus Ibsen <[email protected]> AuthorDate: Sun Sep 13 17:08:40 2026 +0200 CAMEL-24698: camel-jbang - a timer exchange property used as a header (${header.CamelTimerCounter}) points to ${exchangeProperty.CamelTimerCounter} The timer sets its counter, name, period and time with setProperty, so the header is null; the catalog has no metadata for exchange properties, so the timer's are listed in the header check. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj --- .../dsl/jbang/core/commands/ai/HeaderChecks.java | 25 +++++++++++++++++++++- .../commands/ai/SourceValidatorEndpointTest.java | 14 ++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java index 3479c81c6340..15d785f8aebf 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/HeaderChecks.java @@ -49,6 +49,13 @@ final class HeaderChecks { * the value is null at runtime. Checked against the header metadata of every component the file names, with the * closest real name. */ + /** + * Names a component sets as exchange properties, not headers; the catalog has no metadata for those, so the ones a + * beginner reaches for are listed here (TimerConsumer sets them with setProperty). + */ + static final Map<String, List<String>> EXCHANGE_PROPERTIES = Map.of( + "timer", List.of("CamelTimerCounter", "CamelTimerName", "CamelTimerPeriod", "CamelTimerTime")); + public static List<String> validateKnownHeaders(String content, CamelCatalog catalog) { List<String> msgs = new ArrayList<>(); if (content == null || catalog == null) { @@ -94,7 +101,23 @@ final class HeaderChecks { Matcher m = CAMEL_HEADER_REF_PATTERN.matcher(lines[i]); while (m.find()) { String name = m.group(1); - if (known.contains(name) || common.contains(name) || !reported.add(name)) { + if (common.contains(name) || reported.contains(name)) { + continue; + } + String propertyOwner = null; + for (String scheme : schemes) { + if (EXCHANGE_PROPERTIES.getOrDefault(scheme, List.of()).contains(name)) { + propertyOwner = scheme; + } + } + if (propertyOwner != null) { + // before the header metadata: an exchange property is never a header, whatever the metadata says + reported.add(name); + msgs.add("Line " + (i + 1) + ": " + name + " is an exchange property set by " + propertyOwner + + ", not a header (the header would be null): write ${exchangeProperty." + name + "}"); + continue; + } + if (known.contains(name) || !reported.add(name)) { continue; } String best = closestName(name, new ArrayList<>(known)); diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java index 8cfa7d321e79..911d3a3c7fa3 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorEndpointTest.java @@ -384,6 +384,20 @@ class SourceValidatorEndpointTest { assertThat(msgs.get(1)).contains("header CamelFilePathX is not set by file"); } + @Test + void aTimerExchangePropertyUsedAsAHeaderIsNamed() { + // TimerConsumer sets the counter, name, period and time as exchange properties; only the fired time is a header + List<String> msgs = SourceValidator.validateKnownHeaders(""" + - from: + uri: "timer:tick?period=1000" + steps: + - log: "${header.CamelTimerCounter} ${header.CamelTimerFiredTime} ${exchangeProperty.CamelTimerName}" + """, catalog); + assertThat(msgs).hasSize(1); + assertThat(msgs.get(0)).contains("CamelTimerCounter is an exchange property set by timer, not a header") + .contains("${exchangeProperty.CamelTimerCounter}"); + } + @Test void aProducerOnlyComponentInFromIsNamed() { List<String> msgs = SourceValidator.validateYamlEndpoints("""
