This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-24692-tui in repository https://gitbox.apache.org/repos/asf/camel.git
commit 4916178aa310a96d1ff67f54ed0f1b133a010233 Author: Claus Ibsen <[email protected]> AuthorDate: Sat Sep 12 10:04:57 2026 +0200 CAMEL-24692: camel-jbang-plugin-tui - narrow the simple validation placeholder workaround The save-time validation skipped any simple expression that started with {{ and ended with }}, to work around the catalog rejecting a property placeholder used as an operand. The catalog handles that now, so the workaround hid real errors in expressions such as "{{a}} =!= {{b}}". One case remains that the catalog cannot validate: a placeholder used as an operand of a logical operator, as in "{{enabled}} && ${body} > 1". A placeholder can expand to an entire predicate, and the catalog substitutes a dummy value, which a logical operator does not accept on either side. Replace the old workaround with a check for exactly that shape. This is narrower than before, so expressions that are merely placeholder valued are now validated, and it also covers the case the old workaround missed, where the placeholder is not at the start of the expression. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../jbang/core/commands/tui/SourceEditAssist.java | 53 +++++++++++++++++++++- .../commands/tui/SourceEditAssistValidateTest.java | 44 ++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java index a767e0f0bfe0..ccd16eca885d 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssist.java @@ -1617,8 +1617,8 @@ final class SourceEditAssist { 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; } @@ -1646,6 +1646,55 @@ final class SourceEditAssist { return errors; } + /** + * 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. + */ + 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; + } + static String findParentEip(String[] lines, int lineIdx, int lineIndent) { for (int j = lineIdx - 1; j >= 0; j--) { String prev = lines[j]; diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssistValidateTest.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssistValidateTest.java index 78f1a847ae31..0875a793a538 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssistValidateTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceEditAssistValidateTest.java @@ -80,4 +80,48 @@ class SourceEditAssistValidateTest { assertTrue(SourceEditAssist.isValidatableFile("routes.YAML")); assertFalse(SourceEditAssist.isValidatableFile("Foo.java")); } + + @Test + void placeholderAsOperandIsValidated() { + // CAMEL-24692: the catalog can validate these now, so they must no longer be skipped + assertFalse(SourceEditAssist.hasPlaceholderAsLogicalOperand("{{hot.threshold}}")); + assertFalse(SourceEditAssist.hasPlaceholderAsLogicalOperand("${body} >= {{hot.threshold}}")); + assertFalse(SourceEditAssist.hasPlaceholderAsLogicalOperand("{{a}} == {{b}}")); + assertFalse(SourceEditAssist.hasPlaceholderAsLogicalOperand("${body} >= {{t}} && ${body} < {{u}}")); + assertFalse(SourceEditAssist.hasPlaceholderAsLogicalOperand("${body} == 'abc'")); + assertFalse(SourceEditAssist.hasPlaceholderAsLogicalOperand(null)); + } + + @Test + void placeholderAsLogicalOperandIsSkipped() { + // a placeholder can expand to an entire predicate which the catalog cannot know + assertTrue(SourceEditAssist.hasPlaceholderAsLogicalOperand("{{a}} && {{b}}")); + assertTrue(SourceEditAssist.hasPlaceholderAsLogicalOperand("${body} > 1 && {{flag}}")); + assertTrue(SourceEditAssist.hasPlaceholderAsLogicalOperand("{{flag}} || ${body} > 1")); + // a logical operator inside a quoted literal is not an operator + assertFalse(SourceEditAssist.hasPlaceholderAsLogicalOperand("${body} == '{{a}} && {{b}}'")); + } + + @Test + void placeholderPredicateIsNotReportedAsError() { + List<String> errors = assist().validateSource("placeholder.camel.yaml", """ + - route: + id: placeholder + from: + uri: timer:tick + steps: + - choice: + when: + - simple: "${body} >= {{hot.threshold}}" + steps: + - log: + message: "hot" + - simple: "{{enabled}} && ${body} > 1" + steps: + - log: + message: "on" + """); + + assertTrue(errors.stream().noneMatch(e -> e.contains("Simple syntax error")), String.valueOf(errors)); + } }
