Skip to content

Fix model hierarchy backward compatibility#1214

Merged
fjtirado merged 1 commit intoserverlessworkflow:mainfrom
fjtirado:fix_backward_compatibility
Mar 9, 2026
Merged

Fix model hierarchy backward compatibility#1214
fjtirado merged 1 commit intoserverlessworkflow:mainfrom
fjtirado:fix_backward_compatibility

Conversation

@fjtirado
Copy link
Collaborator

@fjtirado fjtirado commented Mar 9, 2026

Provide a default asNumber(Class targerNumberClass) implementation

Copilot AI review requested due to automatic review settings March 9, 2026 12:55
@fjtirado fjtirado force-pushed the fix_backward_compatibility branch from 9ed184c to 1cad574 Compare March 9, 2026 12:56
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 to AbstractWorkflowModel.
  • Provide a default protected <N extends Number> Optional<N> asNumber(Class<N>) implementation in AbstractWorkflowModel.
  • Simplify JavaModel by 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())));
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Suggested change
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));

Copilot uses AI. Check for mistakes.
@fjtirado fjtirado force-pushed the fix_backward_compatibility branch from 1cad574 to e3a305b Compare March 9, 2026 13:07
Copilot AI review requested due to automatic review settings March 9, 2026 13:15
@fjtirado fjtirado force-pushed the fix_backward_compatibility branch from e3a305b to d2cb8ed Compare March 9, 2026 13:15
Provide a default asNumber(Class<N> targerNumberClass) implementation

Signed-off-by: fjtirado <ftirados@redhat.com>
@fjtirado fjtirado force-pushed the fix_backward_compatibility branch from d2cb8ed to fdf9653 Compare March 9, 2026 13:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 47 to 53
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());
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +49
} 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())));
}
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copilot uses AI. Check for mistakes.
@fjtirado fjtirado merged commit a762600 into serverlessworkflow:main Mar 9, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants