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

oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-kamelets.git


The following commit(s) were added to refs/heads/main by this push:
     new b618b1429 Fix #929: read the headers a Kamelet declares inside a data 
type (#3075)
b618b1429 is described below

commit b618b1429247effb01e3cb41f9f60e84b5e6cf4b
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Oct 1 10:39:16 2026 +0200

    Fix #929: read the headers a Kamelet declares inside a data type (#3075)
    
    #3061 made getKameletSupportedHeaders prefer what a Kamelet declares
    over what its component can emit, but it read only the side-level
    spec.dataTypes.<in|out>.headers. Four Kamelets put their headers one
    level deeper, inside a data type, and those declarations were ignored:
    
        slack-source            5 CloudEvent headers    reported 0
        azure-cosmosdb-source   5 CloudEvent headers    component fallback
        google-sheets-sink      5 GoogleSheets headers  component fallback
        aws-ddb-sink            2 DDB headers           component fallback
    
    slack-source is the plainest case: the test asserted zero supported
    headers for a Kamelet that declares five. So #3061 fixed 15 of the 19
    Kamelets that declare anything, and this covers the rest.
    
    Read per-type headers as a per-side fallback, not as a union. Where a
    side declares a top-level headers block that stays the answer, because
    those are the headers the Kamelet emits whichever data type is in use,
    while a per-type header appears only when its type is selected. Merging
    the two would over-report in exactly the way the component list does --
    unioning them took aws-s3-source from its 4 declared S3 headers to 9 by
    adding the cloudevents ones, and testDeclaredHeadersWinOverTheComponent
    from #3061 failed on it, which is the rule working as intended.
    
    toHeaderModel now takes the fields rather than the object: the
    side-level and type-level header POJOs are generated separately and
    share no supertype.
    
    Tests: assert the four counts above, so the behaviour is pinned rather
    than implied. aws-s3-source stays at 4, unchanged.
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    Co-authored-by: Claude Opus 5 <[email protected]>
---
 .../camel/kamelets/catalog/KameletsCatalog.java    | 73 +++++++++++++++++-----
 .../kamelets/catalog/KameletsCatalogTest.java      |  7 ++-
 2 files changed, 64 insertions(+), 16 deletions(-)

diff --git 
a/library/camel-kamelets-catalog/src/main/java/org/apache/camel/kamelets/catalog/KameletsCatalog.java
 
b/library/camel-kamelets-catalog/src/main/java/org/apache/camel/kamelets/catalog/KameletsCatalog.java
index 7bb6cbc48..1df9502c8 100644
--- 
a/library/camel-kamelets-catalog/src/main/java/org/apache/camel/kamelets/catalog/KameletsCatalog.java
+++ 
b/library/camel-kamelets-catalog/src/main/java/org/apache/camel/kamelets/catalog/KameletsCatalog.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
@@ -42,6 +43,7 @@ import org.apache.camel.tooling.model.ComponentModel;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.v1.Kamelet;
 import org.apache.camel.v1.kameletspec.datatypes.Headers;
+import org.apache.camel.v1.kameletspec.datatypes.Types;
 import org.apache.camel.v1.kameletspec.DataTypes;
 import org.apache.camel.v1.kameletspec.Definition;
 import org.apache.camel.v1.kameletspec.Template;
@@ -276,32 +278,73 @@ public class KameletsCatalog {
      * template actually emits or consumes rather than what its component 
supports.
      */
     private List<ComponentModel.EndpointHeaderModel> 
getDeclaredHeaders(Kamelet kamelet) {
-        List<ComponentModel.EndpointHeaderModel> declared = new ArrayList<>();
         if (kamelet.getSpec() == null || kamelet.getSpec().getDataTypes() == 
null) {
-            return declared;
+            return new ArrayList<>();
         }
+        // Keyed by name, because the same header commonly repeats across the 
data types
+        // of one side.
+        Map<String, ComponentModel.EndpointHeaderModel> declared = new 
LinkedHashMap<>();
         for (DataTypes dataType : kamelet.getSpec().getDataTypes().values()) {
-            if (dataType == null || dataType.getHeaders() == null) {
+            if (dataType == null) {
                 continue;
             }
-            for (Map.Entry<String, Headers> entry : 
dataType.getHeaders().entrySet()) {
-                declared.add(toHeaderModel(entry.getKey(), entry.getValue()));
+            if (dataType.getHeaders() != null && 
!dataType.getHeaders().isEmpty()) {
+                // The side-level block is the authoritative summary for that 
side: it is
+                // what the Kamelet always emits or consumes, whichever data 
type is in
+                // use. Where it exists it is the answer, and the per-type 
blocks below
+                // are deliberately not merged into it -- those headers appear 
only when
+                // their type is selected, so adding them would over-report 
exactly the
+                // way the component list does.
+                for (Map.Entry<String, Headers> entry : 
dataType.getHeaders().entrySet()) {
+                    Headers header = entry.getValue();
+                    declared.computeIfAbsent(entry.getKey(), n -> 
toHeaderModel(n,
+                            header == null ? null : header.getTitle(),
+                            header == null ? null : header.getDescription(),
+                            header == null ? null : header.getType(),
+                            header == null ? null : header.get_default(),
+                            header == null ? null : header.getRequired()));
+                }
+                continue;
+            }
+            // No side-level block, so fall back to what its data types 
declare before
+            // falling back to the component. A Kamelet that only transforms 
its payload
+            // puts its headers there, and reading nothing made the catalog 
report that
+            // such a Kamelet supports no headers at all.
+            if (dataType.getTypes() != null) {
+                for (Types type : dataType.getTypes().values()) {
+                    if (type == null || type.getHeaders() == null) {
+                        continue;
+                    }
+                    for (Map.Entry<String, 
org.apache.camel.v1.kameletspec.datatypes.types.Headers> entry
+                            : type.getHeaders().entrySet()) {
+                        
org.apache.camel.v1.kameletspec.datatypes.types.Headers header = 
entry.getValue();
+                        declared.computeIfAbsent(entry.getKey(), n -> 
toHeaderModel(n,
+                                header == null ? null : header.getTitle(),
+                                header == null ? null : 
header.getDescription(),
+                                header == null ? null : header.getType(),
+                                header == null ? null : header.get_default(),
+                                header == null ? null : header.getRequired()));
+                    }
+                }
             }
         }
-        return declared;
+        return new ArrayList<>(declared.values());
     }
 
-    private ComponentModel.EndpointHeaderModel toHeaderModel(String name, 
Headers header) {
+    /**
+     * The side-level and type-level header POJOs are generated separately and 
share no
+     * supertype, so the fields are passed in rather than the object.
+     */
+    private ComponentModel.EndpointHeaderModel toHeaderModel(
+            String name, String title, String description, String type, String 
defaultValue, Boolean required) {
         ComponentModel.EndpointHeaderModel model = new 
ComponentModel.EndpointHeaderModel();
         model.setName(name);
-        if (header != null) {
-            model.setDisplayName(header.getTitle());
-            model.setDescription(header.getDescription());
-            model.setType(header.getType());
-            model.setJavaType(header.getType());
-            model.setDefaultValue(header.get_default());
-            model.setRequired(Boolean.TRUE.equals(header.getRequired()));
-        }
+        model.setDisplayName(title);
+        model.setDescription(description);
+        model.setType(type);
+        model.setJavaType(type);
+        model.setDefaultValue(defaultValue);
+        model.setRequired(Boolean.TRUE.equals(required));
         return model;
     }
 
diff --git 
a/library/camel-kamelets-catalog/src/test/java/org/apache/camel/kamelets/catalog/KameletsCatalogTest.java
 
b/library/camel-kamelets-catalog/src/test/java/org/apache/camel/kamelets/catalog/KameletsCatalogTest.java
index 394fd3937..77e482d86 100644
--- 
a/library/camel-kamelets-catalog/src/test/java/org/apache/camel/kamelets/catalog/KameletsCatalogTest.java
+++ 
b/library/camel-kamelets-catalog/src/test/java/org/apache/camel/kamelets/catalog/KameletsCatalogTest.java
@@ -277,7 +277,12 @@ public class KameletsCatalogTest {
         verifyHeaders("sftp-sink", 6);
         verifyHeaders("sftp-source", 15);
         verifyHeaders("slack-sink", 0);
-        verifyHeaders("slack-source", 0);
+        // Declares five CloudEvent headers under 
spec.dataTypes.out.types.cloudevents.headers,
+        // so it reports those rather than falling back to the camel-slack 
component.
+        verifyHeaders("slack-source", 5);
+        verifyHeaders("azure-cosmosdb-source", 5);
+        verifyHeaders("google-sheets-sink", 5);
+        verifyHeaders("aws-ddb-sink", 2);
         verifyHeaders("splunk-hec-sink", 1);
         verifyHeaders("splunk-sink", 0);
         verifyHeaders("splunk-source", 0);

Reply via email to