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

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

commit 7c7a2beca8a637e78da6a244fd8f0de2bbb3aef9
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jan 28 11:40:56 2026 +0100

    CAMEL-22899: camel-core - Add chain operator to simple language
---
 .../modules/languages/pages/simple-language.adoc   | 59 ++++++++++++++++++++++
 .../camel/language/simple/SimpleTokenizer.java     | 18 ++++---
 .../camel/language/simple/ast/OtherExpression.java | 26 ++++++++++
 .../language/simple/types/OtherOperatorType.java   | 14 ++++-
 .../camel/language/simple/SimpleOperatorTest.java  | 17 +++++++
 5 files changed, 124 insertions(+), 10 deletions(-)

diff --git 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
index aea31cef6a0d..6f1912d6c81d 100644
--- 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
+++ 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
@@ -772,6 +772,28 @@ The following numeric operators can be used:
 |`--` | To decrement a number by one. The left-hand side must be a function, 
otherwise parsed as literal.
 |====
 
+These operators are unary and requires to be attached directly next to a 
function:
+
+[source,text]
+----
+${function}OP
+----
+
+For example to increment the result of the function:
+
+[source,java]
+----
+simple("${header.myNumber}++ > 10");
+----
+
+And the same for decrement:
+
+[source,java]
+----
+simple("${header.myNumber}-- < 10");
+----
+
+
 === Other Operators
 
 The following other operators can be used:
@@ -781,8 +803,12 @@ The following other operators can be used:
 |Operator |Description
 |`? :` | The ternary operator evaluates a condition and returns a value based 
on the result. If the condition is true, the first value (after `?`) is 
returned; otherwise, the second value (after `:`) is returned. There must be 
spaces around both `?` and `:` operators. This is similar to the ternary 
operator in Java.
 |`?:` | The elvis operator returns the left-hand side if it has an effective 
Boolean value of true, otherwise it returns the right-hand side. This is useful 
for providing fallback values when an expression may evaluate to a value with 
an effective Boolean value of false (such as `null`, `false`, `0`, empty/blank 
string).
+|`~>` | The chain operator is used in the situations where multiple nested 
functions need to be applied to a value, while making it easy to read. The 
value on the left-hand-side is evaluated, and set as the new message body 
before evaluating the right-hand-side function. This concept is similar to the 
xref:eips:pipeline-eip.adoc[Pipeline EIP].
+|`?~>` | The null-safe chain operator, where the chain will not continue when 
a function returned `null`.
 |====
 
+==== Ternary Operator
+
 The syntax for the ternary operator is:
 
 [source,text]
@@ -804,6 +830,8 @@ Ternary operators can also be nested to handle multiple 
conditions:
 simple("${header.score >= 90 ? 'A' : ${header.score >= 80 ? 'B' : 'C'}}");
 ----
 
+==== Elvis Operator
+
 For example the following elvis operator will return the username header 
unless its null or empty, which
 then the default value of `Guest` is returned.
 
@@ -812,6 +840,37 @@ then the default value of `Guest` is returned.
 simple("${header.username} ?: 'Guest'");
 ----
 
