Skip to content

Commit dfc5696

Browse files
authored
Introduce input, output methods to FuncDSL (#1197)
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
1 parent 9c56c07 commit dfc5696

File tree

2 files changed

+266
-0
lines changed

2 files changed

+266
-0
lines changed

experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncDSL.java

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,4 +1324,109 @@ public static FuncCallHttpStep post(
13241324

13251325
return http(name).POST().endpoint(endpoint, auth).body(body);
13261326
}
1327+
1328+
/**
1329+
* Extracts and deserializes the workflow input data into the specified type from a workflow
1330+
* context.
1331+
*
1332+
* <p>This utility method provides type-safe access to the workflow's initial input.
1333+
*
1334+
* <p>Use this method when you have access to the {@link WorkflowContextData} and need to retrieve
1335+
* the original input that was provided when the workflow instance was started.
1336+
*
1337+
* <p><b>Usage Example:</b>
1338+
*
1339+
* <pre>{@code
1340+
* inputFrom((object, WorkflowContextData workflowContext) -> {
1341+
* OrderRequest order = input(workflowContext, OrderRequest.class);
1342+
* return new Input(order);
1343+
* });
1344+
* }</pre>
1345+
*
1346+
* @param <T> the type to deserialize the input into
1347+
* @param context the workflow context containing instance data and input
1348+
* @param inputClass the class object representing the target type for deserialization
1349+
* @return the deserialized workflow input object of type T
1350+
*/
1351+
public static <T> T input(WorkflowContextData context, Class<T> inputClass) {
1352+
return context
1353+
.instanceData()
1354+
.input()
1355+
.as(inputClass)
1356+
.orElseThrow(
1357+
() ->
1358+
new IllegalStateException(
1359+
"Workflow input is missing or cannot be deserialized into type "
1360+
+ inputClass.getName()
1361+
+ " when calling FuncDSL.input(WorkflowContextData, Class<T>)."));
1362+
}
1363+
1364+
/**
1365+
* Extracts and deserializes the task input data into the specified type from a task context.
1366+
*
1367+
* <p>This utility method provides type-safe access to a task's input.
1368+
*
1369+
* <p>Use this method when you have access to the {@link TaskContextData} and need to retrieve the
1370+
* input provided to that task.
1371+
*
1372+
* <p><b>Usage Example:</b>
1373+
*
1374+
* <pre>{@code
1375+
* inputFrom((Object obj, TaskContextData taskContextData) -> {
1376+
* OrderRequest order = input(taskContextData, OrderRequest.class);
1377+
* return order;
1378+
* });
1379+
* }</pre>
1380+
*
1381+
* @param <T> the type to deserialize the input into
1382+
* @param taskContextData the task context from which to retrieve the task input
1383+
* @param inputClass the class object representing the target type for deserialization
1384+
* @return the deserialized task input object of type T
1385+
*/
1386+
public static <T> T input(TaskContextData taskContextData, Class<T> inputClass) {
1387+
return taskContextData
1388+
.input()
1389+
.as(inputClass)
1390+
.orElseThrow(
1391+
() ->
1392+
new IllegalStateException(
1393+
"Workflow input is missing or cannot be deserialized into type "
1394+
+ inputClass.getName()
1395+
+ " when calling FuncDSL.input(TaskContextData, Class<T>)."));
1396+
}
1397+
1398+
/**
1399+
* Extracts and deserializes the output data from a task into the specified type.
1400+
*
1401+
* <p>This utility method provides type-safe access to a task's output.
1402+
*
1403+
* <p>Use this method when you need to access the result/output produced by a task execution. This
1404+
* is particularly useful in subsequent tasks that need to process or transform the output of a
1405+
* previous task in the workflow.
1406+
*
1407+
* <p><b>Usage Example:</b>
1408+
*
1409+
* <pre>{@code
1410+
* .exportAs((object, workflowContext, taskContextData) -> {
1411+
* Long output = output(taskContextData, Long.class);
1412+
* return output * 2;
1413+
* })
1414+
* }</pre>
1415+
*
1416+
* @param <T> the type to deserialize the task output into
1417+
* @param taskContextData the task context containing the output data
1418+
* @param outputClass the class object representing the target type for deserialization
1419+
* @return the deserialized task output object of type T
1420+
*/
1421+
public static <T> T output(TaskContextData taskContextData, Class<T> outputClass) {
1422+
return taskContextData
1423+
.output()
1424+
.as(outputClass)
1425+
.orElseThrow(
1426+
() ->
1427+
new IllegalStateException(
1428+
"Task output is missing or cannot be deserialized into type "
1429+
+ outputClass.getName()
1430+
+ " when calling FuncDSL.output(TaskContextData, Class<T>)."));
1431+
}
13271432
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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.serverless.workflow.impl.executors.func;
17+
18+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.function;
19+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.input;
20+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.output;
21+
22+
import io.serverlessworkflow.api.types.Workflow;
23+
import io.serverlessworkflow.fluent.func.FuncWorkflowBuilder;
24+
import io.serverlessworkflow.impl.WorkflowApplication;
25+
import io.serverlessworkflow.impl.WorkflowDefinition;
26+
import io.serverlessworkflow.impl.WorkflowModel;
27+
import org.assertj.core.api.SoftAssertions;
28+
import org.junit.jupiter.api.Test;
29+
30+
public class FuncDSLDataFlowTransformationHelpersTest {
31+
32+
@Test
33+
void test_input_with_inputFrom() {
34+
35+
SoftAssertions softly = new SoftAssertions();
36+
37+
Workflow workflow =
38+
FuncWorkflowBuilder.workflow("reviewSubmissionWithModel")
39+
.tasks(
40+
function(
41+
"add5",
42+
(Long input) -> {
43+
softly.assertThat(input).isEqualTo(10L);
44+
return input + 5;
45+
},
46+
Long.class),
47+
function("returnEnriched", (Long enrichedValue) -> enrichedValue, Long.class)
48+
.inputFrom(
49+
(object, workflowContext) -> {
50+
softly.assertThat(object).isEqualTo(15L);
51+
Long input = input(workflowContext, Long.class);
52+
softly.assertThat(input).isEqualTo(10L);
53+
return object + input;
54+
},
55+
Long.class))
56+
.build();
57+
58+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
59+
WorkflowDefinition def = app.workflowDefinition(workflow);
60+
WorkflowModel model = def.instance(10L).start().join();
61+
Number number = model.asNumber().orElseThrow();
62+
softly.assertThat(number.longValue()).isEqualTo(25L);
63+
}
64+
65+
softly.assertAll();
66+
}
67+
68+
@Test
69+
void test_input_with_outputAs() {
70+
71+
SoftAssertions softly = new SoftAssertions();
72+
73+
Workflow workflow =
74+
FuncWorkflowBuilder.workflow("enrichOutputWithModelTest")
75+
.tasks(
76+
function(
77+
"add5",
78+
(Long input) -> {
79+
softly.assertThat(input).isEqualTo(10L);
80+
return input + 5;
81+
},
82+
Long.class)
83+
.outputAs(
84+
(object, workflowContext, taskContextData) -> {
85+
softly.assertThat(object).isEqualTo(15L);
86+
Long input = input(workflowContext, Long.class);
87+
softly.assertThat(input).isEqualTo(10L);
88+
return input + object;
89+
},
90+
Long.class))
91+
.build();
92+
93+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
94+
WorkflowDefinition def = app.workflowDefinition(workflow);
95+
96+
WorkflowModel model = def.instance(10L).start().join();
97+
Number number = model.asNumber().orElseThrow();
98+
99+
softly.assertThat(number.longValue()).isEqualTo(25L);
100+
}
101+
102+
softly.assertAll();
103+
}
104+
105+
@Test
106+
void test_output_with_exportAs() {
107+
108+
SoftAssertions softly = new SoftAssertions();
109+
110+
Workflow workflow =
111+
FuncWorkflowBuilder.workflow("enrichOutputWithInputTest")
112+
.tasks(
113+
function(
114+
"add5",
115+
(Long input) -> {
116+
softly.assertThat(input).isEqualTo(10L);
117+
return input + 5;
118+
},
119+
Long.class)
120+
.exportAs(
121+
(object, workflowContext, taskContextData) -> {
122+
Long taskOutput = output(taskContextData, Long.class);
123+
softly.assertThat(taskOutput).isEqualTo(15L);
124+
return taskOutput * 2;
125+
}))
126+
.build();
127+
128+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
129+
WorkflowDefinition def = app.workflowDefinition(workflow);
130+
WorkflowModel model = def.instance(10L).start().join();
131+
Number number = model.asNumber().orElseThrow();
132+
softly.assertThat(number.longValue()).isEqualTo(15L);
133+
}
134+
135+
softly.assertAll();
136+
}
137+
138+
@Test
139+
void test_input_with_inputFrom_fluent_way() {
140+
SoftAssertions softly = new SoftAssertions();
141+
142+
Workflow workflow =
143+
FuncWorkflowBuilder.workflow("enrichOutputWithInputTest")
144+
.tasks(
145+
function("sumFive", (Long input) -> input + 5, Long.class)
146+
.inputFrom(
147+
(object, workflowContext, taskContextData) ->
148+
input(taskContextData, Long.class) * 2))
149+
.build();
150+
151+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
152+
WorkflowDefinition def = app.workflowDefinition(workflow);
153+
WorkflowModel model = def.instance(10L).start().join();
154+
Number number = model.asNumber().orElseThrow();
155+
156+
softly.assertThat(number.longValue()).isEqualTo(25L);
157+
}
158+
159+
softly.assertAll();
160+
}
161+
}

0 commit comments

Comments
 (0)