Skip to content

Commit 8461f27

Browse files
committed
Add number type conversion validation to ensure proper casting between numeric types
- Implement convertNumber method in AbstractWorkflowModel to handle conversions between Integer, Long, Double, Float, Short, and Byte - Add comprehensive test suite (WorkflowNumberConversionTest) covering all numeric type conversions - Fix issue where asNumber() returned incompatible Number types without proper casting - Ensure type safety when converting workflow model outputs to specific numeric types
1 parent 60a4ff4 commit 8461f27

File tree

2 files changed

+183
-1
lines changed

2 files changed

+183
-1
lines changed

impl/core/src/main/java/io/serverlessworkflow/impl/AbstractWorkflowModel.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public <T> Optional<T> as(Class<T> clazz) {
3535
} else if (OffsetDateTime.class.isAssignableFrom(clazz)) {
3636
return (Optional<T>) asDate();
3737
} else if (Number.class.isAssignableFrom(clazz)) {
38-
return (Optional<T>) asNumber();
38+
return asNumber().map(num -> convertNumber(num, clazz));
3939
} else if (Collection.class.isAssignableFrom(clazz)) {
4040
Collection<?> collection = asCollection();
4141
return collection.isEmpty() ? Optional.empty() : (Optional<T>) Optional.of(collection);
@@ -45,4 +45,23 @@ public <T> Optional<T> as(Class<T> clazz) {
4545
return convert(clazz);
4646
}
4747
}
48+
49+
@SuppressWarnings("unchecked")
50+
private <T> T convertNumber(Number num, Class<T> clazz) {
51+
if (clazz == Integer.class) {
52+
return (T) Integer.valueOf(num.intValue());
53+
} else if (clazz == Long.class) {
54+
return (T) Long.valueOf(num.longValue());
55+
} else if (clazz == Double.class) {
56+
return (T) Double.valueOf(num.doubleValue());
57+
} else if (clazz == Float.class) {
58+
return (T) Float.valueOf(num.floatValue());
59+
} else if (clazz == Short.class) {
60+
return (T) Short.valueOf(num.shortValue());
61+
} else if (clazz == Byte.class) {
62+
return (T) Byte.valueOf(num.byteValue());
63+
} else {
64+
return (T) num;
65+
}
66+
}
4867
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl.test;
17+
18+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.function;
19+
20+
import io.serverlessworkflow.api.types.Workflow;
21+
import io.serverlessworkflow.fluent.func.FuncWorkflowBuilder;
22+
import io.serverlessworkflow.impl.WorkflowApplication;
23+
import io.serverlessworkflow.impl.WorkflowModel;
24+
import java.util.function.Function;
25+
import org.junit.jupiter.api.Assertions;
26+
import org.junit.jupiter.api.Test;
27+
28+
public class WorkflowNumberConversionTest {
29+
30+
@Test
31+
void incompatible_test() {
32+
Workflow workflow =
33+
FuncWorkflowBuilder.workflow("numbers")
34+
.tasks(
35+
function(
36+
"scoreProposal",
37+
(Proposal input) -> {
38+
Integer score = calculateScore(input.abstractText());
39+
System.out.println("Score calculated having the result as: " + score);
40+
return score;
41+
},
42+
Proposal.class)
43+
.outputAs(
44+
(Integer score) -> new ProposalScore(score, score >= 7), Integer.class))
45+
.build();
46+
47+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
48+
WorkflowModel model =
49+
app.workflowDefinition(workflow)
50+
.instance(new Proposal("Workflow, workflow, workflow..."))
51+
.start()
52+
.join();
53+
Assertions.assertNotNull(model);
54+
ProposalScore result = model.as(ProposalScore.class).orElseThrow();
55+
Assertions.assertEquals(10, result.score());
56+
Assertions.assertTrue(result.accepted());
57+
}
58+
}
59+
60+
@Test
61+
void long_to_integer_conversion() {
62+
Workflow workflow =
63+
FuncWorkflowBuilder.workflow("longToInt")
64+
.tasks(
65+
function("convertLong", Function.identity(), Long.class)
66+
.outputAs((Integer result) -> result * 2, Integer.class))
67+
.build();
68+
69+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
70+
WorkflowModel model = app.workflowDefinition(workflow).instance(100L).start().join();
71+
Integer result = model.as(Integer.class).orElseThrow();
72+
Assertions.assertEquals(200, result);
73+
}
74+
}
75+
76+
@Test
77+
void integer_to_long_conversion() {
78+
Workflow workflow =
79+
FuncWorkflowBuilder.workflow("intToLong")
80+
.tasks(
81+
function("convertInt", Function.identity(), Integer.class)
82+
.outputAs((Long result) -> result * 3L, Long.class))
83+
.build();
84+
85+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
86+
WorkflowModel model = app.workflowDefinition(workflow).instance(50).start().join();
87+
Long result = model.as(Long.class).orElseThrow();
88+
Assertions.assertEquals(150L, result);
89+
}
90+
}
91+
92+
@Test
93+
void double_to_integer_conversion() {
94+
Workflow workflow =
95+
FuncWorkflowBuilder.workflow("doubleToInt")
96+
.tasks(
97+
function("convertDouble", Function.identity(), Double.class)
98+
.outputAs((Integer result) -> result + 5, Integer.class))
99+
.build();
100+
101+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
102+
WorkflowModel model = app.workflowDefinition(workflow).instance(42.7).start().join();
103+
Integer result = model.as(Integer.class).orElseThrow();
104+
Assertions.assertEquals(47, result);
105+
}
106+
}
107+
108+
@Test
109+
void float_to_double_conversion() {
110+
Workflow workflow =
111+
FuncWorkflowBuilder.workflow("floatToDouble")
112+
.tasks(
113+
function("convertFloat", Function.identity(), Float.class)
114+
.outputAs((Double result) -> result * 1.5, Double.class))
115+
.build();
116+
117+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
118+
WorkflowModel model = app.workflowDefinition(workflow).instance(10.0f).start().join();
119+
Double result = model.as(Double.class).orElseThrow();
120+
Assertions.assertEquals(15.0, result, 0.001);
121+
}
122+
}
123+
124+
@Test
125+
void short_to_integer_conversion() {
126+
Workflow workflow =
127+
FuncWorkflowBuilder.workflow("shortToInt")
128+
.tasks(
129+
function("convertShort", (Short input) -> input.intValue(), Short.class)
130+
.outputAs((Integer result) -> result * 10, Integer.class))
131+
.build();
132+
133+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
134+
WorkflowModel model = app.workflowDefinition(workflow).instance((short) 5).start().join();
135+
Integer result = model.as(Integer.class).orElseThrow();
136+
Assertions.assertEquals(50, result);
137+
}
138+
}
139+
140+
@Test
141+
void byte_to_integer_conversion() {
142+
Workflow workflow =
143+
FuncWorkflowBuilder.workflow("byteToInt")
144+
.tasks(
145+
function("convertByte", Function.identity(), Byte.class)
146+
.outputAs((Integer result) -> result + 100, Integer.class))
147+
.build();
148+
149+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
150+
WorkflowModel model = app.workflowDefinition(workflow).instance((byte) 25).start().join();
151+
Integer result = model.as(Integer.class).orElseThrow();
152+
Assertions.assertEquals(125, result);
153+
}
154+
}
155+
156+
private Integer calculateScore(String abstractText) {
157+
return abstractText.contains("Workflow") ? 10 : 5;
158+
}
159+
160+
public record ProposalScore(Integer score, boolean accepted) {}
161+
162+
public record Proposal(String abstractText) {}
163+
}

0 commit comments

Comments
 (0)