Skip to content

Commit 5ad0078

Browse files
feat: Implement FuncDoTaskBuilder to also cover raise and tryCatch (#1322)
* feat: Implement FuncDoTaskBuilder to also cover raise and tryCatch Signed-off-by: Matheus Andre <matheusandr2@gmail.com> Signed-off-by: Matheus André <matheusandr2@gmail.com> * add test FuncDoTaskTest, testDoTaskRaiseAndTryCatch Signed-off-by: Matheus André <matheusandr2@gmail.com> * refactor: RaiseTask and TryTask builders to extend base builder classes Signed-off-by: Matheus André <matheusandr2@gmail.com> --------- Signed-off-by: Matheus Andre <matheusandr2@gmail.com> Signed-off-by: Matheus André <matheusandr2@gmail.com>
1 parent ac16ede commit 5ad0078

13 files changed

Lines changed: 853 additions & 450 deletions

File tree

experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncDoTaskBuilder.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ public FuncDoTaskBuilder listen(String name, Consumer<FuncListenTaskBuilder> ite
4747
return this;
4848
}
4949

50+
@Override
51+
public FuncDoTaskBuilder raise(String name, Consumer<FuncRaiseTaskBuilder> itemsConfigurer) {
52+
this.listBuilder().raise(name, itemsConfigurer);
53+
return this;
54+
}
55+
5056
@Override
5157
public FuncDoTaskBuilder forEach(String name, Consumer<FuncForTaskBuilder> itemsConfigurer) {
5258
this.listBuilder().forEach(name, itemsConfigurer);
@@ -96,4 +102,10 @@ public FuncDoTaskBuilder openapi(
96102
this.listBuilder().openapi(name, itemsConfigurer);
97103
return this;
98104
}
105+
106+
@Override
107+
public FuncDoTaskBuilder tryCatch(String name, Consumer<FuncTryTaskBuilder> itemsConfigurer) {
108+
this.listBuilder().tryCatch(name, itemsConfigurer);
109+
return this;
110+
}
99111
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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;
17+
18+
import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder;
19+
import io.serverlessworkflow.fluent.func.spi.FuncTaskTransformations;
20+
import io.serverlessworkflow.fluent.spec.BaseRaiseTaskBuilder;
21+
22+
public class FuncRaiseTaskBuilder extends BaseRaiseTaskBuilder<FuncRaiseTaskBuilder>
23+
implements FuncTaskTransformations<FuncRaiseTaskBuilder>,
24+
ConditionalTaskBuilder<FuncRaiseTaskBuilder> {
25+
26+
FuncRaiseTaskBuilder() {
27+
super();
28+
}
29+
30+
@Override
31+
protected FuncRaiseTaskBuilder self() {
32+
return this;
33+
}
34+
}

experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ public FuncTaskItemListBuilder listen(
9393
new TaskItem(name, new Task().withListenTask(listenTaskJavaBuilder.build())));
9494
}
9595

96+
@Override
97+
public FuncTaskItemListBuilder raise(
98+
String name, Consumer<FuncRaiseTaskBuilder> itemsConfigurer) {
99+
name = this.defaultNameAndRequireConfig(name, itemsConfigurer, TYPE_RAISE);
100+
final FuncRaiseTaskBuilder raiseTaskJavaBuilder = new FuncRaiseTaskBuilder();
101+
itemsConfigurer.accept(raiseTaskJavaBuilder);
102+
return this.addTaskItem(
103+
new TaskItem(name, new Task().withRaiseTask(raiseTaskJavaBuilder.build())));
104+
}
105+
96106
@Override
97107
public FuncTaskItemListBuilder forEach(
98108
String name, Consumer<FuncForTaskBuilder> itemsConfigurer) {
@@ -154,4 +164,13 @@ public FuncTaskItemListBuilder openapi(
154164

155165
return this.addTaskItem(new TaskItem(name, task));
156166
}
167+
168+
@Override
169+
public FuncTaskItemListBuilder tryCatch(
170+
String name, Consumer<FuncTryTaskBuilder> itemsConfigurer) {
171+
name = this.defaultNameAndRequireConfig(name, itemsConfigurer, TYPE_TRY);
172+
final FuncTryTaskBuilder tryTaskBuilder = new FuncTryTaskBuilder();
173+
itemsConfigurer.accept(tryTaskBuilder);
174+
return this.addTaskItem(new TaskItem(name, new Task().withTryTask(tryTaskBuilder.build())));
175+
}
157176
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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;
17+
18+
import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder;
19+
import io.serverlessworkflow.fluent.func.spi.FuncTaskTransformations;
20+
import io.serverlessworkflow.fluent.spec.BaseTryTaskBuilder;
21+
22+
public class FuncTryTaskBuilder
23+
extends BaseTryTaskBuilder<FuncTryTaskBuilder, FuncTaskItemListBuilder>
24+
implements FuncTaskTransformations<FuncTryTaskBuilder>,
25+
ConditionalTaskBuilder<FuncTryTaskBuilder> {
26+
27+
FuncTryTaskBuilder() {
28+
super(new FuncTaskItemListBuilder(0));
29+
}
30+
31+
@Override
32+
protected FuncTryTaskBuilder self() {
33+
return this;
34+
}
35+
}

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
import io.serverlessworkflow.api.types.func.FilterFunction;
2929
import io.serverlessworkflow.fluent.func.FuncCallTaskBuilder;
3030
import io.serverlessworkflow.fluent.func.FuncEmitTaskBuilder;
31+
import io.serverlessworkflow.fluent.func.FuncRaiseTaskBuilder;
3132
import io.serverlessworkflow.fluent.func.FuncSwitchTaskBuilder;
3233
import io.serverlessworkflow.fluent.func.FuncTaskItemListBuilder;
34+
import io.serverlessworkflow.fluent.func.FuncTryTaskBuilder;
3335
import io.serverlessworkflow.fluent.func.configurers.FuncCallHttpConfigurer;
3436
import io.serverlessworkflow.fluent.func.configurers.FuncCallOpenAPIConfigurer;
3537
import io.serverlessworkflow.fluent.func.configurers.FuncTaskConfigurer;
@@ -883,6 +885,48 @@ public static FuncTaskConfigurer switchCase(String taskName, SwitchCaseConfigure
883885
return list -> list.switchCase(taskName, s -> snapshot.forEach(s::onPredicate));
884886
}
885887

888+
/**
889+
* Create a {@link FuncTaskConfigurer} that adds a {@code raise} task.
890+
*
891+
* @param configurer raise task builder configurer
892+
* @return list configurer
893+
*/
894+
public static FuncTaskConfigurer raise(Consumer<FuncRaiseTaskBuilder> configurer) {
895+
return list -> list.raise(configurer);
896+
}
897+
898+
/**
899+
* Create a {@link FuncTaskConfigurer} that adds a named {@code raise} task.
900+
*
901+
* @param name task name
902+
* @param configurer raise task builder configurer
903+
* @return list configurer
904+
*/
905+
public static FuncTaskConfigurer raise(String name, Consumer<FuncRaiseTaskBuilder> configurer) {
906+
return list -> list.raise(name, configurer);
907+
}
908+
909+
/**
910+
* Create a {@link FuncTaskConfigurer} that adds a {@code try/catch} task.
911+
*
912+
* @param configurer try task builder configurer
913+
* @return list configurer
914+
*/
915+
public static FuncTaskConfigurer tryCatch(Consumer<FuncTryTaskBuilder> configurer) {
916+
return list -> list.tryCatch(configurer);
917+
}
918+
919+
/**
920+
* Create a {@link FuncTaskConfigurer} that adds a named {@code try/catch} task.
921+
*
922+
* @param name task name
923+
* @param configurer try task builder configurer
924+
* @return list configurer
925+
*/
926+
public static FuncTaskConfigurer tryCatch(String name, Consumer<FuncTryTaskBuilder> configurer) {
927+
return list -> list.tryCatch(name, configurer);
928+
}
929+
886930
/**
887931
* Sugar for a single-case switch: if predicate matches, jump to {@code thenTask}.
888932
*

experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncDoFluent.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,20 @@
2222
import io.serverlessworkflow.fluent.func.FuncForTaskBuilder;
2323
import io.serverlessworkflow.fluent.func.FuncForkTaskBuilder;
2424
import io.serverlessworkflow.fluent.func.FuncListenTaskBuilder;
25+
import io.serverlessworkflow.fluent.func.FuncRaiseTaskBuilder;
2526
import io.serverlessworkflow.fluent.func.FuncSetTaskBuilder;
2627
import io.serverlessworkflow.fluent.func.FuncSwitchTaskBuilder;
28+
import io.serverlessworkflow.fluent.func.FuncTryTaskBuilder;
2729
import io.serverlessworkflow.fluent.spec.spi.CallHttpFluent;
2830
import io.serverlessworkflow.fluent.spec.spi.CallOpenAPIFluent;
2931
import io.serverlessworkflow.fluent.spec.spi.EmitFluent;
3032
import io.serverlessworkflow.fluent.spec.spi.ForEachFluent;
3133
import io.serverlessworkflow.fluent.spec.spi.ForkFluent;
3234
import io.serverlessworkflow.fluent.spec.spi.ListenFluent;
35+
import io.serverlessworkflow.fluent.spec.spi.RaiseFluent;
3336
import io.serverlessworkflow.fluent.spec.spi.SetFluent;
3437
import io.serverlessworkflow.fluent.spec.spi.SwitchFluent;
38+
import io.serverlessworkflow.fluent.spec.spi.TryCatchFluent;
3539

3640
public interface FuncDoFluent<SELF extends FuncDoFluent<SELF>>
3741
extends SetFluent<FuncSetTaskBuilder, SELF>,
@@ -40,6 +44,8 @@ public interface FuncDoFluent<SELF extends FuncDoFluent<SELF>>
4044
SwitchFluent<FuncSwitchTaskBuilder, SELF>,
4145
ForkFluent<FuncForkTaskBuilder, SELF>,
4246
ListenFluent<FuncListenTaskBuilder, SELF>,
47+
RaiseFluent<FuncRaiseTaskBuilder, SELF>,
48+
TryCatchFluent<FuncTryTaskBuilder, SELF>,
4349
CallFnFluent<FuncCallTaskBuilder, SELF>,
4450
CallHttpFluent<FuncCallHttpTaskBuilder, SELF>,
4551
CallOpenAPIFluent<FuncCallOpenAPITaskBuilder, SELF> {}

experimental/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/FuncDSLTest.java

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.http;
2424
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.listen;
2525
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.produced;
26+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.raise;
2627
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.switchWhenOrElse;
2728
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.toOne;
29+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.tryCatch;
2830
import static io.serverlessworkflow.fluent.spec.dsl.DSL.use;
2931
import static org.junit.jupiter.api.Assertions.assertEquals;
3032
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
@@ -174,6 +176,133 @@ void mixed_chaining_order_and_exports() {
174176
assertNotNull(t2.getListenTask().getExport(), "listen step should carry export");
175177
}
176178

179+
@Test
180+
void raise_and_tryCatch_build_through_func_workflow_builder() {
181+
Workflow wf =
182+
FuncWorkflowBuilder.workflow("raise-try")
183+
.tasks(
184+
FuncDSL.tasks(
185+
raise("boom", r -> r.error(e -> e.type("org.acme.Boom").status(409))),
186+
tryCatch(
187+
"guarded",
188+
t ->
189+
t.tryHandler(
190+
tb ->
191+
tb.function(
192+
cb ->
193+
cb.function((String s) -> s.trim(), String.class)))
194+
.catchHandler(
195+
c ->
196+
c.when("$.errorType == 'TEMP'")
197+
.doTasks(
198+
d ->
199+
d.raise(
200+
"handled",
201+
r ->
202+
r.error(
203+
e ->
204+
e.type("org.acme.Handled")
205+
.status(500))))))))
206+
.build();
207+
208+
List<TaskItem> items = wf.getDo();
209+
assertEquals(2, items.size());
210+
211+
Task raiseTask = items.get(0).getTask();
212+
assertNotNull(raiseTask.getRaiseTask(), "RaiseTask expected");
213+
var type = raiseTask.getRaiseTask().getRaise().getError().getRaiseErrorDefinition().getType();
214+
assertNotNull(type, "Error type expected");
215+
assertEquals(
216+
"org.acme.Boom",
217+
type.getLiteralErrorType() != null
218+
? type.getLiteralErrorType().getLiteralUri().toString()
219+
: type.getExpressionErrorType());
220+
assertEquals(
221+
409, raiseTask.getRaiseTask().getRaise().getError().getRaiseErrorDefinition().getStatus());
222+
223+
Task tryTask = items.get(1).getTask();
224+
assertNotNull(tryTask.getTryTask(), "TryTask expected");
225+
assertEquals(1, tryTask.getTryTask().getTry().size(), "Try block should contain one task");
226+
assertNotNull(
227+
tryTask.getTryTask().getTry().get(0).getTask().getCallTask(),
228+
"Function task should compile inside try");
229+
230+
assertNotNull(tryTask.getTryTask().getCatch(), "Catch block expected");
231+
assertEquals("$.errorType == 'TEMP'", tryTask.getTryTask().getCatch().getWhen());
232+
assertEquals(
233+
1, tryTask.getTryTask().getCatch().getDo().size(), "Catch block should contain one task");
234+
assertNotNull(
235+
tryTask.getTryTask().getCatch().getDo().get(0).getTask().getRaiseTask(),
236+
"Raise task should compile inside catch");
237+
}
238+
239+
@Test
240+
@DisplayName("tryCatch.when(String) / then(String) / exportAs(String) no longer NPE")
241+
void tryCatch_inherited_task_base_builder_methods_do_not_npe() {
242+
Workflow wf =
243+
FuncWorkflowBuilder.workflow("try-base-methods")
244+
.tasks(
245+
tryCatch(
246+
"guarded",
247+
t ->
248+
t.tryHandler(
249+
tb ->
250+
tb.function(
251+
cb -> cb.function((String s) -> s.trim(), String.class)))
252+
.catchHandler(c -> c.when("$.errorType == 'TEMP'"))
253+
// inherited TaskBaseBuilder methods – must not NPE
254+
.when(".input != null")
255+
.then("nextStep")
256+
.exportAs("$.result")))
257+
.build();
258+
259+
List<TaskItem> items = wf.getDo();
260+
assertEquals(1, items.size());
261+
262+
var tryTask = items.get(0).getTask().getTryTask();
263+
assertNotNull(tryTask, "TryTask expected");
264+
assertEquals(".input != null", tryTask.getIf(), "when(String) should set 'if' on TryTask");
265+
assertEquals(
266+
"nextStep", tryTask.getThen().getString(), "then(String) should set FlowDirective");
267+
assertNotNull(tryTask.getExport(), "exportAs(String) should set Export on TryTask");
268+
assertEquals(
269+
"$.result", tryTask.getExport().getAs().getString(), "exportAs value should be propagated");
270+
}
271+
272+
@Test
273+
@DisplayName(
274+
"tryCatch.exportAs(Function) and when(Predicate) are available via new SPI interfaces")
275+
void tryCatch_func_transformations_and_conditional_builder_available() {
276+
Workflow wf =
277+
FuncWorkflowBuilder.workflow("try-func-interfaces")
278+
.tasks(
279+
tryCatch(
280+
"guarded",
281+
t ->
282+
t.tryHandler(
283+
tb ->
284+
tb.function(
285+
cb -> cb.function((String s) -> s.trim(), String.class)))
286+
.catchHandler(c -> c.when("$.errorType == 'TEMP'"))
287+
// FuncTaskTransformations – function-based exportAs
288+
.exportAs((String s) -> s.toUpperCase())
289+
// ConditionalTaskBuilder – predicate-based when
290+
.when((String s) -> s != null, String.class)))
291+
.build();
292+
293+
List<TaskItem> items = wf.getDo();
294+
assertEquals(1, items.size());
295+
296+
var tryTask = items.get(0).getTask().getTryTask();
297+
assertNotNull(tryTask, "TryTask expected");
298+
299+
// FuncTaskTransformations.exportAs(Function) must set a non-literal Export
300+
assertNotNull(tryTask.getExport(), "exportAs(Function) should set Export on TryTask");
301+
assertNull(
302+
tryTask.getExport().getAs().getString(),
303+
"Export 'as' must not be a literal string when using Function overload");
304+
}
305+
177306
@Test
178307
void switchWhenOrElse_jq_to_taskName() {
179308
Workflow wf =

0 commit comments

Comments
 (0)