This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 42873b157acf CAMEL-26311: camel-core-languages - Simple: a predicate 
inside ${ } with equals, !equals, !startsWith or !endsWith, or with a quoted 
argument on the left, is read as one (#28714)
42873b157acf is described below

commit 42873b157acfa29919e8fb0e63c13907f9981c97
Author: allthingssecurity <[email protected]>
AuthorDate: Fri Oct 30 21:58:37 2026 +0530

    CAMEL-26311: camel-core-languages - Simple: a predicate inside ${ } with 
equals, !equals, !startsWith or !endsWith, or with a quoted argument on the 
left, is read as one (#28714)
    
    A predicate inside ${ } with equals, !equals, !startsWith or !endsWith, or 
with a quoted argument on the left, is now read as one predicate. The syntax 
hints list every operator. Upgrade guide entry added.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../camel/language/simple/SimpleSyntaxHints.java   | 34 ++++++----------
 .../simple/SimplePredicateInBracesTest.java        | 46 ++++++++++++++++++++++
 .../language/simple/SimpleSyntaxHintsTest.java     | 11 ++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    | 10 +++++
 4 files changed, 79 insertions(+), 22 deletions(-)

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 900c9a3b02a1..a6be4d341287 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
@@ -28,8 +28,9 @@ import java.util.Set;
  */
 public final class SimpleSyntaxHints {
 
-    public static final String OPERATORS = "==, =~, !=, <, <=, >, >=, 
contains, !contains, ~~, !~~, regex, !regex, in, !in, "
-                                           + "is, !is, range, !range, 
startsWith, endsWith, && and ||";
+    public static final String OPERATORS = "==, =~, !=, !=~, <, <=, >, >=, 
equals, !equals, contains, !contains, ~~, !~~, "
+                                           + "regex, !regex, in, !in, is, !is, 
range, !range, startsWith, !startsWith, "
+                                           + "endsWith, !endsWith, && and ||";
 
     /** The value forms an operator accepts on its right hand side. */
     public static final String VALUE_FORMS = "a quoted literal 'x', a number, 
true, false, null, or a function ${...}";
@@ -82,7 +83,8 @@ public final class SimpleSyntaxHints {
 
     private static final String[] OPERATOR_WORDS = {
             "==", "!=", ">=", "<=", ">", "<", "=~", "!=~", "~~", "!~~", 
"contains",
-            "!contains", "regex", "!regex", "in", "!in", "is", "!is", "range", 
"!range", "startsWith", "endsWith", "&&", "||" };
+            "!contains", "regex", "!regex", "in", "!in", "is", "!is", "range", 
"!range", "startsWith", "!startsWith",
+            "endsWith", "!endsWith", "equals", "!equals", "&&", "||" };
 
     private SimpleSyntaxHints() {
     }
@@ -187,7 +189,7 @@ public final class SimpleSyntaxHints {
             " >= ", " <= ", " > ", " < ", " == ", " != ", " =~ ", " !=~ ",
             " contains ", " !contains ", " ~~ ", " !~~ ", " regex ", " !regex 
",
             " in ", " !in ", " is ", " !is ", " range ", " !range ",
-            " startsWith ", " !startsWith ", " endsWith ", " !endsWith " };
+            " startsWith ", " !startsWith ", " endsWith ", " !endsWith ", " 
equals ", " !equals " };
 
     /**
      * Wraps the function references of a predicate written inside {@code ${ 
}} so that it can be parsed as one:
@@ -378,7 +380,10 @@ public final class SimpleSyntaxHints {
         int best = -1;
         String bestOp = null;
         for (String op : OPERATOR_WORDS) {
-            int i = function.indexOf(" " + op + " ");
+            // an operator inside quotes belongs to a literal, such as the 
argument of ${uppercase('a') == 'A'}: the
+            // first one outside quotes, brackets and parentheses is the 
operator, so a function with a quoted argument
+            // can be compared
+            int i = indexOutsideQuotes(function, " " + op + " ");
             if (i > 0 && (best < 0 || i < best)) {
                 best = i;
                 bestOp = op;
@@ -387,24 +392,9 @@ public final class SimpleSyntaxHints {
         if (bestOp == null) {
             return null;
         }
+        // an operator inside an argument list that may hold a predicate (iif, 
filter, forEach), or inside a key such as
+        // ${header[order in progress]}, is not at the top level, so 
indexOutsideQuotes has skipped it
         String head = function.substring(0, best);
-        if (head.contains("'") || head.contains("\"")) {
-            return null;
-        }
-        int open = 0;
-        for (int i = 0; i < head.length(); i++) {
-            char c = head.charAt(i);
-            if (c == '(' || c == '[') {
-                open++;
-            } else if (c == ')' || c == ']') {
-                open--;
-            }
-        }
-        if (open > 0) {
-            // the operator is inside an argument list that may hold a 
predicate (iif, filter, forEach),
-            // or inside a key such as ${header[order in progress]}
-            return null;
-        }
         if (head.startsWith("properties:") && head.indexOf(':', 11) > 0) {
             // the operator word is in the default value: 
${properties:msg:value is not set}
             return null;
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateInBracesTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateInBracesTest.java
index 970adaa9eb77..fe155c01c7b8 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateInBracesTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimplePredicateInBracesTest.java
@@ -93,6 +93,52 @@ public class SimplePredicateInBracesTest extends 
LanguageTestSupport {
         assertPredicate("${header[n] == 5}", true);
     }
 
+    @Test
+    void testEveryComparisonOperatorInsideTheBraces() {
+        // equals and !equals (CAMEL-24582), !startsWith and !endsWith 
(CAMEL-22868) are operators inside the braces
+        // too; the predicate answered false and the expression null, as the 
text was read as one header name
+        exchange.getIn().setHeader("type", "gold");
+        assertPredicate("${header.type equals 'gold'}", true);
+        assertPredicate("${header.type equals 'silver'}", false);
+        assertPredicate("${header.type !equals 'silver'}", true);
+        assertPredicate("${header.type !startsWith 'si'}", true);
+        assertPredicate("${header.type !endsWith 'ld'}", false);
+        assertExpression("${header.type equals 'gold'}", true);
+        // the same answers as outside the braces
+        assertPredicate("${header.type} !startsWith 'si'", true);
+        assertPredicate("${header.type} !endsWith 'ld'", false);
+    }
+
+    @Test
+    void testFunctionWithAQuotedArgumentOnTheLeft() {
+        // the comparison was dropped and the value of the function used: the 
predicate was true for any non-empty value
+        exchange.getIn().setBody("Hello World");
+        assertPredicate("${substringAfter('Hello ') == 'World'}", true);
+        assertPredicate("${substringAfter('Hello ') == 'Camel'}", false);
+        assertPredicate("${uppercase('a') == 'B'}", false);
+        assertExpression("${uppercase('a') == 'A'}", true);
+        assertPredicate("${concat('a','b') == 'ab' && body contains 'World'}", 
true);
+        // a bracket or a parenthesis inside the quotes is text too
+        assertPredicate("${replace('[', '(') != 'Hello World'}", false);
+        assertPredicate("${replace('(', '[') == 'Hi'}", false);
+    }
+
+    @Test
+    void testAnOperatorInsideQuotesIsPartOfTheArgument() {
+        exchange.getIn().setBody("a == b");
+        assertExpression("${replace(' == ','=')}", "a=b");
+        assertExpression("${properties:msg:a == b}", "a == b");
+    }
+
+    @Test
+    void testEqualsInTheConditionOfATernaryInsideTheBraces() {
+        // the condition of a ternary is wrapped with the same operators: 
equals was not one of them, so the condition
+        // was refused as text outside ${ }
+        exchange.getIn().setHeader("type", "gold");
+        assertExpression("${header.type equals 'gold' ? 'yes' : 'no'}", "yes");
+        assertExpression("${header.type !equals 'gold' ? 'yes' : 'no'}", "no");
+    }
+
     @Test
     public void testTheTernaryStillWins() {
         exchange.getIn().setHeader("n", 5);
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 412106724618..0c353aa0bbad 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
@@ -95,6 +95,17 @@ public class SimpleSyntaxHintsTest extends 
ExchangeTestSupport {
         assertThat(predicateError("${body} == 'x' || ")).contains("needs a 
predicate on the right hand side");
     }
 
+    @Test
+    public void testUnknownOperatorListsEveryOperator() {
+        // the hint lists every binary operator of the tokenizer, including 
the negated and the text-only ones
+        String message = predicateError("${body} = 'x'");
+        String operators = message.substring(message.indexOf("Operators are ") 
+ 14, message.indexOf(" at location"));
+        List<String> listed = List.of(operators.split(", | and "));
+        assertThat(listed).containsExactlyInAnyOrder("==", "=~", "!=", "!=~", 
"<", "<=", ">", ">=", "equals", "!equals",
+                "contains", "!contains", "~~", "!~~", "regex", "!regex", "in", 
"!in", "is", "!is", "range", "!range",
+                "startsWith", "!startsWith", "endsWith", "!endsWith", "&&", 
"||");
+    }
+
     @Test
     public void testQuotes() {
         assertThat(predicateError("${body} == 'it''s'")).contains("double 
quotes");
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 21a2e9379c0f..ecaa21722e6b 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -157,6 +157,16 @@ graphql, kubernetes-custom-resources and typesafe-ai) now 
have type converters t
 or `InputStream`, so such a body sent to http or a file failed. A `JsonObject` 
or `JsonArray` that holds a value that
 is not JSON, such as a POJO, converts as before.
 
+=== camel-core - Simple: more predicates inside ${ } are read as predicates
+
+A predicate written inside the braces (CAMEL-24921, 4.23) is now also read as 
one when it uses the operators
+`equals`, `!equals`, `!startsWith` or `!endsWith`, and when its left side is a 
function with a quoted argument, such
+as `+${substringAfter('ID-') == '42'}+`. Before, the whole text was read as 
one function:
+`+${header.type equals 'gold'}+` answered null (a predicate that never 
matches) and `+${uppercase('a') == 'B'}+`
+answered `A` (a predicate that always matches). The condition of a ternary 
inside the braces accepts `equals` and
+`!equals` as well: `+${header.type equals 'gold' ? 'a' : 'b'}+` was refused. 
The same predicates written outside the
+braces, such as `+${header.type} equals 'gold'+`, are unchanged.
+
 === camel-core - Simple: the elvis operator inside the braces
 
 The elvis operator can now be written inside the braces: `${header.limit ?: 
10}` means `${header.limit} ?: 10`.

Reply via email to