This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-25736 in repository https://gitbox.apache.org/repos/asf/camel.git
commit e7c2aac26838dde5083c8be40f02cbaac3f34c40 Author: Claus Ibsen <[email protected]> AuthorDate: Sat Oct 17 16:23:50 2026 +0200 CAMEL-25736: camel-jbang - the AI samples and the documentation tests read the YAML examples with a longer delimiter or a leading comment, and four broken examples are fixed DocBlocks, the extractor of the doc-samples plugin and of the documentation tests, now reads a listing with a delimiter longer than ---- (14 examples on 3 pages) and a block that opens with a comment before a top-level entry (35 examples on about 20 pages); a list of steps with a comment above it stays a fragment. CatalogSamples uses DocBlocks instead of its own regex, so camel_catalog_sample reads the docs as the plugin and the tests do, without the callout markers and the blocks marked as what to avoid. The examples the tests then checked for the first time that were wrong are fixed: docling-batch-processing (the aggregate's strategy, output and a property the strategy does not have), keycloak-consumer (a filter without steps, an extra expression level), keycloak-producer (an operation that does not exist, application.yaml syntax in the route file) and keycloak-security (handled as a plain value); with their catalog copies. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m --- .../catalog/docs/docling-batch-processing.adoc | 13 ++-- .../camel/catalog/docs/keycloak-consumer.adoc | 28 +++---- .../camel/catalog/docs/keycloak-producer.adoc | 19 ++--- .../camel/catalog/docs/keycloak-security.adoc | 4 +- .../src/main/docs/docling-batch-processing.adoc | 13 ++-- .../src/main/docs/keycloak-consumer.adoc | 28 +++---- .../src/main/docs/keycloak-producer.adoc | 19 ++--- .../src/main/docs/keycloak-security.adoc | 4 +- .../dsl/jbang/core/commands/ai/CatalogSamples.java | 13 ++-- .../apache/camel/dsl/yaml/validator/DocBlocks.java | 53 +++++++++++-- .../camel/dsl/yaml/validator/DocBlocksTest.java | 87 ++++++++++++++++++++++ 11 files changed, 206 insertions(+), 75 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/docling-batch-processing.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/docling-batch-processing.adoc index 477e747a0f1e..80d99254369f 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/docling-batch-processing.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/docling-batch-processing.adoc @@ -174,8 +174,6 @@ YAML:: - beans: - name: documentListProcessor type: "#class:org.apache.camel.processor.aggregate.GroupedBodyAggregationStrategy" - properties: - strategyMethodName: "aggregate" # Route 1: Collect documents - route: @@ -205,14 +203,15 @@ YAML:: concurrentConsumers: 1 steps: - aggregate: - aggregationStrategy: - bean: "documentListProcessor" + aggregationStrategy: documentListProcessor correlationExpression: - constant: true + constant: + expression: "true" completionSize: 10 completionTimeout: 5000 - - to: - uri: direct:batch-process + steps: + - to: + uri: direct:batch-process # Route 3: Process batch - route: diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-consumer.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-consumer.adoc index 9c0484ac670c..f2f0bb098fa4 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-consumer.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-consumer.adoc @@ -563,21 +563,21 @@ YAML:: expression: simple: expression: "${body.type} == 'LOGIN_ERROR'" - - aggregate: - correlationExpression: - expression: - simple: - expression: "${body.ipAddress}" - aggregationStrategy: "#arrayListAggregation" - completionSize: 5 - completionTimeout: 300000 steps: - - log: - message: "SECURITY ALERT: Multiple failed login attempts from ${body[0].ipAddress}" - - to: - uri: direct:block-ip - - to: - uri: direct:send-security-alert + - aggregate: + correlationExpression: + simple: + expression: "${body.ipAddress}" + aggregationStrategy: "#class:org.apache.camel.processor.aggregate.GroupedBodyAggregationStrategy" + completionSize: 5 + completionTimeout: 300000 + steps: + - log: + message: "SECURITY ALERT: Multiple failed login attempts from ${body[0].ipAddress}" + - to: + uri: direct:block-ip + - to: + uri: direct:send-security-alert ---- ==== diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-producer.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-producer.adoc index fe3c24e952a6..320d98815f57 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-producer.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-producer.adoc @@ -784,7 +784,7 @@ YAML:: - to: uri: keycloak:admin parameters: - operation: setUserPassword + operation: resetUserPassword - setHeader: name: CamelKeycloakRoleName constant: "admin" @@ -794,14 +794,15 @@ YAML:: operation: assignRoleToUser - transform: constant: "User environment setup completed successfully" +---- + +The component configuration, in `application.properties`: -# Component configuration -camel: - component: - keycloak: - server-url: "http://localhost:8080" - realm: "master" - username: "admin" - password: "admin" +[source,properties] +---- +camel.component.keycloak.server-url = http://localhost:8080 +camel.component.keycloak.realm = master +camel.component.keycloak.username = admin +camel.component.keycloak.password = admin ---- ==== diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-security.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-security.adoc index e4b7da310e94..eebdd1767291 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-security.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/keycloak-security.adoc @@ -1006,7 +1006,9 @@ YAML:: - onException: exception: - "org.apache.camel.CamelAuthorizationException" - handled: true + handled: + constant: + expression: "true" steps: - setHeader: name: "CamelHttpResponseCode" diff --git a/components/camel-ai/camel-docling/src/main/docs/docling-batch-processing.adoc b/components/camel-ai/camel-docling/src/main/docs/docling-batch-processing.adoc index 477e747a0f1e..80d99254369f 100644 --- a/components/camel-ai/camel-docling/src/main/docs/docling-batch-processing.adoc +++ b/components/camel-ai/camel-docling/src/main/docs/docling-batch-processing.adoc @@ -174,8 +174,6 @@ YAML:: - beans: - name: documentListProcessor type: "#class:org.apache.camel.processor.aggregate.GroupedBodyAggregationStrategy" - properties: - strategyMethodName: "aggregate" # Route 1: Collect documents - route: @@ -205,14 +203,15 @@ YAML:: concurrentConsumers: 1 steps: - aggregate: - aggregationStrategy: - bean: "documentListProcessor" + aggregationStrategy: documentListProcessor correlationExpression: - constant: true + constant: + expression: "true" completionSize: 10 completionTimeout: 5000 - - to: - uri: direct:batch-process + steps: + - to: + uri: direct:batch-process # Route 3: Process batch - route: diff --git a/components/camel-keycloak/src/main/docs/keycloak-consumer.adoc b/components/camel-keycloak/src/main/docs/keycloak-consumer.adoc index 9c0484ac670c..f2f0bb098fa4 100644 --- a/components/camel-keycloak/src/main/docs/keycloak-consumer.adoc +++ b/components/camel-keycloak/src/main/docs/keycloak-consumer.adoc @@ -563,21 +563,21 @@ YAML:: expression: simple: expression: "${body.type} == 'LOGIN_ERROR'" - - aggregate: - correlationExpression: - expression: - simple: - expression: "${body.ipAddress}" - aggregationStrategy: "#arrayListAggregation" - completionSize: 5 - completionTimeout: 300000 steps: - - log: - message: "SECURITY ALERT: Multiple failed login attempts from ${body[0].ipAddress}" - - to: - uri: direct:block-ip - - to: - uri: direct:send-security-alert + - aggregate: + correlationExpression: + simple: + expression: "${body.ipAddress}" + aggregationStrategy: "#class:org.apache.camel.processor.aggregate.GroupedBodyAggregationStrategy" + completionSize: 5 + completionTimeout: 300000 + steps: + - log: + message: "SECURITY ALERT: Multiple failed login attempts from ${body[0].ipAddress}" + - to: + uri: direct:block-ip + - to: + uri: direct:send-security-alert ---- ==== diff --git a/components/camel-keycloak/src/main/docs/keycloak-producer.adoc b/components/camel-keycloak/src/main/docs/keycloak-producer.adoc index fe3c24e952a6..320d98815f57 100644 --- a/components/camel-keycloak/src/main/docs/keycloak-producer.adoc +++ b/components/camel-keycloak/src/main/docs/keycloak-producer.adoc @@ -784,7 +784,7 @@ YAML:: - to: uri: keycloak:admin parameters: - operation: setUserPassword + operation: resetUserPassword - setHeader: name: CamelKeycloakRoleName constant: "admin" @@ -794,14 +794,15 @@ YAML:: operation: assignRoleToUser - transform: constant: "User environment setup completed successfully" +---- + +The component configuration, in `application.properties`: -# Component configuration -camel: - component: - keycloak: - server-url: "http://localhost:8080" - realm: "master" - username: "admin" - password: "admin" +[source,properties] +---- +camel.component.keycloak.server-url = http://localhost:8080 +camel.component.keycloak.realm = master +camel.component.keycloak.username = admin +camel.component.keycloak.password = admin ---- ==== diff --git a/components/camel-keycloak/src/main/docs/keycloak-security.adoc b/components/camel-keycloak/src/main/docs/keycloak-security.adoc index e4b7da310e94..eebdd1767291 100644 --- a/components/camel-keycloak/src/main/docs/keycloak-security.adoc +++ b/components/camel-keycloak/src/main/docs/keycloak-security.adoc @@ -1006,7 +1006,9 @@ YAML:: - onException: exception: - "org.apache.camel.CamelAuthorizationException" - handled: true + handled: + constant: + expression: "true" steps: - setHeader: name: "CamelHttpResponseCode" 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 b4a883b0872a..c647acfaceb3 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 @@ -34,6 +34,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import org.apache.camel.catalog.CamelCatalog; +import org.apache.camel.dsl.yaml.validator.DocBlocks; import org.apache.camel.tooling.model.ComponentModel; import org.apache.camel.tooling.model.DataFormatModel; import org.apache.camel.tooling.model.EipModel; @@ -195,7 +196,6 @@ public final class CatalogSamples { return sample(catalog, null, name, limit); } - private static final Pattern YAML_BLOCK = Pattern.compile("\\[source,yaml\\]\\s*\\n----\\n(.*?)\\n----", Pattern.DOTALL); private static final Map<String, Found> DOC_CACHE = new ConcurrentHashMap<>(); /** The validated examples of a page family, and for a component whether any of them uses its endpoint. */ @@ -274,13 +274,10 @@ public final class CatalogSamples { if (doc == null) { continue; } - Matcher m = YAML_BLOCK.matcher(doc); - while (m.find()) { - String yaml = m.group(1).stripTrailing() + "\n"; - if (!yaml.stripLeading().startsWith("- ")) { - // a fragment (an option list, a manifest), not a route file - continue; - } + // the route examples as the doc-samples plugin and the documentation tests read them (CAMEL-25736): a + // longer delimiter and a leading comment are fine, callout markers are left out, and a block marked as + // what to avoid is no sample; the fragments (an option list, a manifest) are not route files + for (String yaml : DocBlocks.examples(doc)) { if (!SourceValidator.validateYamlSchema(yaml, catalog).isEmpty()) { continue; } diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/DocBlocks.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/DocBlocks.java index 99f19e3523b8..70dd9bf73c89 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/DocBlocks.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/DocBlocks.java @@ -18,6 +18,7 @@ package org.apache.camel.dsl.yaml.validator; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -34,7 +35,11 @@ public final class DocBlocks { /** The AsciiDoc comment that keeps a block out of the examples. */ public static final String SKIP_MARKER = "// yaml-validator: skip"; - private static final Pattern YAML_BLOCK = Pattern.compile("\\[source,yaml\\]\\s*\\n----\\n(.*?)\\n----", + /** + * A YAML listing block: four or more dashes, closed by the same line. A delimiter longer than four dashes is valid + * AsciiDoc and common in older pages, and their examples were not read (CAMEL-25736). + */ + private static final Pattern YAML_BLOCK = Pattern.compile("\\[source,yaml\\]\\s*\\n(-{4,})\\n(.*?)\\n\\1(?=\\n|$)", Pattern.DOTALL); /** An AsciiDoc callout marker at the end of a line, which is documentation and not part of the route. */ @@ -47,8 +52,8 @@ public final class DocBlocks { } /** - * The route examples of the page: the YAML blocks that start with a top-level list entry, without their callout - * markers, and without the blocks that carry {@value #SKIP_MARKER}. + * The route examples of the page: the YAML blocks that start with a top-level list entry (after the comment lines + * that may come first), without their callout markers, and without the blocks that carry {@value #SKIP_MARKER}. */ public static List<String> examples(String doc) { List<String> answer = new ArrayList<>(); @@ -57,14 +62,52 @@ public final class DocBlocks { } Matcher m = YAML_BLOCK.matcher(doc); while (m.find()) { - String yaml = CALLOUT.matcher(m.group(1)).replaceAll("").stripTrailing() + "\n"; - if (yaml.stripLeading().startsWith("- ") && !markedToSkip(doc, m.start())) { + String yaml = CALLOUT.matcher(m.group(2)).replaceAll("").stripTrailing() + "\n"; + if (startsWithEntry(yaml) && !markedToSkip(doc, m.start())) { answer.add(yaml); } } return answer; } + /** The entries a YAML DSL file is a list of. */ + private static final Set<String> TOP_LEVEL = Set.of("beans", "dataFormats", "errorHandler", "from", "intercept", + "interceptFrom", "interceptSendToEndpoint", "kamelet", "onCompletion", "onException", "rest", + "restConfiguration", "route", "route-configuration", "route-template", "routeConfiguration", "routeTemplate", + "sslContextParameters", "templated-route", "templatedRoute", "transformers", "validators"); + + /** The key of a list entry, as route in "- route:". */ + private static final Pattern ENTRY_KEY = Pattern.compile("- ([\\w-]+):"); + + /** + * Whether the YAML is a route file: its first line that is not blank or a comment is a list entry. After a comment + * it must be a top-level entry: a block that opens with "# send the dataset to a queue" and then - route: is a + * route file, and was skipped (CAMEL-25736), while a list of steps with a comment above it is a fragment of a + * route. + */ + static boolean startsWithEntry(String yaml) { + boolean comment = false; + for (String line : yaml.split("\n")) { + String s = line.strip(); + if (s.isEmpty()) { + continue; + } + if (s.startsWith("#")) { + comment = true; + continue; + } + if (!s.startsWith("- ")) { + return false; + } + if (!comment) { + return true; + } + Matcher m = ENTRY_KEY.matcher(s); + return m.lookingAt() && TOP_LEVEL.contains(m.group(1)); + } + return false; + } + /** Whether the block at this offset carries {@link #SKIP_MARKER} in the lines before it. */ public static boolean markedToSkip(String doc, int blockStart) { int from = Math.max(0, blockStart - MARKER_LOOKBEHIND); diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/DocBlocksTest.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/DocBlocksTest.java new file mode 100644 index 000000000000..81a46beff35b --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/DocBlocksTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.yaml.validator; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * CAMEL-25736: the route examples of a page include the blocks with a delimiter longer than four dashes, and the blocks + * that open with a comment; 53 examples of the documentation were not read before. + */ +class DocBlocksTest { + + @Test + void aLongerDelimiterAndALeadingCommentAreRead() { + String doc = """ + = A page + + [source,yaml] + -------------------------------------------------------------------------------- + - from: + uri: direct:search + steps: + - log: + message: "${body}" + -------------------------------------------------------------------------------- + + [source,yaml] + ---- + # send the dataset to a queue + - route: + from: + uri: dataset:foo + steps: + - to: + uri: seda:queue + ---- + + [source,yaml] + ---- + key: a fragment, not a route + ---- + + [source,yaml] + ---- + # the steps to add to the route: a fragment too + - setHeader: + name: CamelJmsDestinationName + expression: + constant: + expression: "queue:///MY_QUEUE" + ---- + """; + List<String> examples = DocBlocks.examples(doc); + assertEquals(2, examples.size(), String.valueOf(examples)); + assertTrue(examples.get(0).startsWith("- from:"), examples.get(0)); + assertTrue(examples.get(1).startsWith("# send the dataset to a queue\n- route:"), examples.get(1)); + } + + @Test + void aBlockClosesAtItsOwnDelimiter() { + // a shorter or a longer line of dashes inside the block is part of it + String doc = "[source,yaml]\n--------\n- from:\n uri: direct:a\n steps:\n - log:\n" + + " message: x\n----\n----------\n--------\nafter\n"; + List<String> examples = DocBlocks.examples(doc); + assertEquals(1, examples.size()); + assertTrue(examples.get(0).endsWith("----\n----------\n"), examples.get(0)); + } +}
