allthingssecurity opened a new pull request, #26930:
URL: https://github.com/apache/camel/pull/26930

   # Description
   
   [CAMEL-25013](https://issues.apache.org/jira/browse/CAMEL-25013) (follow-up 
to #26873)
   
   #26873 made `ObjectHelper.typeCoerceEquals` / `typeCoerceCompare` compare 
numbers of different types by value, and its description said this covers every 
Simple comparison operator, `in` included. It does not reach `in`. 
`ValueBuilder.in(Object...)` first converts each value to the type of the left 
value (`ExpressionBuilder.convertToExpression`), and only then calls 
`isEqualTo`. For an `Integer` or `Long` left value, the type converter uses 
`intValue()` / `longValue()`, so the decimals are dropped and a long outside 
the int range wraps. A `String` goes through `Integer.valueOf`, which throws 
for `2.5`. By the time `typeCoerceEquals` runs, the original value is gone.
   
   The docs say that `in` converts each element to the type of the left-hand 
side (`simple-operators.adoc`: "Camel will convert each element into the type 
of the left-hand side"). That conversion is not the problem here. The problem 
is that it is lossy, and that `in` then gives a different answer from `==`:
   
   With an `Integer` header `n` = 2:
   - Java DSL: `header("n").in(2.5)`, `in(2.9)`, `in(new BigDecimal("2.5"))` 
and `in(4294967298L)` are all `true`. So is `99L in(99.99, 100.01)`.
   - Simple: `${header.n} in '2.5,3.5'`, `!in '2.5'` and `in '2.0'` fail the 
exchange with `TypeConversionException: ... For input string: "2.5"`, while 
`${header.n} == 2.5` is `false` and `== 2.0` is `true`.
   
   `MockValueBuilder.in` (camel-mock) uses the same code. The conversion dates 
from Camel 2.x, so this is not a regression.
   
   This change:
   - A new `ExpressionBuilder.inValueExpression(value, left)` is used by 
`ValueBuilder.in` and `MockValueBuilder.in` in place of `convertToExpression`. 
When the left value is a number, a number value is kept as it is and a String 
with a decimal number becomes a `BigDecimal`. `typeCoerceEquals` then compares 
them by value, exactly as `==` does since #26873.
   - Any other value is converted to the type of the left value as before, and 
a left value that is not a number goes through `convertToExpression` unchanged. 
So `in` with Strings, enums, booleans and so on is not affected, and neither is 
a `null` left value. `convertToExpression` itself is not touched, so this does 
not overlap with #26924 (CAMEL-25044), which changes its `null` branch. Both 
merge cleanly (`git merge-tree`), and once #26924 is in, a `null` left value 
gets its behaviour through the delegation.
   - `${header.n} in '2.0'` with an `Integer` 2 is now `true`. I chose this 
because the elements of an `in` list are always text, and the docs describe 
them as numbers when the left side is a number. It matches the unquoted 
`${header.n} == 2.0`, which is `true`. The quoted `${header.n} == '2.0'` is 
`false` today, because a quoted value that is not the canonical form of the 
number is compared as text, and this PR does not change that. The simpler 
alternative, dropping the conversion and relying on `isEqualTo` alone, would 
make `in '2.0'` `false`. It would also make a `BigDecimal` header `2.50` no 
longer `in '2.5'` (it is `true` today), so I did not take it.
   - Docs: the `in` paragraph of `simple-operators.adoc` (and its catalog copy) 
says that numbers are compared by value. The 4.23 upgrade guide bullet of 
CAMEL-25013 now covers `in` / `!in`.
   
   Tests:
   - `PredicateBuilderTest.testNumberValueIn`: with an `Integer` header 2, 
`in(2.5)`, `in(2.9, 3.5)`, `in(BigDecimal 2.5)`, `in(4294967298L)` and 
`in("2.5")` do not match, while `in(1, 2, 3)`, `in(2.0)`, `in(2L)` and 
`in("2")` do. Also `0 in(-0.5)`, `-1294967296 in(3000000000L)`, `99L in(99.99, 
100.01)` and `Long.MAX_VALUE in(1e20)` do not match, and a `Double` header 2.5 
is `in(2.5)` and `in("2.5")` but not `in(2)`.
   - `SimpleOperatorTest.testInWithDecimals`: `${header.n} in '2.5,3.5'` is 
`false` without an exception, `!in '2.5'` is `true`, `in '2,3'` and `in '2.0'` 
are `true`, `in '4294967298'` is `false`, a `Long` 99 is not `in 
'99.99,100.01'`, and a `BigDecimal` 2.50 is `in '2.5'`.
   - `MockValueBuilderInTest`: `mock.message(0).header("n").in(...)` with 
decimals and with a long outside the int range.
   
   Without the change in `ValueBuilder` and `MockValueBuilder`, all the new 
tests fail: `testNumberValueIn` (`in ([header(n) == 2.5])` is `true`), 
`testInWithDecimals` (`TypeConversionException ... For input string: "2.5"`), 
and both negative `MockValueBuilderInTest` cases. With the change, 
`TypeCoerce*,*Simple*,*Predicate*,*ValueBuilder*,*ExpressionBuilder*,*ObjectHelper*,*Filter*,*Choice*,*Mock*,*Compar*`
 in camel-util, camel-support, camel-core and camel-console pass: 1315 tests, 0 
failures.
   
   Found with a Lean model of `in` as it is implemented today. It proves that 
for every `Integer` n from 0 to 2^31-2 and every digit d from 1 to 9, `in(n.d)` 
is true for a `Double` or `BigDecimal` value, and that every long n + k·2^32 
with k ≠ 0 is "in" for n. It also proves that integral values within the int 
range always give the right answer, so only lossy conversions are affected. I 
then reproduced the bug against the real classes. A jqwik property shrinks the 
failure to `0 in(0.1)`, and another one shows that `in '0.0'` throws where `== 
0.0` is true.
   
   # Target
   
   - [x] I checked that the commit is targeting the correct branch (Camel 4 
uses the `main` branch)
   
   # Tracking
   - [x] If this is a large change, bug fix, or code improvement, I checked 
there is a [JIRA issue](https://issues.apache.org/jira/browse/CAMEL) filed for 
the change (usually before you start working on it).
   
   # Apache Camel coding standards and style
   
   - [x] I checked that each commit in the pull request has a meaningful 
subject line and body.
   - [ ] I have run `mvn clean install -DskipTests` locally from root folder 
and I have committed all auto-generated changes.
     (I built and tested the affected modules, including the formatter and 
import-sort plugins. I did not run the full root build, and I edited the 
catalog copy of `simple-operators.adoc` by hand to match the source.)
   
   # AI-assisted contributions
   
   - [x] If this PR includes AI-generated code, commits have proper 
co-authorship attribution (e.g., `Co-authored-by` trailers) and the PR 
description identifies the AI tool used.
     This PR was prepared with Claude Code (Claude Opus 5.5). The commit 
carries a `Co-Authored-By` trailer.
   
   _Claude Code on behalf of allthingssecurity_
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
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