This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new ddca15435112 CAMEL-25735: camel-jbang - samples for interceptFrom,
interceptSendToEndpoint and dataFormats, and sslContextParameters in
camel_catalog_doc (#28044)
ddca15435112 is described below
commit ddca15435112f80fa2992e460255f9ba76d8cc8f
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 18 10:41:41 2026 +0200
CAMEL-25735: camel-jbang - samples for interceptFrom,
interceptSendToEndpoint and dataFormats, and sslContextParameters in
camel_catalog_doc (#28044)
interceptFrom and interceptSendToEndpoint had no sample: their examples are
on the page of the intercept EIP, after
its own. The sample lookup now reads that page for them, keeping the
examples that start with their own key.
dataFormats had no sample, as the documentation had no example of it. The
data format page of the user manual now
shows a data format defined once in a top-level dataFormats entry and used
by unmarshal and marshal with custom: ref:
(checked with camel run), and the sample generator reads that page for
dataFormats.
sslContextParameters is an application model of the catalog (models-app),
not an EIP model, so camel_catalog_doc
and camel_catalog_find did not find it. It is answered like an EIP now, and
camel_catalog_find lists it for ssl. When
the application model cannot be read from the catalog, it is logged at
DEBUG.
With CAMEL-25719, all 18 top-level entries of the YAML DSL are known to
camel_catalog_doc, camel_catalog_find and
camel_catalog_sample.
Signed-off-by: Claus Ibsen <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
---
.../modules/ROOT/pages/data-format.adoc | 34 +++++++++++++++
dsl/camel-jbang/camel-jbang-core/pom.xml | 1 +
.../dsl/jbang/core/commands/ai/eip-samples.json | 6 +++
.../dsl/jbang/core/commands/ai/CatalogDocs.java | 48 ++++++++++++++++++++++
.../dsl/jbang/core/commands/ai/CatalogSamples.java | 22 ++++++++++
.../jbang/core/commands/ai/CatalogDocsTest.java | 16 ++++++++
.../jbang/core/commands/ai/CatalogSamplesTest.java | 16 ++++++++
7 files changed, 143 insertions(+)
diff --git a/docs/user-manual/modules/ROOT/pages/data-format.adoc
b/docs/user-manual/modules/ROOT/pages/data-format.adoc
index fc58293a61e9..24a6a3a2002c 100644
--- a/docs/user-manual/modules/ROOT/pages/data-format.adoc
+++ b/docs/user-manual/modules/ROOT/pages/data-format.adoc
@@ -26,6 +26,40 @@ support formats such as XML, CSV, JSON, YAML, Avro,
Protobuf, and many more.
See xref:components:eips:marshal-eip.adoc[Marshal] for more information and
examples.
+== Defining a data format once
+
+A data format with options is often used in more than one place, for example
to unmarshal a message and later marshal
+it again. Define it once, with an id, in a top-level `dataFormats` entry, and
refer to it by that id with `custom`:
+
+[source,yaml]
+----
+- dataFormats:
+ - json:
+ id: orderJson
+ library: Jackson
+ prettyPrint: true
+- route:
+ from:
+ uri: timer:tick
+ parameters:
+ repeatCount: 1
+ steps:
+ - setBody:
+ expression:
+ constant:
+ expression: '{"orderId": "ORD-1001", "qty": 2}'
+ - unmarshal:
+ custom:
+ ref: orderJson
+ - marshal:
+ custom:
+ ref: orderJson
+ - log:
+ message: "${body}"
+----
+
+Both `unmarshal` and `marshal` use the same JSON data format, so the log shows
the order pretty printed.
+
== See Also
** xref:manual::dataformat-dsl.adoc[Data Format DSL]
diff --git a/dsl/camel-jbang/camel-jbang-core/pom.xml
b/dsl/camel-jbang/camel-jbang-core/pom.xml
index 4fbd5c823ac5..e57bfc6dce53 100644
--- a/dsl/camel-jbang/camel-jbang-core/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-core/pom.xml
@@ -304,6 +304,7 @@
<entryPage>../../../docs/user-manual/modules/ROOT/pages/transformer.adoc=transformers</entryPage>
<entryPage>../../../docs/user-manual/modules/ROOT/pages/validator.adoc=validators</entryPage>
<entryPage>../../../docs/user-manual/modules/ROOT/pages/camel-configuration-utilities.adoc=sslContextParameters</entryPage>
+
<entryPage>../../../docs/user-manual/modules/ROOT/pages/data-format.adoc=dataFormats</entryPage>
</entryPages>
<!-- user manual pages that show old syntax or
YAML that is not a Camel file -->
<excludes>
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json
index 5548570fb542..90d37e636eb8 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/org/apache/camel/dsl/jbang/core/commands/ai/eip-samples.json
@@ -235,6 +235,12 @@
"yaml": "- route:\n from:\n uri: direct\n parameters:\n
name: start\n steps:\n - loadBalance:\n
customLoadBalancer:\n ref: myBalancer\n steps:\n
- to:\n uri: seda:x\n - to:\n
uri: seda:y\n - to:\n uri: seda:z\n"
}
],
+ "dataFormats": [
+ {
+ "source": "data-format.adoc",
+ "yaml": "- dataFormats:\n - json:\n id: orderJson\n
library: Jackson\n prettyPrint: true\n- route:\n from:\n uri:
timer:tick\n parameters:\n repeatCount: 1\n steps:\n -
setBody:\n expression:\n constant:\n
expression: '{\"orderId\": \"ORD-1001\", \"qty\": 2}'\n - unmarshal:\n
custom:\n ref: orderJson\n - marshal:\n
custom:\n re [...]
+ }
+ ],
"deadLetterChannel": [
{
"source": "dead-letter-channel.adoc",
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
index a8584ad191b4..76074aa8e7a0 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
@@ -42,11 +42,14 @@ import org.apache.camel.tooling.model.BaseOptionModel;
import org.apache.camel.tooling.model.ComponentModel;
import org.apache.camel.tooling.model.DataFormatModel;
import org.apache.camel.tooling.model.EipModel;
+import org.apache.camel.tooling.model.JsonMapper;
import org.apache.camel.tooling.model.LanguageModel;
import org.apache.camel.tooling.model.PojoBeanModel;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
import org.apache.camel.util.json.Jsoner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* The catalog documentation an AI agent authoring an integration asks for: a
component, data format, language or EIP
@@ -58,6 +61,8 @@ import org.apache.camel.util.json.Jsoner;
*/
public final class CatalogDocs {
+ private static final Logger LOG =
LoggerFactory.getLogger(CatalogDocs.class);
+
/** The sub-pages of a language's documentation (simple has functions,
operators, ognl and advanced). */
private static final List<String> LANGUAGE_DOC_PAGES =
List.of("functions", "operators", "ognl", "advanced");
@@ -389,6 +394,10 @@ public final class CatalogDocs {
return beans;
}
}
+ EipModel app = appModel(catalog, name);
+ if (app != null) {
+ return eipDoc(catalog, app, lowerFilter, scope, includeDoc,
null);
+ }
if (kind != null) {
// an alias (fan-out, dedup, rate-limit) or the whole title
names the EIP as well
JsonObject byTerm = eipByTerm(catalog, name, lowerFilter,
scope, includeDoc);
@@ -630,6 +639,36 @@ public final class CatalogDocs {
return result;
}
+ /**
+ * The top-level entries of the YAML DSL whose model is an application
model of the catalog (models-app), not an EIP
+ * model: camel_catalog_doc and camel_catalog_find did not find
sslContextParameters (CAMEL-25735).
+ */
+ static final List<String> APP_ENTRIES = List.of("sslContextParameters");
+
+ /** The application model of the catalog for a top-level entry (any case),
or null. */
+ static EipModel appModel(CamelCatalog catalog, String name) {
+ String entry = APP_ENTRIES.stream().filter(e ->
e.equalsIgnoreCase(name)).findFirst().orElse(null);
+ if (entry == null) {
+ return null;
+ }
+ try (InputStream is = catalog.getVersionManager()
+ .getResourceAsStream("org/apache/camel/catalog/models-app/" +
entry + ".json")) {
+ if (is == null) {
+ return null;
+ }
+ EipModel model = JsonMapper.generateEipModel(new
String(is.readAllBytes(), StandardCharsets.UTF_8));
+ // the description comes from the javadoc, with its XML example
escaped
+ if (model.getDescription() != null) {
+ model.setDescription(model.getDescription().replace("<",
"<").replace(">", ">")
+ .replace(""", "\"").replace("&", "&"));
+ }
+ return model;
+ } catch (Exception e) {
+ LOG.debug("Cannot read the application model {} from the catalog",
entry, e);
+ return null;
+ }
+ }
+
/** The top-level entry of the YAML DSL that declares beans: a list of
bean factories, with no model of its own. */
static final String BEANS_ENTRY = "beans";
@@ -988,6 +1027,15 @@ public final class CatalogDocs {
if (BEANS_ENTRY.equals(key) || "bean".equals(key)) {
matches.add(summary("eip", BEANS_ENTRY, "Beans",
BEANS_DESCRIPTION, "configuration"));
}
+ // and the entries of an application model, by the start of their
name (ssl)
+ for (String entry : APP_ENTRIES) {
+ if (key.length() >= 3 && normalizeName(entry).startsWith(key))
{
+ EipModel m = appModel(catalog, entry);
+ if (m != null) {
+ matches.add(summary("eip", m.getName(), m.getTitle(),
m.getDescription(), m.getLabel()));
+ }
+ }
+ }
}
if (kind == null || "bean".equals(kind)) {
// the built-in beans: an interface name (AggregationStrategy)
lists its implementations
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamples.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamples.java
index 208786c44d8b..4a6390156eda 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamples.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamples.java
@@ -242,7 +242,22 @@ public final class CatalogSamples {
return found;
}
+ /**
+ * The entries whose examples are on the page of another EIP, after the
examples of that EIP: interceptFrom and
+ * interceptSendToEndpoint are on the page of intercept, and had no sample
(CAMEL-25735).
+ */
+ static final Map<String, String> SAMPLES_ON_PAGE_OF
+ = Map.of("interceptFrom", "intercept", "interceptSendToEndpoint",
"intercept");
+
private static Found eipSamples(CamelCatalog catalog, String key) {
+ String host = SAMPLES_ON_PAGE_OF.get(key);
+ if (host != null) {
+ // the examples of the page that start with the entry, not those
of the EIP the page is about
+ String start = "- " + key + ":";
+ List<Map<String, String>> own = eipSamples(catalog,
host).samples().stream()
+ .filter(s ->
s.get("yaml").stripLeading().startsWith(start)).toList();
+ return own.isEmpty() ? Found.NONE : new Found(own, false);
+ }
for (String candidate : List.of(key + "-eip", kebab(key) + "-eip",
kebab(key), key)) {
if (catalog.asciiDoc(candidate) != null) {
return pageSamples(catalog, List.of(candidate), null);
@@ -836,6 +851,13 @@ public final class CatalogSamples {
return k;
}
}
+ // an entry whose examples are on the page of another EIP, which the
shipped set does not have: they come from
+ // the catalog's documentation (CAMEL-25735)
+ for (String k : SAMPLES_ON_PAGE_OF.keySet()) {
+ if (k.equalsIgnoreCase(n)) {
+ return k;
+ }
+ }
return null;
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
index bc790f834679..e626e7100eb5 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
@@ -303,6 +303,22 @@ class CatalogDocsTest {
assertEquals("bean", ((JsonObject) eips.get(0)).getString("name"));
}
+ @Test
+ void sslContextParametersIsKnown() throws Exception {
+ // CAMEL-25735: a top-level entry of the YAML DSL whose model is an
application model of the catalog (models-app),
+ // not an EIP model; camel_catalog_doc and camel_catalog_find answered
that nothing was found
+ JsonObject doc = catalogDoc(Map.of("name", "sslContextParameters"));
+ assertEquals("sslContextParameters", doc.getString("name"),
String.valueOf(doc));
+ assertTrue(doc.getString("placement").startsWith("top-level entry"),
doc.getString("placement"));
+ assertTrue(String.valueOf(doc.get("options")).contains("keyStore"),
String.valueOf(doc.get("options")));
+ assertFalse(doc.getString("description").contains("<"),
doc.getString("description"));
+ assertNotNull(doc.get("sampleHint"),
"camel-configuration-utilities.adoc has an example");
+
+ JsonArray matches = (JsonArray) CatalogDocs.find(new
DefaultCamelCatalog(), "ssl", null, 10).get("matches");
+ assertTrue(matches.stream().anyMatch(m ->
"sslContextParameters".equals(((JsonObject) m).getString("name"))),
+ String.valueOf(matches));
+ }
+
@Test
void anEipAliasFindsTheEip() throws Exception {
// the aliases of the EIP models (fan-out, dedup, rate-limit) are what
a model asks with; the former
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamplesTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamplesTest.java
index 4c227b3beed8..1820d2a98c17 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamplesTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogSamplesTest.java
@@ -40,6 +40,22 @@ public class CatalogSamplesTest {
assertThat(((JsonObject)
samples.get(0)).getString("yaml")).startsWith("- onException:");
}
+ @Test
+ void theInterceptEntriesAndDataFormatsHaveSamples() {
+ // CAMEL-25735: interceptFrom and interceptSendToEndpoint are on the
page of intercept, after its own examples,
+ // and the data format page of the user manual has an example of
dataFormats now
+ CamelCatalog catalog = new DefaultCamelCatalog();
+ for (String entry : List.of("interceptFrom",
"interceptSendToEndpoint", "dataFormats")) {
+ JsonObject o = CatalogSamples.sample(catalog, entry, 2);
+
assertThat(o.getString("placement")).as(entry).contains("top-level");
+ JsonArray samples = (JsonArray) o.get("samples");
+ assertThat(samples).as(entry).isNotEmpty();
+ for (Object s : samples) {
+ assertThat(((JsonObject)
s).getString("yaml")).as(entry).startsWith("- " + entry + ":");
+ }
+ }
+ }
+
@Test
void theTopLevelEntriesDocumentedOnAnotherPageHaveSamples() {
// CAMEL-25719: their examples are on the page of what they belong to
(route templates, the REST DSL,