This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch fix/CAMEL-24874 in repository https://gitbox.apache.org/repos/asf/camel.git
commit d18ec2cd50a143bd4181b83af5c0786c90790dc8 Author: Claus Ibsen <[email protected]> AuthorDate: Mon Sep 21 13:20:49 2026 +0200 CAMEL-24874: camel-core-languages - the simple init block operators := and ~:= are in the catalog The init block of a simple expression ($init{ ... }init$) assigns local variables with := and declares local custom functions with ~:= (usually a ~> chain), but the catalog had no operator entry for either, so nothing that reads simple.json knew they existed: camel_catalog_doc / tui_catalog_doc (operator syntax list and optionsFilter), the generated JS validator, the MCP server and any agent fetching the catalog. The docs were the only source, and docPage=advanced is the whole simple-advanced page. Both operators are declared in SimpleOperatorConstants (kind init) with their syntax and complete examples, listed in the operators doc table with a link to the init block sections, and the unfinished sentence and typos of the "Init Blocks with custom functions" section are fixed. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../apache/camel/catalog/docs/simple-advanced.adoc | 4 +--- .../apache/camel/catalog/docs/simple-operators.adoc | 2 ++ .../org/apache/camel/catalog/languages/simple.json | 4 +++- .../camel/catalog/simple/camel-simple-validator.js | 4 +++- .../org/apache/camel/language/simple/simple.json | 4 +++- .../modules/languages/pages/simple-advanced.adoc | 4 +--- .../modules/languages/pages/simple-operators.adoc | 2 ++ .../language/simple/SimpleOperatorConstants.java | 20 ++++++++++++++++++++ .../dsl/jbang/core/commands/ai/CatalogDocsTest.java | 15 +++++++++++++++ 9 files changed, 50 insertions(+), 9 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc index 58c5c2057aed..8925f33c254f 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc @@ -160,9 +160,7 @@ and then refer to the file such as `resource:classpath:mymapping.txt` where the === Init Blocks with custom functions -You can also declare custom functions using - -Inside the init block, it is a lso possible to define custom functions in the syntax `$nane ~:= <statement>;` where you can then use simple language to declare +Inside the init block, it is also possible to define custom functions in the syntax `$name ~:= <statement>;` where you can then use simple language to declare the structure of the function. Then you can later use these custom functions in your simple language expressions. For example to create a function that can cleanup a `String` value: diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc index da22008cd82e..e5354e79f7db 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-operators.adoc @@ -115,6 +115,8 @@ The following other operators can be used: |`?:` | 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`. +|`:=` | Assigns a local variable in the init block at the top of an expression (`$init{ $minAge := 18; }init$`), see xref:simple-advanced.adoc#_init_blocks[Init Blocks]. +|`~:=` | Declares a local custom function in the init block, usually as a chain (`$init{ $clean ~:= ${trim()} ~> ${uppercase()}; }init$`), called as `${clean()}`, see xref:simple-advanced.adoc#_init_blocks_with_custom_functions[Init Blocks with custom functions]. |==== ==== Ternary Operator 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 4e7580acfd4e..e2b8a01cf9be 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 @@ -213,6 +213,8 @@ "? :": { "index": 30, "kind": "operator", "displayName": "Ternary", "label": "ternary", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Ternary conditional operator. Evaluates the predicate and returns trueValue if true, falseValue if false. Requires spaces around both ? and : tokens.", "operatorKind": "ternary", "operatorSyntax": "predicate ? trueValue : falseValue", "precedence": 25, "examples": [ "${header.foo} > [...] "~>": { "index": 31, "kind": "operator", "displayName": "Chain", "label": "chain", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Pipes the result of the left expression as input body to the right expression. Use $param in the right expression to reference the piped value explicitly.", "operatorKind": "chain", "operatorSyntax": "expr ~> expr", "precedence": 5, "examples": [ "${trim()} ~> ${uppercase()}", "${substrin [...] "?~>": { "index": 32, "kind": "operator", "displayName": "Chain null safe", "label": "chain", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Null-safe chain operator. Same as ~> but stops chaining and returns null if the left expression evaluates to null.", "operatorKind": "chain", "operatorSyntax": "expr ?~> expr", "precedence": 5, "examples": [ "${header.name} ?~> ${trim()} ?~> ${uppercase()}" ] }, - "?:": { "index": 33, "kind": "operator", "displayName": "Elvis", "label": "other", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Elvis operator (null-coalescing). Returns the left operand if it is not null\/empty, otherwise returns the right operand as a fallback value.", "operatorKind": "other", "operatorSyntax": "expr ?: defaultValue", "precedence": 20, "examples": [ "${header.username} ?: 'Guest'", "${body} ?: $ [...] + "?:": { "index": 33, "kind": "operator", "displayName": "Elvis", "label": "other", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Elvis operator (null-coalescing). Returns the left operand if it is not null\/empty, otherwise returns the right operand as a fallback value.", "operatorKind": "other", "operatorSyntax": "expr ?: defaultValue", "precedence": 20, "examples": [ "${header.username} ?: 'Guest'", "${body} ?: $ [...] + ":=": { "index": 34, "kind": "operator", "displayName": "Init variable", "label": "init", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Assigns a local variable in the init block at the top of an expression ($init{ ... }init$), computed once and used in the expression as ${name}. Each statement ends with a semicolon and a new line.", "operatorKind": "init", "operatorSyntax": "$name := expr;", "precedence": 1, "exam [...] + "~:=": { "index": 35, "kind": "operator", "displayName": "Init function", "label": "init", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Declares a local custom function in the init block at the top of an expression ($init{ ... }init$), usually as a chain of functions on the input, called as ${name()} (the message body as input), ${name(exp)} (an explicit input) or from another function as ${function(name)}. Each s [...] } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/simple/camel-simple-validator.js b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/simple/camel-simple-validator.js index fedf540459e7..091e88d267c2 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/simple/camel-simple-validator.js +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/simple/camel-simple-validator.js @@ -192,7 +192,9 @@ const OPERATORS = { '? :': { kind: 'ternary', description: 'Ternary conditional operator. Evaluates the predicate and returns trueValue if true, falseValue if false. Requires spaces around both ? and : tokens.' }, '~>': { kind: 'chain', description: 'Pipes the result of the left expression as input body to the right expression. Use $param in the right expression to reference the piped value explicitly.' }, '?~>': { kind: 'chain', description: 'Null-safe chain operator. Same as ~> but stops chaining and returns null if the left expression evaluates to null.' }, - '?:': { kind: 'other', description: 'Elvis operator (null-coalescing). Returns the left operand if it is not null/empty, otherwise returns the right operand as a fallback value.' } + '?:': { kind: 'other', description: 'Elvis operator (null-coalescing). Returns the left operand if it is not null/empty, otherwise returns the right operand as a fallback value.' }, + ':=': { kind: 'init', description: 'Assigns a local variable in the init block at the top of an expression ($init{ ... }init$), computed once and used in the expression as ${name}. Each statement ends with a semicolon and a new line.' }, + '~:=': { kind: 'init', description: 'Declares a local custom function in the init block at the top of an expression ($init{ ... }init$), usually as a chain of functions on the input, called as ${name()} (the message body as input), ${name(exp)} (an explicit input) or from another function as ${function(name)}. Each statement ends with a semicolon and a new line.' } }; 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 4e7580acfd4e..e2b8a01cf9be 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 @@ -213,6 +213,8 @@ "? :": { "index": 30, "kind": "operator", "displayName": "Ternary", "label": "ternary", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Ternary conditional operator. Evaluates the predicate and returns trueValue if true, falseValue if false. Requires spaces around both ? and : tokens.", "operatorKind": "ternary", "operatorSyntax": "predicate ? trueValue : falseValue", "precedence": 25, "examples": [ "${header.foo} > [...] "~>": { "index": 31, "kind": "operator", "displayName": "Chain", "label": "chain", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Pipes the result of the left expression as input body to the right expression. Use $param in the right expression to reference the piped value explicitly.", "operatorKind": "chain", "operatorSyntax": "expr ~> expr", "precedence": 5, "examples": [ "${trim()} ~> ${uppercase()}", "${substrin [...] "?~>": { "index": 32, "kind": "operator", "displayName": "Chain null safe", "label": "chain", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Null-safe chain operator. Same as ~> but stops chaining and returns null if the left expression evaluates to null.", "operatorKind": "chain", "operatorSyntax": "expr ?~> expr", "precedence": 5, "examples": [ "${header.name} ?~> ${trim()} ?~> ${uppercase()}" ] }, - "?:": { "index": 33, "kind": "operator", "displayName": "Elvis", "label": "other", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Elvis operator (null-coalescing). Returns the left operand if it is not null\/empty, otherwise returns the right operand as a fallback value.", "operatorKind": "other", "operatorSyntax": "expr ?: defaultValue", "precedence": 20, "examples": [ "${header.username} ?: 'Guest'", "${body} ?: $ [...] + "?:": { "index": 33, "kind": "operator", "displayName": "Elvis", "label": "other", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Elvis operator (null-coalescing). Returns the left operand if it is not null\/empty, otherwise returns the right operand as a fallback value.", "operatorKind": "other", "operatorSyntax": "expr ?: defaultValue", "precedence": 20, "examples": [ "${header.username} ?: 'Guest'", "${body} ?: $ [...] + ":=": { "index": 34, "kind": "operator", "displayName": "Init variable", "label": "init", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Assigns a local variable in the init block at the top of an expression ($init{ ... }init$), computed once and used in the expression as ${name}. Each statement ends with a semicolon and a new line.", "operatorKind": "init", "operatorSyntax": "$name := expr;", "precedence": 1, "exam [...] + "~:=": { "index": 35, "kind": "operator", "displayName": "Init function", "label": "init", "required": false, "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Declares a local custom function in the init block at the top of an expression ($init{ ... }init$), usually as a chain of functions on the input, called as ${name()} (the message body as input), ${name(exp)} (an explicit input) or from another function as ${function(name)}. Each s [...] } } diff --git a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc index 58c5c2057aed..8925f33c254f 100644 --- a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc +++ b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc @@ -160,9 +160,7 @@ and then refer to the file such as `resource:classpath:mymapping.txt` where the === Init Blocks with custom functions -You can also declare custom functions using - -Inside the init block, it is a lso possible to define custom functions in the syntax `$nane ~:= <statement>;` where you can then use simple language to declare +Inside the init block, it is also possible to define custom functions in the syntax `$name ~:= <statement>;` where you can then use simple language to declare the structure of the function. Then you can later use these custom functions in your simple language expressions. For example to create a function that can cleanup a `String` value: diff --git a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc index da22008cd82e..e5354e79f7db 100644 --- a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc +++ b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-operators.adoc @@ -115,6 +115,8 @@ The following other operators can be used: |`?:` | 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`. +|`:=` | Assigns a local variable in the init block at the top of an expression (`$init{ $minAge := 18; }init$`), see xref:simple-advanced.adoc#_init_blocks[Init Blocks]. +|`~:=` | Declares a local custom function in the init block, usually as a chain (`$init{ $clean ~:= ${trim()} ~> ${uppercase()}; }init$`), called as `${clean()}`, see xref:simple-advanced.adoc#_init_blocks_with_custom_functions[Init Blocks with custom functions]. |==== ==== Ternary Operator diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleOperatorConstants.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleOperatorConstants.java index 658878ea5778..6b1c8318dbf7 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleOperatorConstants.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleOperatorConstants.java @@ -239,6 +239,26 @@ public final class SimpleOperatorConstants { annotations = { "kind=other", "syntax=expr ?: defaultValue", "precedence=20" }) public static final String ELVIS = "?:"; + // --- Init block operators (only inside $init{ ... }init$ at the start of an expression) --- + + @Metadata(description = "Assigns a local variable in the init block at the top of an expression ($init{ ... }init$)," + + " computed once and used in the expression as ${name}. Each statement ends with a semicolon and a new line.", + label = "init", + examples = { "$init{\n $minAge := 18;\n $greeting := 'Hello ${body}';\n}init$\n${greeting} (${minAge})" }, + annotations = { "kind=init", "syntax=$name := expr;", "precedence=1" }) + public static final String INIT_VARIABLE = ":="; + + @Metadata(description = "Declares a local custom function in the init block at the top of an expression ($init{ ... }init$)," + + " usually as a chain of functions on the input, called as ${name()} (the message body as input)," + + " ${name(exp)} (an explicit input) or from another function as ${function(name)}." + + " Each statement ends with a semicolon and a new line.", + label = "init", + examples = { + "$init{\n $cleanName ~:= ${trim()} ~> ${normalizeWhitespace()} ~> ${uppercase()};\n}init$\nCustomer: ${cleanName(${header.customerName})}", + "$init{\n $clean ~:= ${trim()} ~> ${normalizeWhitespace()};\n $count ~:= ${clean()} ~> ${split(' ')} ~> ${size()};\n}init$\nYou said: ${clean()} in ${count()} words" }, + annotations = { "kind=init", "syntax=$name ~:= expr ~> expr;", "precedence=1" }) + public static final String INIT_FUNCTION = "~:="; + private SimpleOperatorConstants() { } } diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java index 97cbf8277fbe..a715af758295 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java @@ -177,6 +177,21 @@ class CatalogDocsTest { assertTrue(binary.getInteger("matchedOperators") > 5); JsonObject eq = (JsonObject) binary.getCollection("operators").iterator().next(); assertEquals("LHS == RHS", eq.getString("syntax")); + + // CAMEL-24874: the init block operators are in the catalog, so a model asking about a local function or the + // init block gets the syntax and a complete example instead of the whole advanced page + JsonObject init = catalogDoc(Map.of("name", "simple", "kind", "language", "optionsFilter", "init")); + assertEquals("init", init.getString("operatorKind")); + assertEquals(2, init.getInteger("matchedOperators")); + JsonObject function = init.getCollection("operators").stream() + .map(JsonObject.class::cast) + .filter(op -> "~:=".equals(op.getString("name"))) + .findFirst().orElseThrow(); + assertEquals("$name ~:= expr ~> expr;", function.getString("syntax")); + String example = (String) function.getCollection("examples").iterator().next(); + assertTrue(example.startsWith("$init{\n") && example.contains("}init$\n"), example); + JsonObject summary = catalogDoc(Map.of("name", "simple", "kind", "language")); + assertTrue(summary.getCollection("operatorSyntax").contains("$name := expr;")); } @Test
