This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-24983 in repository https://gitbox.apache.org/repos/asf/camel.git
commit defc344dc6fec7987b20e5752634d273bcdc5045 Author: Claus Ibsen <[email protected]> AuthorDate: Thu Sep 24 08:17:17 2026 +0200 CAMEL-24983: simple - say what to write when ! is used to negate a function Simple has no boolean ! prefix, and a negated function failed with a message that pointed at the operator instead of at the !: ${body != null && !${body.isEmpty()}} -> Logical operator && needs a predicate on the right hand side, e.g. ${header.foo} == 'bar'; was: !${body.isEmpty()} The nesting is not the problem - ${body != null && ${body.isEmpty()}} evaluates fine - and the plain ${body != null && !body.isEmpty()} fails the same way. The message now names the ! and the comparison to write: ! does not negate a function: compare it instead, so !${body.isEmpty()} is written as ${body.isEmpty()} == false, or negate the operator (!=, !contains) A negated operator is left alone, so ${body} !contains 'x' is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01Bp3538HRBPMQkb5ta9xRaj --- .../camel/language/simple/SimpleSyntaxHints.java | 22 +++++++++++++++++ .../language/simple/SimpleSyntaxHintsTest.java | 28 ++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleSyntaxHints.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleSyntaxHints.java index 9b80f36a0e8b..ffd5f4710811 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleSyntaxHints.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleSyntaxHints.java @@ -103,6 +103,23 @@ public final class SimpleSyntaxHints { return expression.substring(start, end); } + /** + * The comparison that a negated function is written as, or null when the text is not one: simple has no boolean + * {@code !} prefix, so {@code !${body.isEmpty()}} is {@code ${body.isEmpty()} == false}. Models and people write + * the Java form, and the parser otherwise reports it as a missing predicate next to the operator. + */ + private static String negatedFunction(String word) { + if (word.length() < 2 || word.charAt(0) != '!') { + return null; + } + String rest = word.substring(1); + // !=, !contains and the other negated operators are words of their own, not a negated function + if (rest.isEmpty() || rest.charAt(0) == '=' || Character.isLetter(rest.charAt(0)) && !rest.contains("(")) { + return null; + } + return rest + " == false"; + } + /** The message for a token the grammar does not know at the given index. */ public static String unexpectedToken(String expression, int index) { String word = wordAt(expression, index); @@ -145,6 +162,11 @@ public final class SimpleSyntaxHints { /** The message when an operator has no usable value next to it. */ public static String unsupportedOperand(String kind, Object operator, String expression, int index) { String word = wordAt(expression, index); + String compared = negatedFunction(word); + if (compared != null) { + return "! does not negate a function: compare it instead, so " + word + " is written as " + compared + + ", or negate the operator (!=, !contains)"; + } if ("Logical".equals(kind)) { return kind + " operator " + operator + " needs a predicate on the right hand side, e.g. ${header.foo} == 'bar'" + (word.isEmpty() ? "" : "; was: " + word); diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java index 55de40810c70..ea81f6c87e83 100644 --- a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleSyntaxHintsTest.java @@ -17,6 +17,7 @@ package org.apache.camel.language.simple; import org.apache.camel.ExchangeTestSupport; +import org.apache.camel.Expression; import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException; import org.junit.jupiter.api.Test; @@ -35,6 +36,18 @@ public class SimpleSyntaxHintsTest extends ExchangeTestSupport { return assertThrows(SimpleIllegalSyntaxException.class, parser::parsePredicate).getMessage(); } + /** + * The message of a function that only fails when the node builds its expression, such as one with operators inside + * the {@code ${ }} (CAMEL-24921), which the parsers above accept as a single function token. + */ + private String functionError(String text) { + return assertThrows(SimpleIllegalSyntaxException.class, () -> { + Expression exp = context.resolveLanguage("simple").createExpression(text); + exp.init(context); + exp.evaluate(exchange, Object.class); + }).getMessage(); + } + private String expressionError(String text) { SimpleExpressionParser parser = new SimpleExpressionParser(context, text, true, null); return assertThrows(SimpleIllegalSyntaxException.class, parser::parseExpression).getMessage(); @@ -77,6 +90,21 @@ public class SimpleSyntaxHintsTest extends ExchangeTestSupport { assertThat(predicateError("${body} == 'x' || ")).contains("needs a predicate on the right hand side"); } + @Test + public void testNegatedFunction() { + // simple has no boolean ! prefix; both the nested and the plain form fail the same way, and the message + // says the comparison to write instead (the shapes a local model wrote in the benchmark) + assertThat(functionError("${body != null && !${body.isEmpty()}}")) + .contains("! does not negate a function") + .contains("${body.isEmpty()} == false") + .doesNotContain("needs a predicate on the right hand side"); + assertThat(functionError("${body != null && !body.isEmpty()}")) + .contains("! does not negate a function") + .contains("body.isEmpty() == false"); + // a negated operator is not a negated function + assertThat(predicateError("${body} !contains")).doesNotContain("does not negate a function"); + } + @Test public void testQuotes() { assertThat(predicateError("${body} == 'it''s'")).contains("double quotes");
