This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch pr/CAMEL-24698-source-checks in repository https://gitbox.apache.org/repos/asf/camel.git
commit 54d8c93b1641d8627a4e673c70cc0e5620d66bf4 Author: Claus Ibsen <[email protected]> AuthorDate: Sun Sep 13 15:29:30 2026 +0200 CAMEL-24692: camel-jbang - the Simple check skips only a placeholder used as an operand of a logical operator Every placeholder-only expression was skipped as a workaround for the catalog rejecting placeholders as operands (fixed upstream in CAMEL-24692). A placeholder used as a value is validated now; one used as an operand of && or || is not, since it can expand to a whole predicate the catalog cannot know. Moved here from PR 26350, whose file no longer holds the validation since CAMEL-24695. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj --- .../dsl/jbang/core/commands/ai/SimpleChecks.java | 53 ++++++++++++++- .../ai/SourceValidatorPlaceholderGuardTest.java | 78 ++++++++++++++++++++++ 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SimpleChecks.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SimpleChecks.java index 2ef42d5654c9..51832938609b 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SimpleChecks.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/SimpleChecks.java @@ -96,8 +96,8 @@ final class SimpleChecks { if (simpleText == null || simpleText.isEmpty()) { continue; } - // Skip placeholder-only expressions - if (simpleText.startsWith("{{") && simpleText.endsWith("}}")) { + // Skip what the catalog cannot validate because a placeholder is unresolved + if (hasPlaceholderAsLogicalOperand(simpleText)) { continue; } @@ -161,4 +161,53 @@ final class SimpleChecks { return false; } + /** + * Whether the text uses a property placeholder as an operand of a logical operator, such as + * <tt>{{enabled}} && ${body} > 10</tt>. + * + * A placeholder can expand to an entire predicate, which the catalog cannot know as it validates without a running + * Camel application. The catalog substitutes a placeholder with a dummy value, which is what an operand of a binary + * operator needs, but a logical operator needs a predicate on either side. Validating those would report an error + * for a route that is perfectly valid at runtime, so they are skipped. + */ + public static boolean hasPlaceholderAsLogicalOperand(String text) { + if (text == null || !text.contains("{{")) { + return false; + } + + // split into the operands of the logical operators, ignoring any quoted literal + List<String> operands = new ArrayList<>(); + char quote = 0; + int start = 0; + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); + if (quote == 0 && (ch == '\'' || ch == '"')) { + quote = ch; + } else if (quote == ch) { + quote = 0; + } else if (quote == 0 && i < text.length() - 1) { + char next = text.charAt(i + 1); + if (ch == '&' && next == '&' || ch == '|' && next == '|') { + operands.add(text.substring(start, i)); + i++; + start = i + 1; + } + } + } + if (operands.isEmpty()) { + // no logical operator so the placeholders are all used as a value which the catalog can validate + return false; + } + operands.add(text.substring(start)); + + for (String operand : operands) { + String s = operand.trim(); + // is the operand nothing but a single placeholder + if (s.startsWith("{{") && s.endsWith("}}") && s.indexOf("}}") == s.length() - 2) { + return true; + } + } + return false; + } + } diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorPlaceholderGuardTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorPlaceholderGuardTest.java new file mode 100644 index 000000000000..8a4a854dc10d --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/SourceValidatorPlaceholderGuardTest.java @@ -0,0 +1,78 @@ +/* + * 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.jbang.core.commands.ai; + +import java.util.List; + +import org.apache.camel.catalog.CamelCatalog; +import org.apache.camel.catalog.DefaultCamelCatalog; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * CAMEL-24692: a placeholder used as a value is validated (the catalog substitutes it), a placeholder used as an + * operand of a logical operator is not (it can expand to a whole predicate the catalog cannot know). + */ +class SourceValidatorPlaceholderGuardTest { + + private static final CamelCatalog CATALOG = new DefaultCamelCatalog(); + + @Test + void placeholderAsValueIsValidated() { + assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand("{{hot.threshold}}")); + assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand("${body} >= {{hot.threshold}}")); + assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand("{{a}} == {{b}}")); + assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand("${body} >= {{t}} && ${body} < {{u}}")); + assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand("${body} == 'abc'")); + assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand(null)); + } + + @Test + void placeholderAsLogicalOperandIsSkipped() { + assertTrue(SimpleChecks.hasPlaceholderAsLogicalOperand("{{a}} && {{b}}")); + assertTrue(SimpleChecks.hasPlaceholderAsLogicalOperand("${body} > 1 && {{flag}}")); + assertTrue(SimpleChecks.hasPlaceholderAsLogicalOperand("{{flag}} || ${body} > 1")); + // inside a quoted literal it is text + assertFalse(SimpleChecks.hasPlaceholderAsLogicalOperand("${body} == '{{a}} && {{b}}'")); + } + + @Test + void placeholderPredicatesAreNotReportedAsErrors() { + String yaml = """ + - from: + uri: timer:tick + steps: + - filter: + simple: "${body} >= {{hot.threshold}}" + steps: + - log: hot + - filter: + simple: "{{flag}} && ${body} > 1" + steps: + - log: flagged + - filter: + simple: "${body" + steps: + - log: broken + """; + List<String> errors = SourceValidator.validateYamlSimple(yaml, CATALOG); + assertTrue(errors.stream().noneMatch(e -> e.contains("hot.threshold") || e.contains("flag")), String.valueOf(errors)); + assertTrue(errors.stream().anyMatch(e -> e.contains("Line 13")), String.valueOf(errors)); + } +}
