Croway opened a new pull request, #28679: URL: https://github.com/apache/camel/pull/28679
_Claude Code on behalf of Croway_ [CAMEL-26401](https://issues.apache.org/jira/browse/CAMEL-26401) ## Problem Since CAMEL-24921 a ternary and a predicate can be written inside `${ }`, and coding agents write the elvis operator there too. The parser refused it (`${header.missing ?: 10}` -> `${ cannot accept ?:`), and without a space after it the text is not an operator at all: `${header.missing ?:10}` reads a header named `missing ?:10`, evaluates to null, and `camel_eval_expression` answered `{"status":"ok"}` with no `result` key (the MCP server leaves out null fields). A model took that for a form that works, wrote `LIMIT :#${header.offset ?:0}, :#${header.limit ?:10}` into its SQL file and got a `BadSqlGrammarException`. ## Fix **camel-core-languages.** Inside `${ }` a ` ?: ` with a space on both sides, outside quotes, brackets, parentheses and nested functions, is the elvis operator, and `${a ?: b}` means exactly `${a} ?: b`: - `SimpleExpressionParser` leaves an elvis token inside a function as text of the function (spaces included), as the predicate parser already did, instead of making it an `OtherExpression` node that `${` cannot accept. - `SimpleFunctionExpression` reads it after the predicate check of CAMEL-24921: `SimpleSyntaxHints.elvisOutside` rewrites `a ?: b` to `${a} ?: b`, which is parsed with the expression parser, so the fallback rule (null, empty, `false`, `0`), the accepted values, chains and nested functions are those of the operator after the braces. The approach and rules are CAMEL-24921's (whitespace, quotes; the strict flag it checked has since been removed as always true, CAMEL-24971). A property default stays text (`${properties:msg:which ?: none}`), as CAMEL-24963 did for the predicate. - Without a space on both sides nothing changes, inside or outside the braces. - The one change for text that parsed before: in a predicate the parser already handed the function text over as it is, so `${header.a ?: 10} == 10` read a header named `a ?: 10`; it is the operator there too now (the compatibility run below found no such text outside the AI tools' own examples). | Expression (body `x`) | Before | After | |---|---|---| | `${header.missing} ?: 10` | `10` | `10` | | `${header.missing} ?:10` | ` ?:10` | ` ?:10` | | `${header.missing}?: 10` | `?: 10` | `?: 10` | | `Limit: ${header.missing} ?: 10` | `Limit: 10` | `Limit: 10` | | `${header.missing} ?: 10 rows` | `10 rows` | `10 rows` | | `${header.missing} ?: ${header.other} ?: 'c'` | `c` | `c` | | `${header.missing ?: 10}` | `${ cannot accept ?:` | `10` | | `Limit: ${header.missing ?: 10}` | `${ cannot accept ?:` | `Limit: 10` | | `${header.missing ?: 10 rows}` | `${ cannot accept ?:` | `10 rows` | | `${header.missing ?: ${header.other} ?: 'c'}` | `${ cannot accept ?:` | `c` | | `${header.missing ?: 'a ?: b'}` | `${ cannot accept ?:` | `a ?: b` | | `${header.n == 5 ? header.missing ?: 'd' : 'no'}` (n = 5) | `${ cannot accept ?:` | `d` | | `${header.missing ?: Guest}` | `${ cannot accept ?:` | `Other operator ?: does not accept Guest on the right hand side: ... e.g. 'Guest'` | | `${header.missing ?:10}` | null (header `missing ?:10`) | null (unchanged) | | `${header.missing?: 10}` | null | null (unchanged) | | `${body == 'x' ? 'yes' : 'no'}` | `yes` | `yes` | | `${body?.length()}` | `1` | `1` | | predicate `${header.missing ?: 10} == 10` | false (header `missing ?: 10`) | true | **Docs.** The Elvis section of `simple-operators.adoc` (and its catalog copy): both forms mean the same; the form after the braces stays the recommended one since it also works before 4.24; the form inside is the one for a single `${...}`, such as a camel-sql `:#${...}` parameter in a query file (the parameter is read up to the first `}`, and in the endpoint URI a `?` starts the endpoint options); without the spaces it is not the operator. **Upgrade guide.** A 4.24 entry for the one change above: in a predicate, `${header.limit ?: 10}` read a header named `limit ?: 10` and now falls back; without the spaces nothing changes, and a property default is not affected. **camel-jbang AI tools.** - `SimpleChecks.elvisInsideFunction` (next to `topLevelTernary`, quote, bracket and parenthesis aware) reports a `?:` inside `${...}` without a space on both sides (`${header.offset ?:0}`, `${header.limit?: 10}`, `${h?:x}`) with the portable rewrite first, and the spaced form when the Camel version is older than 4.24. It runs in the validator (`camel_validate_source`, YAML and the model-based Java/XML checks, with the catalog's version from `camelVersion`), in `ExpressionEvaluator` (both paths; the version of the selected integration from its status `context.version`, else the tools' `camelVersion`) and in the answer checks of the TUI and `camel ask`. - `ExpressionEvaluator` marks a null value with `"nullResult": true` and the note "evaluated to null: a header, property or variable that is not set, or a function name that took in the rest of the text"; when the elvis check matches, its message is the note. This is one post-processing step after both evaluation paths. - The tests and pages that used `${header.user ?: 'Guest'}` as the example of an invalid expression now use `${header.user ?: Guest}` (a fallback without quotes, invalid in any version) or the form without the spaces. | Tool call | Before | After | |---|---|---| | `camel_eval_expression` `${header.missing ?:10}` | `{"status":"ok"}` | `"nullResult":true`, note: ``` `${header.missing ?:10}` reads a header named 'missing ?:10': the elvis operator needs a space on both sides and goes after the function: `${header.missing} ?: 10` (from Camel 4.24 also `${header.missing ?: 10}`, the form for a single ${...} such as a camel-sql :#${...} parameter) ``` | | `camel_eval_expression` `${header.missing}` | `{"status":"ok"}` | `"nullResult":true` and the null note | | `camel_eval_expression` `${header.missing ?: 10}` in a 4.22.1 integration | `${ cannot accept ?:` | the same error, note: ``` `${header.missing ?: 10}` needs Camel 4.24 or later, where the elvis operator may be written inside ${...} (here Camel 4.22.1): write it after the function: `${header.missing} ?: 10` ``` | | `camel_validate_source` `simple: "LIMIT :#${header.offset ?:0}, :#${header.limit ?:10}"` | valid | `Line N: ${header.offset ?:0} reads a header named 'offset ?:0': ...; the same for ${header.limit ?:10}` | | `camel_validate_source` `${header.limit ?: 10}` with `camelVersion` 4.22.1 | `Simple syntax error: ${ cannot accept ?:` | the rewrite to `${header.limit} ?: 10` | ## Testing Compatibility, as for CAMEL-24921: a throwaway harness (not committed) collected every distinct text holding `${` from this repository (Java string literals, `simple(...)`/`log(...)`, XML `<simple>` and attributes, YAML values, adoc inline code and code blocks, main and test sources) and from camel-kamelets (d1406cf9), camel-examples (14d4a33) and camel-jbang-examples (52260fa): **7,563 distinct texts** (2,027 from simple contexts such as `simple(...)`, `<simple>`, `simple:`/`expression:` and log messages; 5,536 other strings with `${`). Each was parsed as an expression and as a predicate and evaluated twice against the same exchange, with the camel-core-languages classes of origin/main and of this branch. | | origin/main | this branch | |---|---|---| | parse as an expression | 5,497 | 5,504 | | parse as a predicate | 2,377 | 2,377 | | texts with any difference (value, error, `toString`) | | 7 | The 7 are the texts with a spaced `?:` inside `${ }`, all in the camel-jbang AI tools (their docs and tests used `${header.user ?: 'Guest'}` as the invalid example, updated in this PR): they failed as an expression and now evaluate; 2 of them also parsed as a predicate, reading a header named `user ?: 'Guest'`, and now fall back. No route, kamelet or example is affected. 133 texts with a time, random, thread or reflection-order dependent value differ between two runs of the same code and were compared on their parse outcome only, which is identical. Tests: - `SimpleElvisInBracesTest` (new, camel-core): both forms agree, the falsy rule, nested functions and chains, quoted values and an empty fallback, templates, predicates, the ternary unaffected, the forms without spaces unchanged, quotes, brackets and property defaults as text, the error message. 9 of its 10 tests fail on origin/main (the one about the forms without spaces passes there, as it should). - `SqlProducerExpressionParameterTest#testNamedParameterWithTheElvisOperatorInsideTheExpression` (camel-sql): a `:#${exchangeProperty.license ?: 'XXX'}` parameter in a query file; fails on origin/main with `${ cannot accept ?:`. - camel-jbang-core: `SourceValidatorSimpleTest` (6 new), `ExpressionEvaluatorTest` (4 new), `EvalInTheIntegrationTest` (3 new, in-process path with the integration's version from its status), `AnswerChecksTest` (1 new, examples updated); `AiPanelTest` (TUI) examples updated. ``` mvnd -P apache-snapshots test -pl core/camel-core-languages,core/camel-core -Dtest='org/apache/camel/language/simple/**/*Test' -> 703 tests, 0 failures mvnd -P apache-snapshots test -pl core/camel-core-languages,core/camel-core -Dtest='org/apache/camel/language/**/*Test,**/*Simple*Test' -> 1,068 tests (2 skipped), 0 failures mvnd -P apache-snapshots test -pl core/camel-core-languages,dsl/camel-jbang/camel-jbang-core,dsl/camel-jbang/camel-jbang-plugin-tui -Dtest='org/apache/camel/dsl/jbang/core/commands/ai/**/*Test,AiPanelTest,AiPanelPromptBudgetTest,TuiToolRegistryEvalExpressionTest,TuiMcpServerToolErrorTest' -> camel-jbang-core 771 tests (1 skipped), TUI 92 tests, 0 failures mvnd -P apache-snapshots test -pl core/camel-core-languages,dsl/camel-jbang/camel-jbang-core,dsl/camel-jbang/camel-jbang-plugin-tui -Dtest='ValidateWithObservedBodiesTest,McpFacadeWriteFileTest' -> 14 tests, 0 failures mvnd -P apache-snapshots test -pl core/camel-core-languages,components/camel-sql -Dtest=SqlProducerExpressionParameterTest -> 3 tests, 0 failures ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
