Skip to content

Commit 90df169

Browse files
committed
Add enriched and enrichedOutput DSL functions
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
1 parent f95b082 commit 90df169

File tree

5 files changed

+344
-0
lines changed

5 files changed

+344
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.fluent.func.dsl;
17+
18+
import io.serverlessworkflow.impl.WorkflowModel;
19+
20+
/**
21+
* A function that enriches the current state by combining it with the root workflow input.
22+
*
23+
* <p>This is useful when you need to merge the last task output with the original workflow input,
24+
* where the last state is typed and the root input is a WorkflowModel.
25+
*
26+
* @param <T> The type of the last state
27+
* @param <R> The type of the enriched result
28+
*/
29+
@FunctionalInterface
30+
public interface EnrichWithModelBiFunction<T, R> {
31+
/**
32+
* Applies this function to enrich the last state with the root workflow input.
33+
*
34+
* @param lastState the output from the previous task with its actual type
35+
* @param rootInput the original workflow input as WorkflowModel
36+
* @return the enriched result combining both inputs
37+
*/
38+
R apply(T lastState, WorkflowModel rootInput);
39+
}

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

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import io.serverlessworkflow.fluent.spec.configurers.AuthenticationConfigurer;
3333
import io.serverlessworkflow.impl.TaskContextData;
3434
import io.serverlessworkflow.impl.WorkflowContextData;
35+
import io.serverlessworkflow.impl.WorkflowModel;
3536
import java.net.URI;
3637
import java.util.Collection;
3738
import java.util.List;
@@ -279,6 +280,117 @@ public static FuncPredicateEventConfigurer event(String type) {
279280
return OPS.event(type);
280281
}
281282

