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("""

Reply via email to