Skip to content

Commit d66bf84

Browse files
no-issue: fix merging task items; determining names for multipe tass calls (#1263)
* no-issue: fix merging task items; determining names for multipe tasks calls Signed-off-by: Ricardo Zanini <ricardozanini@gmail.com> * Fix FuncListenTaskBuilder to remove unnecessary sublist Signed-off-by: Ricardo Zanini <ricardozanini@gmail.com> * Fix ListenTaskBuilder redundant list Signed-off-by: Ricardo Zanini <ricardozanini@gmail.com> --------- Signed-off-by: Ricardo Zanini <ricardozanini@gmail.com>
1 parent 803b51f commit d66bf84

18 files changed

Lines changed: 476 additions & 47 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ public class FuncDoTaskBuilder extends BaseDoTaskBuilder<FuncDoTaskBuilder, Func
2626
ConditionalTaskBuilder<FuncDoTaskBuilder>,
2727
FuncDoFluent<FuncDoTaskBuilder> {
2828

29-
public FuncDoTaskBuilder() {
30-
super(new FuncTaskItemListBuilder());
29+
public FuncDoTaskBuilder(int listSizeOffset) {
30+
super(new FuncTaskItemListBuilder(listSizeOffset));
3131
}
3232

3333
@Override

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public FuncForTaskBuilder whileC(String expression) {
113113
}
114114

115115
public FuncForTaskBuilder tasks(Consumer<FuncTaskItemListBuilder> consumer) {
116-
final FuncTaskItemListBuilder builder = new FuncTaskItemListBuilder();
116+
final FuncTaskItemListBuilder builder = new FuncTaskItemListBuilder(this.items.size());
117117
consumer.accept(builder);
118118
this.items.addAll(builder.build());
119119
return this;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public <T, V> FuncForkTaskBuilder branch(Function<T, V> function) {
7272

7373
@Override
7474
public FuncForkTaskBuilder branches(Consumer<FuncTaskItemListBuilder> consumer) {
75-
final FuncTaskItemListBuilder builder = new FuncTaskItemListBuilder();
75+
final FuncTaskItemListBuilder builder = new FuncTaskItemListBuilder(this.items.size());
7676
consumer.accept(builder);
7777
this.items.addAll(builder.build());
7878
return this;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public class FuncListenTaskBuilder
3030

3131
private UntilPredicate untilPredicate;
3232

33-
FuncListenTaskBuilder() {
34-
super(new FuncTaskItemListBuilder());
33+
FuncListenTaskBuilder(FuncTaskItemListBuilder factory) {
34+
super(factory);
3535
}
3636

3737
public <T> FuncListenTaskBuilder until(Predicate<T> predicate, Class<T> predClass) {

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public class FuncTaskItemListBuilder extends BaseTaskItemListBuilder<FuncTaskIte
3030

3131
protected static final String TYPE_FUNCTION = "function";
3232

33-
public FuncTaskItemListBuilder() {
34-
super();
33+
public FuncTaskItemListBuilder(final int listOffsetSize) {
34+
super(listOffsetSize);
3535
}
3636

3737
public FuncTaskItemListBuilder(final List<TaskItem> list) {
@@ -44,8 +44,8 @@ protected FuncTaskItemListBuilder self() {
4444
}
4545

4646
@Override
47-
protected FuncTaskItemListBuilder newItemListBuilder() {
48-
return new FuncTaskItemListBuilder();
47+
protected FuncTaskItemListBuilder newItemListBuilder(int listOffsetSize) {
48+
return new FuncTaskItemListBuilder(listOffsetSize);
4949
}
5050

5151
@Override
@@ -87,7 +87,7 @@ public FuncTaskItemListBuilder emit(String name, Consumer<FuncEmitTaskBuilder> i
8787
public FuncTaskItemListBuilder listen(
8888
String name, Consumer<FuncListenTaskBuilder> itemsConfigurer) {
8989
name = this.defaultNameAndRequireConfig(name, itemsConfigurer, TYPE_LISTEN);
90-
final FuncListenTaskBuilder listenTaskJavaBuilder = new FuncListenTaskBuilder();
90+
final FuncListenTaskBuilder listenTaskJavaBuilder = new FuncListenTaskBuilder(this);
9191
itemsConfigurer.accept(listenTaskJavaBuilder);
9292
return this.addTaskItem(
9393
new TaskItem(name, new Task().withListenTask(listenTaskJavaBuilder.build())));

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ public static FuncWorkflowBuilder workflow() {
4141
}
4242

4343
@Override
44-
protected FuncDoTaskBuilder newDo() {
45-
return new FuncDoTaskBuilder();
44+
protected FuncDoTaskBuilder newDo(int listSizeOffset) {
45+
return new FuncDoTaskBuilder(listSizeOffset);
4646
}
4747

4848
@Override
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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 static org.junit.jupiter.api.Assertions.assertEquals;
19+
import static org.junit.jupiter.api.Assertions.assertNotNull;
20+
21+
import io.serverlessworkflow.api.types.TaskItem;
22+
import java.util.List;
23+
import org.junit.jupiter.api.Test;
24+
25+
public class FuncTaskItemDefaultNamingTest {
26+
27+
@Test
28+
void testFuncForEachTaskAutoNaming() {
29+
// Testing the Func domain builder directly to ensure the functional DSL
30+
// behaves identically to the spec DSL regarding default naming.
31+
FuncTaskItemListBuilder funcBuilder = new FuncTaskItemListBuilder(0);
32+
33+
funcBuilder.forEach(
34+
null,
35+
f ->
36+
f.each("item")
37+
.in("$.list")
38+
// Inner tasks inside the Func ForEach loop
39+
.tasks(
40+
tb ->
41+
tb.set(null, "$.a = 1") // Using the Func set(name, expr) shortcut
42+
.http(null, hb -> hb.endpoint("http://func"))));
43+
44+
List<TaskItem> topItems = funcBuilder.build();
45+
assertEquals(1, topItems.size());
46+
assertEquals("for-0", topItems.get(0).getName(), "Top level Func ForEach should be for-0");
47+
48+
// Fetch the inner tasks of the Func 'for' loop
49+
List<TaskItem> nestedItems = topItems.get(0).getTask().getForTask().getDo();
50+
assertNotNull(nestedItems, "Nested Func forEach items must not be null");
51+
assertEquals(2, nestedItems.size());
52+
53+
// Verify inner builder list indexes independently starting at 0
54+
assertEquals("set-0", nestedItems.get(0).getName());
55+
assertEquals("http-1", nestedItems.get(1).getName());
56+
}
57+
58+
@Test
59+
void testFuncForkTaskMultipleBranchesAppends() {
60+
FuncForkTaskBuilder forkBuilder = new FuncForkTaskBuilder();
61+
62+
// 1. Call branches() - list is initially empty, offset should be 0
63+
forkBuilder.branches(b -> b.set(null, "$.a = 1"));
64+
65+
// 2. Call branch() - list has 1 item, so it should use index 1
66+
forkBuilder.branch((Object x) -> x);
67+
68+
// 3. Call branches() again - list has 2 items, offset should be 2
69+
forkBuilder.branches(b -> b.set(null, "$.b = 2"));
70+
71+
// Build and verify
72+
List<TaskItem> branches = forkBuilder.build().getFork().getBranches();
73+
74+
assertEquals(3, branches.size(), "All branches should be appended to the list");
75+
76+
assertEquals("set-0", branches.get(0).getName(), "First branches() call starts at 0");
77+
assertEquals("branch-1", branches.get(1).getName(), "branch() call picks up index 1");
78+
assertEquals("set-2", branches.get(2).getName(), "Second branches() call picks up index 2");
79+
}
80+
81+
@Test
82+
void testFuncForTaskMultipleTasksAppends() {
83+
io.serverlessworkflow.fluent.func.FuncForTaskBuilder forBuilder =
84+
new io.serverlessworkflow.fluent.func.FuncForTaskBuilder();
85+
86+
forBuilder.each("item").in("$.list");
87+
88+
// 1. Fluent builder - list is empty, offset is 0
89+
forBuilder.tasks(tb -> tb.set(null, "$.a = 1"));
90+
91+
// 2. Functional LoopFunction shortcut - list has 1 item, index should be 1
92+
forBuilder.tasks(null, (Object ctx, Object item) -> ctx);
93+
94+
// 3. Fluent builder again - list has 2 items, offset should be 2
95+
forBuilder.tasks(tb -> tb.http(null, hb -> hb.endpoint("http://test")));
96+
97+
// Build and verify
98+
List<TaskItem> forTasks = forBuilder.build().getDo();
99+
100+
assertEquals(3, forTasks.size(), "All tasks should be appended to the loop");
101+
102+
assertEquals("set-0", forTasks.get(0).getName(), "First fluent block starts at 0");
103+
assertEquals("for-task-1", forTasks.get(1).getName(), "Functional task picks up index 1");
104+
assertEquals("http-2", forTasks.get(2).getName(), "Second fluent block picks up index 2");
105+
}
106+
}

fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTaskItemListBuilder.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,32 @@ public abstract class BaseTaskItemListBuilder<SELF extends BaseTaskItemListBuild
4646
protected final String TYPE_OPENAPI = "openapi";
4747

4848
private final List<TaskItem> list;
49+
private final int offset;
4950

50-
public BaseTaskItemListBuilder() {
51+
/**
52+
* Constructs a new list builder with a specified offset for task indexing. *
53+
*
54+
* <p>The offset ensures deterministic and continuous auto-naming when appending tasks to an
55+
* already existing list (e.g., calling {@code .tasks(...)} multiple times on a workflow builder).
56+
* Without this offset, every new builder would restart its internal counter at 0, resulting in
57+
* duplicate generated names (e.g., multiple "set-0" tasks).
58+
*
59+
* @param listSizeOffset the starting index for auto-generated task names (usually the current
60+
* size of the task list, or 0 for nested scopes like loops).
61+
*/
62+
public BaseTaskItemListBuilder(int listSizeOffset) {
5163
this.list = new ArrayList<>();
64+
this.offset = listSizeOffset;
5265
}
5366

5467
public BaseTaskItemListBuilder(final List<TaskItem> list) {
5568
this.list = list;
69+
this.offset = 0;
5670
}
5771

5872
protected abstract SELF self();
5973

60-
protected abstract SELF newItemListBuilder();
74+
protected abstract SELF newItemListBuilder(int listSizeOffset);
6175

6276
protected final List<TaskItem> mutableList() {
6377
return this.list;
@@ -74,9 +88,8 @@ protected final String defaultNameAndRequireConfig(
7488
Objects.requireNonNull(cfg, "Configurer must not be null");
7589

7690
if (name == null || name.isBlank()) {
77-
return taskType + "-" + this.list.size();
91+
return taskType + "-" + (this.list.size() + offset);
7892
}
79-
8093
return name;
8194
}
8295

fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,19 @@ protected BaseWorkflowBuilder(final String name, final String namespace, final S
5252
this.workflow.setDocument(this.document);
5353
}
5454

55-
protected abstract DBuilder newDo();
55+
/**
56+
* Creates a new task list builder initialized with the specified starting offset. *
57+
*
58+
* <p>This method allows the workflow builder to pass its current task count down to the list
59+
* builder. This ensures that when new tasks are appended to the workflow via subsequent {@code
60+
* .tasks(...)} invocations, the auto-generated task names continue sequentially (e.g., "set-2")
61+
* rather than resetting and causing duplicates.
62+
*
63+
* @param listSizeOffset the current number of tasks already present in the workflow's {@code do}
64+
* list.
65+
* @return a new builder instance configured with the correct naming offset.
66+
*/
67+
protected abstract DBuilder newDo(int listSizeOffset);
5668

5769
protected abstract SELF self();
5870

@@ -117,7 +129,9 @@ public final SELF tasks(Consumer<IListBuilder>... tasks) {
117129
private SELF appendDo(Consumer<DBuilder> configurer) {
118130
if (configurer == null) return self();
119131

120-
final DBuilder doBuilder = newDo();
132+
int currentOffset = this.workflow.getDo() != null ? this.workflow.getDo().size() : 0;
133+
134+
final DBuilder doBuilder = newDo(currentOffset);
121135
configurer.accept(doBuilder);
122136

123137
final List<TaskItem> newItems = doBuilder.build().getDo();

fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
public class DoTaskBuilder extends BaseDoTaskBuilder<DoTaskBuilder, TaskItemListBuilder>
2222
implements DoFluent<DoTaskBuilder> {
2323

24-
DoTaskBuilder() {
25-
super(new TaskItemListBuilder());
24+
DoTaskBuilder(int listSizeOffset) {
25+
super(new TaskItemListBuilder(listSizeOffset));
2626
}
2727

2828
@Override

0 commit comments

Comments
 (0)