Skip to content

Commit 0dacd52

Browse files
committed
Introduce input and output helpers
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
1 parent f95b082 commit 0dacd52

3 files changed

Lines changed: 289 additions & 0 deletions

File tree

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,4 +1322,85 @@ public static FuncCallHttpStep post(
13221322

13231323
return http(name).POST().endpoint(endpoint, auth).body(body);
13241324
}
1325+
1326+
/**
1327+
* Extracts and deserializes the workflow input data into the specified type from a workflow
1328+
* context.
1329+
*
1330+
* <p>This utility method provides type-safe access to the workflow's initial input.
1331+
*
1332+
* <p>Use this method when you have access to the {@link WorkflowContextData} and need to retrieve
1333+
* the original input that was provided when the workflow instance was started.
1334+
*
1335+
* <p><b>Usage Example:</b>
1336+
*
1337+
* <pre>{@code
1338+
* inputFrom((object, WorkflowContextData workflowContext) -> {
1339+
* OrderRequest order = workflowInput(workflowContext, OrderRequest.class);
1340+
* return new Input(order);
1341+
* }
1342+
* }</pre>
1343+
*
1344+
* @param <T> the type to deserialize the input into
1345+
* @param context the workflow context containing instance data and input
1346+
* @param inputClass the class object representing the target type for deserialization
1347+
* @return the deserialized workflow input object of type T
1348+
*/
1349+
public static <T> T input(WorkflowContextData context, Class<T> inputClass) {
1350+
return context.instanceData().input().as(inputClass).orElseThrow();
1351+
}
1352+
1353+
/**
1354+
* Extracts and deserializes the workflow input data into the specified type from a task context.
1355+
*
1356+
* <p>This utility method provides type-safe access to the workflow's initial input.
1357+
*
1358+
* <p>Use this method when you only have access to the {@link TaskContextData} but need to
1359+
* retrieve the original workflow input (not the task's input). This is useful when a task needs
1360+
* to reference the initial workflow input data.
1361+
*
1362+
* <p><b>Usage Example:</b>
1363+
*
1364+
* <pre>{@code
1365+
* inputFrom((Object obj, TaskContextData taskContextData) -> {
1366+
* OrderRequest order = taskInput(taskContextData, OrderRequest.class);
1367+
* return new Input(order);
1368+
* }
1369+
* }</pre>
1370+
*
1371+
* @param <T> the type to deserialize the input into
1372+
* @param taskContextData the task context from which to retrieve the workflow input
1373+
* @param inputClass the class object representing the target type for deserialization
1374+
* @return the deserialized workflow input object of type T
1375+
*/
1376+
public static <T> T input(TaskContextData taskContextData, Class<T> inputClass) {
1377+
return taskContextData.input().as(inputClass).orElseThrow();
1378+
}
1379+
1380+
/**
1381+
* Extracts and deserializes the output data from a task into the specified type.
1382+
*
1383+
* <p>This utility method provides type-safe access to a task's output.
1384+
*
1385+
* <p>Use this method when you need to access the result/output produced by a task execution. This
1386+
* is particularly useful in subsequent tasks that need to process or transform the output of a
1387+
* previous task in the workflow.
1388+
*
1389+
* <p><b>Usage Example:</b>
1390+
*
1391+
* <pre>{@code
1392+
* .exportAs((object, workflowContext, taskContextData) -> {
1393+
* Long input = taskOutput(taskContextData, Long.class);
1394+
* return input * 2;
1395+
* }))
1396+
* }</pre>
1397+
*
1398+
* @param <T> the type to deserialize the task output into
1399+
* @param taskContextData the task context containing the output data
1400+
* @param inputClass the class object representing the target type for deserialization
1401+
* @return the deserialized task output object of type T
1402+
*/
1403+
public static <T> T output(TaskContextData taskContextData, Class<T> inputClass) {
1404+
return taskContextData.output().as(inputClass).orElseThrow();
1405+
}
13251406
}