283+
/**
284+
* Create an input transformation that enriches the typed last state with the root workflow input
285+
* as WorkflowModel.
286+
*
287+
* <p>This is useful when you want to combine the last task output (with its actual type) with the
288+
* original workflow input as WorkflowModel.
289+
*
290+
* <p>Example usage:
291+
*
292+
* <pre>{@code
293+
* function("processData", (Long input) -> input + 5, Long.class),
294+
* function("combineData", (Long enrichedValue) -> enrichedValue, Long.class)
295+
* .enrichInputWith((lastState, rootInputModel) -> {
296+
* Long rootInput = rootInputModel.as(Long.class).orElse(0L);
297+
* return lastState + rootInput;
298+
* }, Long.class)
299+
* }</pre>
300+
*
301+
* @param fn the enrichment function that receives typed lastState and WorkflowModel rootInput
302+
* @param lastStateClass the class of the last state type
303+
* @param <T> the type of the last state
304+
* @param <R> the type of the enriched result
305+
* @return a JavaContextFunction that can be used with inputFrom
306+
*/
307+
public static <T, R> JavaContextFunction<T, R> enriched(
308+
EnrichWithModelBiFunction<T, R> fn, Class<T> lastStateClass) {
309+
return (lastState, workflowContext) -> {
310+
final WorkflowModel rootInput = workflowContext.instanceData().input();
311+
return fn.apply(lastState, rootInput);
312+
};
313+
}
314+
315+
/**
316+
* Create an input transformation that uses only the root workflow input as WorkflowModel.
317+
*
318+
* <p>This is useful when you want to transform the task input based solely on the original
319+
* workflow input, ignoring the last state.
320+
*
321+
* <p>Example usage:
322+
*
323+
* <pre>{@code
324+
* function("processData", (Long input) -> input * 2, Long.class)
325+
* .enrichWithInput(rootInput -> rootInput.asNumber().orElseThrow())
326+
* }</pre>
327+
*
328+
* @param fn the function that receives the root workflow input and returns the enriched result
329+
* @param <R> the type of the enriched result
330+
* @return a JavaContextFunction that can be used with inputFrom
331+
*/
332+
public static <R> JavaContextFunction<Object, R> enriched(Function<WorkflowModel, R> fn) {
333+
return (lastState, workflowContext) -> {
334+
final WorkflowModel rootInput = workflowContext.instanceData().input();
335+
return fn.apply(rootInput);
336+
};
337+
}
338+
339+
/**
340+
* Create an output transformation that enriches the typed task output with the root workflow
341+
* input as WorkflowModel.
342+
*
343+
* <p>This is useful when you want to combine the task output (with its actual type) with the
344+
* original workflow input as WorkflowModel.
345+
*
346+
* <p>Example usage:
347+
*
348+
* <pre>{@code
349+
* function("processData", (Long input) -> input + 5, Long.class)
350+
* .outputAs(FuncDSL.enrichWithModel((taskOutput, rootInputModel) -> {
351+
* Long rootInput = rootInputModel.as(Long.class).orElse(0L);
352+
* return taskOutput + rootInput;
353+
* }, Long.class))
354+
* }</pre>
355+
*
356+
* @param fn the enrichment function that receives typed taskOutput and WorkflowModel rootInput
357+
* @param taskOutputClass the class of the task output type
358+
* @param <T> the type of the task output
359+
* @param <R> the type of the enriched result
360+
* @return a JavaContextFunction that can be used with outputAs
361+
*/
362+
public static <T, R> JavaContextFunction<T, R> enrichedOutput(
363+
EnrichWithModelBiFunction<T, R> fn, Class<T> taskOutputClass) {
364+
return (taskOutput, workflowContext) -> {
365+
final WorkflowModel rootInput = workflowContext.instanceData().input();
366+
return fn.apply(taskOutput, rootInput);
367+
};
368+
}
369+
370+
/**
371+
* Create an output transformation that uses only the root workflow input as WorkflowModel.
372+
*
373+
* <p>This is useful when you want to transform the task output based solely on the original
374+
* workflow input, ignoring the actual task output.
375+
*
376+
* <p>Example usage:
377+
*
378+
* <pre>{@code
379+
* function("processData", (Long input) -> input * 2, Long.class)
380+
* .outputAs(FuncDSL.enrichWithInput(rootInput -> rootInput.asNumber().orElseThrow()))
381+
* }</pre>
382+
*
383+
* @param fn the function that receives the root workflow input and returns the enriched result
384+
* @param <R> the type of the enriched result
385+
* @return a JavaContextFunction that can be used with outputAs
386+
*/
387+
public static <R> JavaContextFunction<Object, R> enrichedOutput(Function<WorkflowModel, R> fn) {
388+
return (taskOutput, workflowContext) -> {
389+
final WorkflowModel rootInput = workflowContext.instanceData().input();
390+
return fn.apply(rootInput);
391+
};
392+
}
393+
282394
/**
283395
* Create a {@link FuncCallStep} that calls a simple Java {@link Function} with explicit input
284396
* type.

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder;
2323
import io.serverlessworkflow.fluent.func.spi.FuncTaskTransformations;
2424
import io.serverlessworkflow.fluent.spec.TaskBaseBuilder;
25+
2526
import java.util.ArrayList;
2627
import java.util.List;
2728
import java.util.function.Consumer;
@@ -225,4 +226,5 @@ public final void accept(FuncTaskItemListBuilder list) {
225226
private void applyPost(B builder) {
226227
for (Consumer<B> c : postConfigurers) c.accept(builder);
227228
}
229+
228230
}

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: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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.enriched;
19+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.enrichedOutput;
20+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.function;
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_enrich_with_model_in_workflow() {
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+
enriched(
50+
(lastState, rootInputModel) -> {
51+
softly.assertThat(lastState).isEqualTo(15L);
52+
Long originalInput = rootInputModel.as(Long.class).orElse(0L);
53+
softly.assertThat(originalInput).isEqualTo(10L);
54+
return lastState + originalInput;
55+
},
56+
Long.class)))
57+
.build();
58+
59+
WorkflowApplication app = WorkflowApplication.builder().build();
60+
WorkflowDefinition def = app.workflowDefinition(workflow);
61+
62+
WorkflowModel model = def.instance(10L).start().join();
63+
Number number = model.asNumber().orElseThrow();
64+
65+
softly.assertThat(number.longValue()).isEqualTo(25L);
66+
67+
softly.assertAll();
68+
}
69+
70+
@Test
71+
void test_enrich_with_input_workflow() {
72+
73+
SoftAssertions softly = new SoftAssertions();
74+
75+
Workflow workflow =
76+
FuncWorkflowBuilder.workflow("enrichWithInputTest")
77+
.tasks(
78+
function(
79+
"add5",
80+
(Long input) -> {
81+
softly.assertThat(input).isEqualTo(10L);
82+
return input + 5;
83+
},
84+
Long.class),
85+
function(
86+
"returnEnriched",
87+
(Long enrichedValue) -> {
88+
softly.assertThat(enrichedValue).isEqualTo(10L);
89+
return enrichedValue;
90+
},
91+
Long.class)
92+
.inputFrom(
93+
enriched(rootInput -> rootInput.asNumber().orElseThrow().longValue())))
94+
.build();
95+
96+
WorkflowApplication app = WorkflowApplication.builder().build();
97+
WorkflowDefinition def = app.workflowDefinition(workflow);
98+
99+
WorkflowModel model = def.instance(10L).start().join();
100+
Number number = model.asNumber().orElseThrow();
101+
102+
softly.assertThat(number).isEqualTo(10L);
103+
}
104+
105+
@Test
106+
void test_enrich_output_with_model_in_workflow() {
107+
108+
SoftAssertions softly = new SoftAssertions();
109+
110+
Workflow workflow =
111+
FuncWorkflowBuilder.workflow("enrichOutputWithModelTest")
112+
.tasks(
113+
function(
114+
"add5",
115+
(Long input) -> {
116+
softly.assertThat(input).isEqualTo(10L);
117+
return input + 5;
118+
},
119+
Long.class)
120+
.outputAs(
121+
enrichedOutput(
122+
(taskOutput, rootInputModel) -> {
123+
// taskOutput is typed as Long (result from add5) = 15
124+
softly.assertThat(taskOutput).isEqualTo(15L);
125+
// rootInput is WorkflowModel (original input) = 10
126+
Long originalInput = rootInputModel.as(Long.class).orElse(0L);
127+
softly.assertThat(originalInput).isEqualTo(10L);
128+
// Return the sum: 15 + 10
129+
return taskOutput + originalInput;
130+
},
131+
Long.class)))
132+
.build();
133+
134+
WorkflowApplication app = WorkflowApplication.builder().build();
135+
WorkflowDefinition def = app.workflowDefinition(workflow);
136+
137+
WorkflowModel model = def.instance(10L).start().join();
138+
Number number = model.asNumber().orElseThrow();
139+
140+
softly.assertThat(number.longValue()).isEqualTo(25L);
141+
142+
softly.assertAll();
143+
}
144+
145+
@Test
146+
void test_enrich_output_with_input_in_workflow() {
147+
148+
SoftAssertions softly = new SoftAssertions();
149+
150+
Workflow workflow =
151+
FuncWorkflowBuilder.workflow("enrichOutputWithInputTest")
152+
.tasks(
153+
function(
154+
"add5",
155+
(Long input) -> {
156+
softly.assertThat(input).isEqualTo(10L);
157+
return input + 5;
158+
},
159+
Long.class)
160+
.outputAs(
161+
enrichedOutput(
162+
rootInput -> {
163+
Long value = rootInput.asNumber().orElseThrow().longValue();
164+
softly.assertThat(value).isEqualTo(10L);
165+
return value;
166+
})))
167+
.build();
168+
169+
WorkflowApplication app = WorkflowApplication.builder().build();
170+
WorkflowDefinition def = app.workflowDefinition(workflow);
171+
172+
WorkflowModel model = def.instance(10L).start().join();
173+
Number number = model.asNumber().orElseThrow();
174+
175+
softly.assertThat(number.longValue()).isEqualTo(10L);
176+
177+
softly.assertAll();
178+
}
179+
}

0 commit comments

Comments
 (0)