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 973d9ca29 Fix #929: let a Kamelet's declared headers answer
getKameletSupportedHeaders (#3061)
973d9ca29 is described below
commit 973d9ca291ee4aa6c1f54894f608adbedc987ade
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Sep 25 09:56:29 2026 +0200
Fix #929: let a Kamelet's declared headers answer
getKameletSupportedHeaders (#3061)
The catalog API never consulted what a Kamelet declares. It took the Kamelet
name, stripped the last segment, mapped that to a Camel component and
returned
the component's endpoint headers. So a caller asking the catalog what a
Kamelet
emits got what its component can emit, which is a different question.
The gap is wide where it has been measured. aws-s3-source declares four
headers
and the component reports twenty six; azure-storage-blob-source declares
eight
against thirty nine; google-storage-source four against twenty one. It runs
the
other way too: azure-storage-datalake-source declares twenty six where the
component reports twenty five, so the declaration carries a header the
component
does not know about, which is the case #929 was opened for.
getKameletSupportedHeaders now returns spec.dataTypes headers when the
Kamelet
declares any, and falls back to the component otherwise. Fifteen Kamelets
declare
headers today, so the other two hundred and forty seven are unaffected.
Six expectations in testSupportedHeaders move to the declared counts. Three
more
declaring Kamelets already agreed with their component and are unchanged. A
new
test states the semantics directly rather than leaving them implied by
counts: it
asserts the four names aws-s3-source declares, that the Kamelet's own title
travels with the header, and that a Kamelet declaring nothing still falls
back.
This does change what the API answers for those fifteen, which is a visible
change for tooling that reads it. It is the half of #929 that needs no
change to
the Kamelet spec. The other half, whether the declaration should be
decoupled
from dataTypes so a Kamelet that does no data type transformation has
somewhere
to put it, is untouched here and is what still keeps adoption at fifteen of
two
hundred and sixty two. The same question covers #328 for media types.
Co-authored-by: Claude Opus 5 <[email protected]>
---
.../camel/kamelets/catalog/KameletsCatalog.java | 42 ++++++++++++++++++++++
.../kamelets/catalog/KameletsCatalogTest.java | 31 ++++++++++++----
2 files changed, 67 insertions(+), 6 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 845fd091b..7bb6cbc48 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
@@ -41,6 +41,7 @@ import
org.apache.camel.kamelets.catalog.model.KameletTypeEnum;
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;
import org.apache.camel.v1.kameletspec.Definition;
import org.apache.camel.v1.kameletspec.Template;
@@ -243,6 +244,13 @@ public class KameletsCatalog {
List<ComponentModel.EndpointHeaderModel> resultingHeaders = new
ArrayList<>();
Kamelet local = kameletModels.get(name);
if (ObjectHelper.isNotEmpty(local)) {
+ // What the Kamelet declares about itself wins. The component list
describes
+ // everything the component can emit, which both over-reports
headers this
+ // template never surfaces and misses the ones the template adds
itself.
+ List<ComponentModel.EndpointHeaderModel> declared =
getDeclaredHeaders(local);
+ if (!declared.isEmpty()) {
+ return declared;
+ }
String camelType = determineCamelType(local);
String kameletName = local.getMetadata().getName();
int lastIndex = kameletName.lastIndexOf("-");
@@ -263,6 +271,40 @@ public class KameletsCatalog {
return resultingHeaders;
}
+ /**
+ * Headers the Kamelet declares under spec.dataTypes, which describe what
this
+ * 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;
+ }
+ for (DataTypes dataType : kamelet.getSpec().getDataTypes().values()) {
+ if (dataType == null || dataType.getHeaders() == null) {
+ continue;
+ }
+ for (Map.Entry<String, Headers> entry :
dataType.getHeaders().entrySet()) {
+ declared.add(toHeaderModel(entry.getKey(), entry.getValue()));
+ }
+ }
+ return declared;
+ }
+
+ private ComponentModel.EndpointHeaderModel toHeaderModel(String name,
Headers header) {
+ 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()));
+ }
+ return model;
+ }
+
public String getKameletScheme(String prefix) {
return enumValue(prefix);
}
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 ace8a10bc..9653d36bf 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
@@ -177,7 +177,7 @@ public class KameletsCatalogTest {
@Test
void testSupportedHeaders() throws Exception {
- verifyHeaders("aws-s3-source", 26);
+ verifyHeaders("aws-s3-source", 4);
verifyHeaders("aws-s3-sink", 51);
verifyHeaders("aws-cloudtrail-source", 4);
verifyHeaders("aws-redshift-source", 0);
@@ -185,10 +185,10 @@ public class KameletsCatalogTest {
verifyHeaders("azure-eventhubs-sink", 2);
verifyHeaders("azure-functions-sink", 8);
verifyHeaders("azure-servicebus-source", 21);
- verifyHeaders("azure-storage-blob-source", 39);
+ verifyHeaders("azure-storage-blob-source", 8);
verifyHeaders("azure-storage-blob-sink", 46);
verifyHeaders("azure-storage-blob-changefeed-source", 39);
- verifyHeaders("azure-storage-datalake-source", 25);
+ verifyHeaders("azure-storage-datalake-source", 26);
verifyHeaders("azure-storage-datalake-sink", 37);
verifyHeaders("azure-storage-queue-source", 6);
verifyHeaders("azure-storage-queue-sink", 16);
@@ -214,11 +214,11 @@ public class KameletsCatalogTest {
verifyHeaders("google-bigquery-sink", 4);
verifyHeaders("google-calendar-source", 1);
verifyHeaders("google-functions-sink", 5);
- verifyHeaders("google-mail-source", 9);
+ verifyHeaders("google-mail-source", 6);
verifyHeaders("google-pubsub-sink", 3);
verifyHeaders("google-pubsub-source", 6);
- verifyHeaders("google-sheets-source", 6);
- verifyHeaders("google-storage-source", 21);
+ verifyHeaders("google-sheets-source", 5);
+ verifyHeaders("google-storage-source", 4);
verifyHeaders("google-storage-sink", 15);
verifyHeaders("http-source", 5);
verifyHeaders("http-sink", 14);
@@ -321,6 +321,25 @@ public class KameletsCatalogTest {
return scheme != null && new
DefaultCamelCatalog().componentModel(scheme) != null;
}
+ @Test
+ void testDeclaredHeadersWinOverTheComponent() throws Exception {
+ // aws-s3-source declares four headers under spec.dataTypes. The
aws2-s3
+ // component reports twenty six, most of which this template never
emits,
+ // so the declaration is what callers should see.
+ List<ComponentModel.EndpointHeaderModel> headers =
catalog.getKameletSupportedHeaders("aws-s3-source");
+ List<String> names =
headers.stream().map(ComponentModel.EndpointHeaderModel::getName).sorted().toList();
+ assertEquals(List.of("CamelAwsS3BucketName", "CamelAwsS3ContentType",
"CamelAwsS3ETag", "CamelAwsS3Key"), names);
+
+ // The title and description travel with it, so a consumer gets the
+ // Kamelet's own wording rather than the component's.
+ ComponentModel.EndpointHeaderModel key = headers.stream()
+ .filter(h ->
"CamelAwsS3Key".equals(h.getName())).findFirst().orElseThrow();
+ assertEquals("S3 Key", key.getDisplayName());
+
+ // A Kamelet that declares nothing still falls back to its component.
+
assertFalse(catalog.getKameletSupportedHeaders("timer-source").isEmpty());
+ }
+
@Test
void testGetKameletScheme() throws Exception {
assertEquals("aws2-s3", catalog.getKameletScheme("aws-s3"));