diff --git a/docs/components/modules/ROOT/examples/js/camel.js b/docs/components/modules/ROOT/examples/js/camel.js index 82c2c14e2d3e6..d2584cc39237d 100644 --- a/docs/components/modules/ROOT/examples/js/camel.js +++ b/docs/components/modules/ROOT/examples/js/camel.js @@ -39,4 +39,48 @@ module.exports = { return e.msg() } }, + + // the body types of the catalog (CAMEL-24961): one row per side of the bodyTypes section + bodySide: (side) => { + const sides = { + consumerOut: 'Consumer', + consumerReply: 'Consumer reply', + producerIn: 'Producer input', + producerOut: 'Producer output', + marshalIn: 'Marshal input', + marshalOut: 'Marshal output', + unmarshalIn: 'Unmarshal input', + unmarshalOut: 'Unmarshal output', + expressionIn: 'Expression input', + expressionOut: 'Expression result', + } + return `*${sides[side] || side}*` + }, + + // text, bytes or stream, with the Java type when the body is always of that type + bodyCarriers: (value) => { + const carriers = (value.carriers || []).map((carrier) => '`' + carrier + '`') + const words = carriers.length === 0 ? '`unknown`' + : carriers.length === 1 ? carriers[0] + : carriers.slice(0, -1).join(', ') + ' or ' + carriers[carriers.length - 1] + return words + (value.javaType ? ' (`' + value.javaType + '`)' : '') + (value.format ? ', as ' + value.format : '') + }, + + // the description, the bodies for other values of an option, and the options the body depends on + bodyDescription: (value) => { + // only what reads as an attribute reference ({body}, not {header.foo}, which keeps a backslash), and the cell separator + const escape = (text) => text.replace(/\{(\w[\w-]*)\}/g, '\\{$1}').split('|').join('\\|') + const parts = [] + if (value.description) { + parts.push(escape(value.description) + (value.description.endsWith('.') ? '' : '.')) + } + if (value.byOption && value.byOption.length) { + parts.push(value.byOption.map((option) => `* With \`${option.option}=${option.value}\`: ` + + module.exports.bodyCarriers(option)).join('\n')) + } + if (value.dependsOn && value.dependsOn.length) { + parts.push('Not known when ' + value.dependsOn.map((option) => '`' + option + '`').join(' or ') + ' is set.') + } + return parts.join('\n\n') + }, } diff --git a/docs/components/modules/ROOT/partials/body-types.adoc b/docs/components/modules/ROOT/partials/body-types.adoc new file mode 100644 index 0000000000000..8df01700d9406 --- /dev/null +++ b/docs/components/modules/ROOT/partials/body-types.adoc @@ -0,0 +1,21 @@ +// body types: START +// the including partial sets body-types-module (the module of the page) and body-types-intro +include::jsonpathcount${body-types-module}:example$json/{shortname}.json[queries='bodytypecount=nodes$.bodyTypes.*'] + +ifeval::[{bodytypecount} != 0] +== Message Body + +[subs="attributes,quotes"] +{body-types-intro} + +[width="100%",cols="2,3,6a",options="header"] +|=== +| Side | Body | Description +|=== + +jsonpathTable::{body-types-module}:example$json/{shortname}.json['nodes$.bodyTypes.*','camel.bodySide(path[2])|camel.bodyCarriers(value)|camel.bodyDescription(value)','camel=xref:ROOT:js/camel.js'] + +The bodies are the ones with the default options. A side that is not listed is not known, and tooling does not guess +it. The carriers are described in the xref:manual::camel-catalog.adoc[Camel Catalog]. +endif::[] +// body types: END diff --git a/docs/components/modules/ROOT/partials/component-endpoint-headers.adoc b/docs/components/modules/ROOT/partials/component-endpoint-headers.adoc index f6c5b0b415cf9..dac98068db711 100644 --- a/docs/components/modules/ROOT/partials/component-endpoint-headers.adoc +++ b/docs/components/modules/ROOT/partials/component-endpoint-headers.adoc @@ -20,4 +20,8 @@ The {doctitle} component supports the following message header(s), which is/are jsonpathTable::example$json/{shortname}.json['nodes$.headers.*',{cellformats},'util=camel-website-util,camel=xref:js/camel.js'] endif::[] -// component headers: END \ No newline at end of file +// component headers: END + +:body-types-module: ROOT +:body-types-intro: The message body at each side of the {doctitle} component: _Consumer_ is the body of the messages the consumer creates, _Consumer reply_ the bodies a consumer that answers its caller can reply with, _Producer input_ the bodies the producer can send or write, and _Producer output_ the body after the producer. +include::partial$body-types.adoc[] diff --git a/docs/components/modules/dataformats/partials/dataformat-options.adoc b/docs/components/modules/dataformats/partials/dataformat-options.adoc index fd65dff21b793..870c4df4f71e9 100644 --- a/docs/components/modules/dataformats/partials/dataformat-options.adoc +++ b/docs/components/modules/dataformats/partials/dataformat-options.adoc @@ -14,3 +14,7 @@ The {doctitle} dataformat supports the following options which are listed below. jsonpathTable::example$json/{shortname}.json['nodes$.properties[?(@.displayName!="Id")]',{cellformats},{requires}] + +:body-types-module: dataformats +:body-types-intro: The message body at each side of the {doctitle} data format: _Marshal input_ and _Unmarshal input_ are the bodies marshal and unmarshal can take, and _Marshal output_ and _Unmarshal output_ the body after them. +include::ROOT:partial$body-types.adoc[] diff --git a/docs/components/modules/languages/partials/language-options.adoc b/docs/components/modules/languages/partials/language-options.adoc index b042b09b8ded9..66f2687fbc329 100644 --- a/docs/components/modules/languages/partials/language-options.adoc +++ b/docs/components/modules/languages/partials/language-options.adoc @@ -14,3 +14,7 @@ The {doctitle} language supports the following options which are listed below. jsonpathTable::example$json/{shortname}.json['nodes$.properties[?(@.kind=="attribute" && @.displayName!="Id")]',{cellformats},{requires}] + +:body-types-module: languages +:body-types-intro: The message body at each side of the {doctitle} language: _Expression input_ is the body its expressions can read, and _Expression result_ what they return. +include::ROOT:partial$body-types.adoc[] diff --git a/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc b/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc index ce268d3ee666a..9d6bb788c1ce3 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-catalog.adoc @@ -163,7 +163,8 @@ description: ---- The sides say what the body is with the default options. A side that is not in the catalog is not known, and tooling -should not guess it. The Java API gives them on the model: +should not guess it. The documentation page of a component, data format or language shows them in its _Message Body_ +section, and `camel catalog doc` prints them. The Java API gives them on the model: [source,java] ---- 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 9f5196ce01280..10c9719778d76 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 @@ -1649,7 +1649,7 @@ private static void addCommonModelFields(JsonObject result, BaseModel model) * The body types a line per side, as compact as the options are: the carriers, the Java type, the format, the * description, the body for other option values, and the options that make it not known (CAMEL-24961). */ - static JsonObject bodyTypes(Map bodyTypes) { + public static JsonObject bodyTypes(Map bodyTypes) { JsonObject answer = new JsonObject(); for (Map.Entry entry : bodyTypes.entrySet()) { BodyTypeModel side = entry.getValue(); diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDoc.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDoc.java index c3f2504380430..9492c0ca720d1 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDoc.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDoc.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.stream.Collectors; import com.github.freva.asciitable.AsciiTable; @@ -46,6 +47,7 @@ import org.apache.camel.main.util.SuggestSimilarHelper; import org.apache.camel.tooling.maven.MavenGav; import org.apache.camel.tooling.model.BaseOptionModel; +import org.apache.camel.tooling.model.BodyTypeModel; import org.apache.camel.tooling.model.ComponentModel; import org.apache.camel.tooling.model.DataFormatModel; import org.apache.camel.tooling.model.EipModel; @@ -124,6 +126,19 @@ public class CatalogDoc extends CamelCommand { description = "Output in JSON Format: the documentation the MCP tool camel_catalog_doc gives") boolean jsonOutput; + /** The sides of the body types, as the documentation pages name them. */ + private static final Map BODY_SIDES = Map.of( + BodyTypeModel.CONSUMER_OUT, "Consumer", + BodyTypeModel.CONSUMER_REPLY, "Consumer reply", + BodyTypeModel.PRODUCER_IN, "Producer input", + BodyTypeModel.PRODUCER_OUT, "Producer output", + BodyTypeModel.MARSHAL_IN, "Marshal input", + BodyTypeModel.MARSHAL_OUT, "Marshal output", + BodyTypeModel.UNMARSHAL_IN, "Unmarshal input", + BodyTypeModel.UNMARSHAL_OUT, "Unmarshal output", + BodyTypeModel.EXPRESSION_IN, "Expression input", + BodyTypeModel.EXPRESSION_OUT, "Expression result"); + CamelCatalog catalog; public CatalogDoc(CamelJBangMain main) { @@ -579,6 +594,7 @@ private void docComponent(ComponentModel cm) throws Exception { printer().println(optionTable(filtered)); printer().println(""); } + printBodyTypes(cm.getBodyTypes()); if (link != null) { printer().println(link); @@ -628,6 +644,7 @@ private void docDataFormat(DataFormatModel dm) throws Exception { } printer().println(optionTable(filtered)); printer().println(""); + printBodyTypes(dm.getBodyTypes()); if (link != null) { printer().println(link); @@ -677,6 +694,7 @@ private void docLanguage(LanguageModel lm) throws Exception { } printer().println(optionTable(filtered)); printer().println(""); + printBodyTypes(lm.getBodyTypes()); if (link != null) { printer().println(link); @@ -775,6 +793,29 @@ private int[] docColumnWidths(int nameMax, int descMax, int defaultMax, int type }; } + /** + * The message body at each side, as the body types of the catalog say (CAMEL-24961) and the documentation page + * shows it: one line per side, with the default options. Nothing when the catalog does not say. + */ + private void printBodyTypes(Map bodyTypes) { + if (bodyTypes.isEmpty()) { + return; + } + JsonObject lines = CatalogDocs.bodyTypes(bodyTypes); + // the side in one line (Expression result, with the padding of the cell), the body in the rest of the terminal + int sideWidth = BODY_SIDES.values().stream().mapToInt(String::length).max().orElse(17) + 2; + int bodyWidth = Math.max(15, Math.min(120, terminalWidth() - TerminalWidthHelper.fancyBorderOverhead(2) - sideWidth)); + printer().println("Message body (with the default options; a side that is not listed is not known):"); + printer().println(AsciiTable.getTable(AsciiTable.FANCY_ASCII, lines.entrySet(), Arrays.asList( + new Column().header("SIDE").dataAlign(HorizontalAlign.LEFT) + .maxWidth(sideWidth, OverflowBehaviour.NEWLINE) + .with(e -> BODY_SIDES.getOrDefault(e.getKey(), e.getKey())), + new Column().header("BODY").dataAlign(HorizontalAlign.LEFT) + .maxWidth(bodyWidth, OverflowBehaviour.NEWLINE) + .with(e -> String.valueOf(e.getValue()))))); + printer().println(""); + } + private String optionTable(List options) { int[] w = docColumnWidths(35, 80, 25, 25); return AsciiTable.getTable(AsciiTable.FANCY_ASCII, options, Arrays.asList( diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDocTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDocTest.java index 30e04440c94a1..b114ed5532b8f 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDocTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogDocTest.java @@ -192,6 +192,28 @@ public void shouldDocumentADataFormatAndALanguage() throws Exception { Assertions.assertTrue(printer.getOutput().startsWith("Language Name: simple"), printer.getOutput()); } + @Test + public void shouldPrintTheMessageBody() throws Exception { + // CAMEL-24961: the body at each side, as the documentation page shows it + CatalogDoc command = createCommand("file", false); + Assertions.assertEquals(0, command.doCall()); + String output = printer.getOutput(); + Assertions.assertTrue(output.contains("Message body (with the default options"), output); + Assertions.assertTrue(output.contains("Producer input"), output); + Assertions.assertTrue(output.contains("text, bytes, stream or file"), output); + + printer = new StringPrinter(); + command = createCommand("jackson", false); + Assertions.assertEquals(0, command.doCall()); + Assertions.assertTrue(printer.getOutput().contains("Unmarshal output"), printer.getOutput()); + + // a component whose body types are not in the catalog has no such table + printer = new StringPrinter(); + command = createCommand("weather", false); + Assertions.assertEquals(0, command.doCall()); + Assertions.assertFalse(printer.getOutput().contains("Message body"), printer.getOutput()); + } + @Test public void shouldFilterTheOptions() throws Exception { CatalogDoc command = createCommand("timer", false);