This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-24962 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 2c518175f101ede3d52d0b99038816a59fe6a435 Author: Claus Ibsen <[email protected]> AuthorDate: Wed Sep 23 18:39:52 2026 +0200 CAMEL-24969: simple - range(max) starts at 0 as documented; CAMEL-24968: fix substring docs and catalog range(max) now returns 0..max-1 (as Python, and as range(0,max)); it used to start at 1. The substring catalog entry described positions and had wrong examples; the numbers are how many characters to clip from each end. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../org/apache/camel/catalog/docs/simple-functions.adoc | 7 +++++-- .../resources/org/apache/camel/catalog/languages/simple.json | 2 +- .../META-INF/org/apache/camel/language/simple/simple.json | 2 +- .../main/docs/modules/languages/pages/simple-functions.adoc | 7 +++++-- .../org/apache/camel/language/simple/SimpleConstants.java | 12 ++++++------ .../language/simple/functions/CollectionFunctionFactory.java | 2 +- .../java/org/apache/camel/language/simple/SimpleTest.java | 9 +++++++++ .../simple/functions/CollectionFunctionFactoryTest.java | 4 ++-- .../modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 4 ++++ 9 files changed, 34 insertions(+), 15 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc index deeed77f9c76..525d77ecd2f1 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc @@ -526,8 +526,8 @@ The string functions is various functions to work with values that are String an |`safeQuote()` | `String` | Returns the message body safely quoted if needed |`safeQuote(exp)` | `String` | Returns the expression safely quoted if needed |`substring(num1)` | `String` | Returns a substring of the message body. If the number is positive, then the returned string is clipped from the beginning. If the number is negative, then the returned string is clipped from the ending. -|`substring(num1,num2)` | `String` | Returns a substring of the message body. `num1` clips from the beginning, `num2` clips from the ending. Negative values clip from the opposite end. -|`substring(num1,num2,exp)` | `String` | Returns a substring of the given expression. `num1` clips from the beginning, `num2` clips from the ending. Negative values clip from the opposite end. +|`substring(num1,num2)` | `String` | Returns a substring of the message body. `num1` is how many characters to clip from the beginning, and `num2` how many characters to clip from the ending. The sign of `num2` does not matter, so `-1` and `1` both clip one character. +|`substring(num1,num2,exp)` | `String` | Returns a substring of the given expression. `num1` is how many characters to clip from the beginning, and `num2` how many characters to clip from the ending. The sign of `num2` does not matter, so `-1` and `1` both clip one character. |`substringAfter(after)` | `String` | Returns a substring of the message body that comes after. Returns `null` if nothing comes after. |`substringAfter(exp,after)` | `String` | Returns a substring of the expression that comes after. Returns `null` if nothing comes after. |`substringBefore(before)` | `String` | Returns a substring of the message body that comes before. Returns `null` if nothing comes before. @@ -600,6 +600,9 @@ The `substring`, `substringBefore`, and `substringAfter` functions are all simil Suppose the message body contains `ABCDEFGHIJK` then `${substring(3)}` returns `DEFGHIJK`, and `${substring(-3)}` returns `ABCDEFGH`. If you want to clip the first and last character you can use `${substring(1,-1)}` returning `BCDEFGHIJ`. +The numbers are how many characters to clip, not positions as in a Python slice: `${substring(3,-2)}` returns `DEFGHI` +(three characters clipped from the beginning and two from the end), and `${substring(1,0)}` returns `BCDEFGHIJK`. + [NOTE] ==== These `substring` functions are Camel functions with their own semantics (such as negative numbers clipping diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json index 0ca682b5a72c..a1df2fe174f9 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json @@ -159,7 +159,7 @@ "skip(num)": { "index": 130, "kind": "function", "displayName": "Skip First Items from the Message Body", "group": "collection", "label": "collection", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The skip function iterates the message body and skips the first number of items. This can be used with the Splitter EIP to split a message body and skip the first N numbe [...] "split(exp,separator)": { "index": 131, "kind": "function", "displayName": "Split String Values", "group": "collection", "label": "collection", "required": false, "javaType": "String[]", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Splits the message body\/expression as a String value using the separator into a String array", "ognl": false, "suffix": "}", "params": [ { "name": "exp", "javaType": "Object", "required": [...] "stepId": { "index": 132, "kind": "function", "displayName": "Step Id", "group": "core", "label": "core", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns the id of the current step the Exchange is being routed.", "ognl": false, "suffix": "}" }, - "substring(head,tail)": { "index": 133, "kind": "function", "displayName": "Substring", "group": "string", "label": "string", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression. If only one positive number, then the returned string is clipped from the beginning. If only one negative number, then the returned string is clipped from [...] + "substring(head,tail)": { "index": 133, "kind": "function", "displayName": "Substring", "group": "string", "label": "string", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression by clipping characters off its ends (not by positions). With one number, a positive number clips that many characters from the beginning, and a negative num [...] "substringAfter(exp,before)": { "index": 134, "kind": "function", "displayName": "Substring After", "group": "string", "label": "string", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that comes after. Returns null if nothing comes after.", "ognl": false, "suffix": "}", "params": [ { "name": "exp", "javaType": "Object", "requi [...] "substringBefore(exp,before)": { "index": 135, "kind": "function", "displayName": "Substring Before", "group": "string", "label": "string", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that comes before. Returns null if nothing comes before.", "ognl": false, "suffix": "}", "params": [ { "name": "exp", "javaType": "Object", "r [...] "substringBetween(exp,after,before)": { "index": 136, "kind": "function", "displayName": "Substring Between", "group": "string", "label": "string", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that are between after and before. Returns null if nothing comes between.", "ognl": false, "suffix": "}", "params": [ { "name": "exp", [...] diff --git a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json index 0ca682b5a72c..a1df2fe174f9 100644 --- a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json +++ b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json @@ -159,7 +159,7 @@ "skip(num)": { "index": 130, "kind": "function", "displayName": "Skip First Items from the Message Body", "group": "collection", "label": "collection", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The skip function iterates the message body and skips the first number of items. This can be used with the Splitter EIP to split a message body and skip the first N numbe [...] "split(exp,separator)": { "index": 131, "kind": "function", "displayName": "Split String Values", "group": "collection", "label": "collection", "required": false, "javaType": "String[]", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Splits the message body\/expression as a String value using the separator into a String array", "ognl": false, "suffix": "}", "params": [ { "name": "exp", "javaType": "Object", "required": [...] "stepId": { "index": 132, "kind": "function", "displayName": "Step Id", "group": "core", "label": "core", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns the id of the current step the Exchange is being routed.", "ognl": false, "suffix": "}" }, - "substring(head,tail)": { "index": 133, "kind": "function", "displayName": "Substring", "group": "string", "label": "string", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression. If only one positive number, then the returned string is clipped from the beginning. If only one negative number, then the returned string is clipped from [...] + "substring(head,tail)": { "index": 133, "kind": "function", "displayName": "Substring", "group": "string", "label": "string", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression by clipping characters off its ends (not by positions). With one number, a positive number clips that many characters from the beginning, and a negative num [...] "substringAfter(exp,before)": { "index": 134, "kind": "function", "displayName": "Substring After", "group": "string", "label": "string", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that comes after. Returns null if nothing comes after.", "ognl": false, "suffix": "}", "params": [ { "name": "exp", "javaType": "Object", "requi [...] "substringBefore(exp,before)": { "index": 135, "kind": "function", "displayName": "Substring Before", "group": "string", "label": "string", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that comes before. Returns null if nothing comes before.", "ognl": false, "suffix": "}", "params": [ { "name": "exp", "javaType": "Object", "r [...] "substringBetween(exp,after,before)": { "index": 136, "kind": "function", "displayName": "Substring Between", "group": "string", "label": "string", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that are between after and before. Returns null if nothing comes between.", "ognl": false, "suffix": "}", "params": [ { "name": "exp", [...] diff --git a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-functions.adoc b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-functions.adoc index deeed77f9c76..525d77ecd2f1 100644 --- a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-functions.adoc +++ b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-functions.adoc @@ -526,8 +526,8 @@ The string functions is various functions to work with values that are String an |`safeQuote()` | `String` | Returns the message body safely quoted if needed |`safeQuote(exp)` | `String` | Returns the expression safely quoted if needed |`substring(num1)` | `String` | Returns a substring of the message body. If the number is positive, then the returned string is clipped from the beginning. If the number is negative, then the returned string is clipped from the ending. -|`substring(num1,num2)` | `String` | Returns a substring of the message body. `num1` clips from the beginning, `num2` clips from the ending. Negative values clip from the opposite end. -|`substring(num1,num2,exp)` | `String` | Returns a substring of the given expression. `num1` clips from the beginning, `num2` clips from the ending. Negative values clip from the opposite end. +|`substring(num1,num2)` | `String` | Returns a substring of the message body. `num1` is how many characters to clip from the beginning, and `num2` how many characters to clip from the ending. The sign of `num2` does not matter, so `-1` and `1` both clip one character. +|`substring(num1,num2,exp)` | `String` | Returns a substring of the given expression. `num1` is how many characters to clip from the beginning, and `num2` how many characters to clip from the ending. The sign of `num2` does not matter, so `-1` and `1` both clip one character. |`substringAfter(after)` | `String` | Returns a substring of the message body that comes after. Returns `null` if nothing comes after. |`substringAfter(exp,after)` | `String` | Returns a substring of the expression that comes after. Returns `null` if nothing comes after. |`substringBefore(before)` | `String` | Returns a substring of the message body that comes before. Returns `null` if nothing comes before. @@ -600,6 +600,9 @@ The `substring`, `substringBefore`, and `substringAfter` functions are all simil Suppose the message body contains `ABCDEFGHIJK` then `${substring(3)}` returns `DEFGHIJK`, and `${substring(-3)}` returns `ABCDEFGH`. If you want to clip the first and last character you can use `${substring(1,-1)}` returning `BCDEFGHIJ`. +The numbers are how many characters to clip, not positions as in a Python slice: `${substring(3,-2)}` returns `DEFGHI` +(three characters clipped from the beginning and two from the end), and `${substring(1,0)}` returns `BCDEFGHIJK`. + [NOTE] ==== These `substring` functions are Camel functions with their own semantics (such as negative numbers clipping diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java index e5b61924a59a..992ea86f809e 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java @@ -851,15 +851,15 @@ public final class SimpleConstants { label = "core") public static final String STEP_ID = "stepId"; - @Metadata(description = "Returns a substring of the message body/expression. If only one positive number, then the returned string is clipped from the beginning. If only one negative number, then the returned string is clipped from the beginning. Otherwise the returned string is clipped between the head and tail positions.", + @Metadata(description = "Returns a substring of the message body/expression by clipping characters off its ends (not by positions). With one number, a positive number clips that many characters from the beginning, and a negative number clips that many characters from the end. With two numbers, head is how many characters to clip from the beginning and tail how many to clip from the end; the sign of tail does not matter, so -1 and 1 both clip one character.", label = "string", javaType = "String", examples = { - "${substring(0,5)} -> Hello // when body is 'Hello World'", - "${substring(6)} -> World // from position 6 to end", - "${substring(-5)} -> World // last 5 characters" }, + "${substring(6)} -> World // when body is 'Hello World', clips the first 6 characters", + "${substring(-6)} -> Hello // clips the last 6 characters", + "${substring(1,-1)} -> ello Worl // clips the first and the last character" }, annotations = { - "param=head:int:required::The start position (inclusive). Negative counts from end", - "param=tail:int:optional::The end position (exclusive)" }) + "param=head:int:required::How many characters to clip from the beginning. When it is the only argument, a negative value clips that many characters from the end instead", + "param=tail:int:optional::How many characters to clip from the end" }) public static final String SUBSTRING = "substring(head,tail)"; @Metadata(description = "Returns a substring of the message body/expression that comes after. Returns null if nothing comes after.", diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollectionFunctionFactory.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollectionFunctionFactory.java index f671dc96eb65..aee3d6a1a7a9 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollectionFunctionFactory.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollectionFunctionFactory.java @@ -107,7 +107,7 @@ public final class CollectionFunctionFactory implements SimpleLanguageFunctionFa } return CollectionExpressionBuilder.rangeExpression(tokens[0].trim(), tokens[1].trim()); } else { - return CollectionExpressionBuilder.rangeExpression("1", values.trim()); + return CollectionExpressionBuilder.rangeExpression("0", values.trim()); } } diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java index 97ce538fc685..165cc21096e4 100644 --- a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java @@ -1862,6 +1862,15 @@ public class SimpleTest extends LanguageTestSupport { assertExpression("${substring(0,-99)}", ""); assertExpression("${substring(99,0)}", ""); assertExpression("${substring(0,0)}", "ABCDEFGHIJK"); + // the sign of the tail does not matter + assertExpression("${substring(3,-2)}", "DEFGHI"); + assertExpression("${substring(3,2)}", "DEFGHI"); + + // the examples in the simple catalog + exchange.getMessage().setBody("Hello World"); + assertExpression("${substring(6)}", "World"); + assertExpression("${substring(-6)}", "Hello"); + assertExpression("${substring(1,-1)}", "ello Worl"); exchange.getMessage().setBody("Hello World"); exchange.getMessage().setHeader("foo", "1234567890"); diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/CollectionFunctionFactoryTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/CollectionFunctionFactoryTest.java index 3bf323a54c70..a1fbdb462d27 100644 --- a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/CollectionFunctionFactoryTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/CollectionFunctionFactoryTest.java @@ -66,9 +66,9 @@ public class CollectionFunctionFactoryTest extends AbstractSimpleFunctionFactory @Test @SuppressWarnings("unchecked") public void testRangeMax() { - // range(max) uses min=1, exclusive upper: range(3) -> [1,2] + // range(max) starts at 0, exclusive upper (as Python): range(3) -> [0,1,2] List<Integer> result = evaluate("range(3)", List.class); - assertEquals(List.of(1, 2), result); + assertEquals(List.of(0, 1, 2), result); } // --- shuffle toString --- diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc index 1cc7bcf85852..de0993249bb3 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc @@ -37,6 +37,10 @@ body when the map has no `sku()` method, the same value `${body[sku]}` gives. A `${body.size}` calls `size()` as before, and a name that is neither a method nor a key still fails. Only expressions that used to throw can now return a value. +The `range(max)` function now starts at 0, as documented and as `range` does in Python: `${range(5)}` returns +`[0, 1, 2, 3, 4]` where it used to return `[1, 2, 3, 4]`. Routes that relied on the range starting at 1 must use +`${range(1,5)}` instead. The two-argument form `range(min,max)` is unchanged. + === Circuit Breaker EIP The exchange property `CamelCircuitBreakerResponseRejected` is now also set inside the `onFallback`,