impl/test/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@
9494
<artifactId>grpc-netty</artifactId>
9595
<scope>test</scope>
9696
</dependency>
97+
<dependency>
98+
<groupId>io.serverlessworkflow</groupId>
99+
<artifactId>serverlessworkflow-experimental-fluent-func</artifactId>
100+
<scope>test</scope>
101+
<version>${project.version}</version>
102+
</dependency>
103+
<dependency>
104+
<groupId>io.serverlessworkflow</groupId>
105+
<artifactId>serverlessworkflow-experimental-lambda</artifactId>
106+
<scope>test</scope>
107+
<version>${project.version}</version>
108+
</dependency>
97109
</dependencies>
98110
<build>
99111
<resources>
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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+
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+
class FuncDSLEnrichWithTest {
31+
32+
@Test
33+
void test_input_with_input_from() {
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_enrich_Input_with_workflowInput_task() {
70+
71+
SoftAssertions softly = new SoftAssertions();
72+
73+
Workflow workflow =
74+
FuncWorkflowBuilder.workflow("enrichWithInputTest")
75+
.tasks(
76+
function(
77+
"add5",
78+
(Long input) -> {
79+
softly.assertThat(input).isEqualTo(10L);
80+
return input + 5;
81+
},
82+
Long.class),
83+
function(
84+
"returnEnriched",
85+
(Long enrichedValue) -> {
86+
softly.assertThat(enrichedValue).isEqualTo(10L);
87+
return enrichedValue;
88+
},
89+
Long.class)
90+
.inputFrom((object, workflowContext) -> input(workflowContext, Long.class)))
91+
.build();
92+
93+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
94+
WorkflowDefinition def = app.workflowDefinition(workflow);
95+
WorkflowModel model = def.instance(10L).start().join();
96+
Number number = model.asNumber().orElseThrow();
97+
softly.assertThat(number).isEqualTo(10L);
98+
}
99+
100+
softly.assertAll();
101+
}
102+
103+
@Test
104+
void test_output_as_with_input_utility() {
105+
106+
SoftAssertions softly = new SoftAssertions();
107+
108+
Workflow workflow =
109+
FuncWorkflowBuilder.workflow("enrichOutputWithModelTest")
110+
.tasks(
111+
function(
112+
"add5",
113+
(Long input) -> {
114+
softly.assertThat(input).isEqualTo(10L);
115+
return input + 5;
116+
},
117+
Long.class)
118+
.outputAs(
119+
(object, workflowContext, taskContextData) -> {
120+
softly.assertThat(object).isEqualTo(15L);
121+
Long input = input(workflowContext, Long.class);
122+
softly.assertThat(input).isEqualTo(10L);
123+
return input + object;
124+
},
125+
Long.class))
126+
.build();
127+
128+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
129+
WorkflowDefinition def = app.workflowDefinition(workflow);
130+
131+
WorkflowModel model = def.instance(10L).start().join();
132+
Number number = model.asNumber().orElseThrow();
133+
134+
softly.assertThat(number.longValue()).isEqualTo(25L);
135+
}
136+
137+
softly.assertAll();
138+
}
139+
140+
@Test
141+
void test_export_as_with_input_utility() {
142+
143+
SoftAssertions softly = new SoftAssertions();
144+
145+
Workflow workflow =
146+
FuncWorkflowBuilder.workflow("enrichOutputWithInputTest")
147+
.tasks(
148+
function(
149+
"add5",
150+
(Long input) -> {
151+
softly.assertThat(input).isEqualTo(10L);
152+
return input + 5;
153+
},
154+
Long.class)
155+
.exportAs(
156+
(object, workflowContext, taskContextData) -> {
157+
Long input = output(taskContextData, Long.class);
158+
softly.assertThat(input).isEqualTo(15L);
159+
return input * 2;
160+
}))
161+
.build();
162+
163+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
164+
WorkflowDefinition def = app.workflowDefinition(workflow);
165+
WorkflowModel model = def.instance(10L).start().join();
166+
Number number = model.asNumber().orElseThrow();
167+
softly.assertThat(number.longValue()).isEqualTo(15L);
168+
}
169+
170+
softly.assertAll();
171+
}
172+
173+
@Test
174+
void test_using_fluent() {
175+
SoftAssertions softly = new SoftAssertions();
176+
177+
Workflow workflow =
178+
FuncWorkflowBuilder.workflow("enrichOutputWithInputTest")
179+
.tasks(
180+
function("sumFive", (Long input) -> input + 5, Long.class)
181+
.inputFrom(
182+
(object, workflowContext, taskContextData) ->
183+
input(taskContextData, Long.class) * 2))
184+
.build();
185+
186+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
187+
WorkflowDefinition def = app.workflowDefinition(workflow);
188+
WorkflowModel model = def.instance(10L).start().join();
189+
Number number = model.asNumber().orElseThrow();
190+
191+
softly.assertThat(number.longValue()).isEqualTo(25L);
192+
}
193+
194+
softly.assertAll();
195+
}
196+
}

0 commit comments

Comments
 (0)