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]

Reply via email to