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]
