Fix model hierarchy backward compatibility#1214
Fix model hierarchy backward compatibility#1214fjtirado merged 1 commit intoserverlessworkflow:mainfrom
Conversation
9ed184c to
1cad574
Compare
There was a problem hiding this comment.
Pull request overview
This PR restores backward compatibility in the AbstractWorkflowModel hierarchy by providing a default implementation for number subclass conversions, reducing the need for each concrete model to duplicate asNumber(Class<N>) logic.
Changes:
- Add shared
asSubclass(Number, Class<N>)conversion helper toAbstractWorkflowModel. - Provide a default
protected <N extends Number> Optional<N> asNumber(Class<N>)implementation inAbstractWorkflowModel. - Simplify
JavaModelby delegating number subclass conversion to the shared helper and removing now-unneeded imports/logic.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| impl/core/src/main/java/io/serverlessworkflow/impl/AbstractWorkflowModel.java | Introduces shared number-conversion helper and default asNumber(Class<N>) implementation for model subclasses. |
| experimental/model/src/main/java/io/serverlessworkflow/impl/model/func/JavaModel.java | Removes duplicated number conversion logic and delegates to the new shared helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } else if (targetNumberClass == Byte.class) { | ||
| return Optional.of(targetNumberClass.cast(num.byteValue())); | ||
| } else if (targetNumberClass == BigDecimal.class) { | ||
| return Optional.of(targetNumberClass.cast(BigDecimal.valueOf(num.doubleValue()))); |
There was a problem hiding this comment.
asSubclass converts to BigDecimal via BigDecimal.valueOf(num.doubleValue()), which (a) can throw NumberFormatException for NaN/Infinity inputs and (b) loses precision for large integral values (e.g., Long > 2^53). Consider using type-specific exact conversions (e.g., BigDecimal.valueOf(long) for integral types, new BigDecimal((BigInteger) num) for BigInteger, BigDecimal.valueOf(double) only for floating types) and returning Optional.empty() when conversion is not representable (including non-finite doubles).
| return Optional.of(targetNumberClass.cast(BigDecimal.valueOf(num.doubleValue()))); | |
| BigDecimal result; | |
| if (num instanceof BigDecimal) { | |
| result = (BigDecimal) num; | |
| } else if (num instanceof BigInteger) { | |
| result = new BigDecimal((BigInteger) num); | |
| } else if (num instanceof Byte | |
| || num instanceof Short | |
| || num instanceof Integer | |
| || num instanceof Long) { | |
| result = BigDecimal.valueOf(num.longValue()); | |
| } else if (num instanceof Float || num instanceof Double) { | |
| double d = num.doubleValue(); | |
| if (Double.isNaN(d) || Double.isInfinite(d)) { | |
| return Optional.empty(); | |
| } | |
| result = BigDecimal.valueOf(d); | |
| } else { | |
| result = new BigDecimal(num.toString()); | |
| } | |
| return Optional.of(targetNumberClass.cast(result)); |
1cad574 to
e3a305b
Compare
e3a305b to
d2cb8ed
Compare
Provide a default asNumber(Class<N> targerNumberClass) implementation Signed-off-by: fjtirado <ftirados@redhat.com>
d2cb8ed to
fdf9653
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| case ALL -> | ||
| modelFactory.fromAny( | ||
| new ProcessResult( | ||
| 0, streamSuppliers.outputStream().get(), streamSuppliers.errorStream().get())); | ||
| code, streamSuppliers.outputStream().get(), streamSuppliers.errorStream().get())); | ||
| case NONE -> model; | ||
| case CODE -> modelFactory.from(0); | ||
| case CODE -> modelFactory.from(code); | ||
| case STDOUT -> modelFactory.from(streamSuppliers.outputStream().get()); |
There was a problem hiding this comment.
This change fixes exit-code propagation for ProcessReturnType.ALL and CODE, but there’s no regression test covering the failure case when return: all is used (i.e., ensuring ProcessResult.code is non-zero for a failing command). Adding a test workflow that fails with return: all would help prevent this from regressing again.
| } else if (targetNumberClass == BigDecimal.class) { | ||
| return Optional.of(targetNumberClass.cast(BigDecimal.valueOf(num.doubleValue()))); | ||
| } else if (targetNumberClass == BigInteger.class) { | ||
| return Optional.of(targetNumberClass.cast(BigInteger.valueOf(num.longValue()))); | ||
| } |
There was a problem hiding this comment.
asSubclass converts to BigDecimal via BigDecimal.valueOf(num.doubleValue()) and to BigInteger via BigInteger.valueOf(num.longValue()), which is lossy for large integral values and for BigInteger/BigDecimal sources outside long/double precision. Since this helper is now shared across models as the default number-conversion path, consider using non-lossy conversions where possible (e.g., BigInteger -> BigDecimal via constructor, BigDecimal -> BigInteger via toBigInteger, and avoiding doubleValue() for integral inputs).
Provide a default asNumber(Class targerNumberClass) implementation