+==== Chain Operator
+
+IMPORTANT: The chain operator only supports passing results via the message 
body. This may change in the future to allow more flexible syntax to
+specify which parameter to use as input in the next fuction.
+
+The syntax for the chain operator is:
+
+[source,text]
+----
+${leftValue} ~> ${rightValue}
+----
+
+And there can be as many chains:
+
+[source,text]
+----
+${leftValue} ~> ${midValue} ~> ${midValue} -> ${rightValue}
+----
+
+For example if the message body contains `Hello World` then the follow would 
return `WORLD`:
+
+[source,java]
+----
+simple("${substringAfter('Hello')} ~> ${trim()} ~> ${uppercase()}");
+----
+
+The _null safe_ variant (`?~>`) can be used to avoid `NullPointerException` if 
it's accepted to not continue the chain when any function returned `null`:
+
+However, many of the simple functions have NPE protection built-in, so this 
variant is only needed in special situations.
+
+
 === Boolean Operators
 
 And the following boolean operators can be used to group expressions:
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
index 6a55dfafe83c..e295d44127c2 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
@@ -27,7 +27,7 @@ import org.apache.camel.util.ObjectHelper;
 public class SimpleTokenizer {
 
     // keep this number in sync with tokens list
-    private static final int NUMBER_OF_TOKENS = 52;
+    private static final int NUMBER_OF_TOKENS = 54;
 
     private static final SimpleTokenType[] KNOWN_TOKENS = new 
SimpleTokenType[NUMBER_OF_TOKENS];
 
@@ -87,23 +87,25 @@ public class SimpleTokenizer {
 
         // other operators
         KNOWN_TOKENS[44] = new SimpleTokenType(TokenType.otherOperator, "?:");
+        KNOWN_TOKENS[45] = new SimpleTokenType(TokenType.otherOperator, "~>");
+        KNOWN_TOKENS[46] = new SimpleTokenType(TokenType.otherOperator, "?~>");
 
         // unary operators
-        KNOWN_TOKENS[45] = new SimpleTokenType(TokenType.unaryOperator, "++");
-        KNOWN_TOKENS[46] = new SimpleTokenType(TokenType.unaryOperator, "--");
+        KNOWN_TOKENS[47] = new SimpleTokenType(TokenType.unaryOperator, "++");
+        KNOWN_TOKENS[48] = new SimpleTokenType(TokenType.unaryOperator, "--");
 
         // logical operators
-        KNOWN_TOKENS[47] = new SimpleTokenType(TokenType.logicalOperator, 
"&&");
-        KNOWN_TOKENS[48] = new SimpleTokenType(TokenType.logicalOperator, 
"||");
+        KNOWN_TOKENS[49] = new SimpleTokenType(TokenType.logicalOperator, 
"&&");
+        KNOWN_TOKENS[50] = new SimpleTokenType(TokenType.logicalOperator, 
"||");
 
         // ternary operators
-        KNOWN_TOKENS[49] = new SimpleTokenType(TokenType.ternaryOperator, "?");
-        KNOWN_TOKENS[50] = new SimpleTokenType(TokenType.ternaryOperator, ":");
+        KNOWN_TOKENS[51] = new SimpleTokenType(TokenType.ternaryOperator, "?");
+        KNOWN_TOKENS[52] = new SimpleTokenType(TokenType.ternaryOperator, ":");
 
         //binary operator
         // it is added as the last item because unary -- has the priority
         // if unary not found it is highly possible - operator is run into.
-        KNOWN_TOKENS[51] = new SimpleTokenType(TokenType.minusValue, "-");
+        KNOWN_TOKENS[53] = new SimpleTokenType(TokenType.minusValue, "-");
     }
 
     /**
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java
index 09902ffd6fe0..b7043021c253 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/OtherExpression.java
@@ -87,6 +87,9 @@ public class OtherExpression extends BaseSimpleNode {
 
         if (operator == OtherOperatorType.ELVIS) {
             return createElvisExpression(camelContext, leftExp, rightExp);
+        } else if (operator == OtherOperatorType.CHAIN || operator == 
OtherOperatorType.CHAIN_NULL_SAFE) {
+            boolean nullSafe = operator == OtherOperatorType.CHAIN_NULL_SAFE;
+            return createChainExpression(camelContext, leftExp, rightExp, 
nullSafe);
         }
 
         throw new SimpleParserException("Unknown other operator " + operator, 
token.getIndex());
@@ -112,6 +115,27 @@ public class OtherExpression extends BaseSimpleNode {
         };
     }
 
+    private Expression createChainExpression(
+            final CamelContext camelContext, final Expression leftExp, final 
Expression rightExp, boolean nullSafe) {
+        return new Expression() {
+            @Override
+            public <T> T evaluate(Exchange exchange, Class<T> type) {
+                // left is input to right
+                Object value = leftExp.evaluate(exchange, Object.class);
+                if (value == null && nullSafe) {
+                    return null; // break out
+                }
+                exchange.getMessage().setBody(value);
+                return rightExp.evaluate(exchange, type);
+            }
+
+            @Override
+            public String toString() {
+                return left + " " + token.getText() + " " + right;
+            }
+        };
+    }
+
     @Override
     public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         return BaseSimpleParser.CODE_START + doCreateCode(camelContext, 
expression) + BaseSimpleParser.CODE_END;
@@ -137,6 +161,8 @@ public class OtherExpression extends BaseSimpleNode {
 
         if (operator == OtherOperatorType.ELVIS) {
             return "elvis(exchange, " + leftExp + ", " + rightExp + ")";
+        } else if (operator == OtherOperatorType.CHAIN) {
+            throw new SimpleParserException("Chain operator " + operator + " 
not supported in csimple", token.getIndex());
         }
 
         throw new SimpleParserException("Unknown other operator " + operator, 
token.getIndex());
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/OtherOperatorType.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/OtherOperatorType.java
index 7ae09a14339d..8440d3a6a797 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/OtherOperatorType.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/OtherOperatorType.java
@@ -21,17 +21,27 @@ package org.apache.camel.language.simple.types;
  */
 public enum OtherOperatorType {
 
+    CHAIN,
+    CHAIN_NULL_SAFE,
     ELVIS;
 
     public static OtherOperatorType asOperator(String text) {
-        if ("?:".equals(text)) {
+        if ("~>".equals(text)) {
+            return CHAIN;
+        } else if ("?~>".equals(text)) {
+            return CHAIN_NULL_SAFE;
+        } else if ("?:".equals(text)) {
             return ELVIS;
         }
         throw new IllegalArgumentException("Operator not supported: " + text);
     }
 
     public static String getOperatorText(OtherOperatorType operator) {
-        if (operator == ELVIS) {
+        if (operator == CHAIN) {
+            return "~>";
+        } else if (operator == CHAIN_NULL_SAFE) {
+            return "?~>";
+        } else if (operator == ELVIS) {
             return "?:";
         }
         return "";
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java
index 2fe58b9b5b8c..d4a054783ea1 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleOperatorTest.java
@@ -894,6 +894,23 @@ public class SimpleOperatorTest extends 
LanguageTestSupport {
                 ">>> Message received from WebSocket Client : Hello World");
     }
 
+    @Test
+    public void testChain() {
+        exchange.getIn().setBody(null);
+        assertExpression("${substringAfter('Hello')} ~> ${trim()} ~> 
${uppercase()}", null);
+        exchange.getIn().setBody("Hello World");
+        assertExpression("${substringAfter('Hello')} ~> ${trim()} ~> 
${uppercase()}", "WORLD");
+    }
+
+    @Test
+    public void testChainNullSafe() {
+        exchange.getIn().setBody(null);
+        assertExpression("${substringAfter('Hello')} ?~> ${collate(2)} ~> 
${uppercase()}", null);
+
+        exchange.getIn().setBody("Hello World,Hello Camel");
+        assertExpression("${substringAfter('Hello')} ?~> ${collate(2)} ~> 
${kindOfType()}", "object");
+    }
+
     @Override
     protected String getLanguageName() {
         return "simple";

Reply via email to