Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public class FuncDoTaskBuilder extends BaseDoTaskBuilder<FuncDoTaskBuilder, Func
ConditionalTaskBuilder<FuncDoTaskBuilder>,
FuncDoFluent<FuncDoTaskBuilder> {

public FuncDoTaskBuilder() {
super(new FuncTaskItemListBuilder());
public FuncDoTaskBuilder(int listSizeOffset) {
super(new FuncTaskItemListBuilder(listSizeOffset));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public FuncForTaskBuilder whileC(String expression) {
}

public FuncForTaskBuilder tasks(Consumer<FuncTaskItemListBuilder> consumer) {
final FuncTaskItemListBuilder builder = new FuncTaskItemListBuilder();
final FuncTaskItemListBuilder builder = new FuncTaskItemListBuilder(this.items.size());
consumer.accept(builder);
this.items.addAll(builder.build());
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public <T, V> FuncForkTaskBuilder branch(Function<T, V> function) {

@Override
public FuncForkTaskBuilder branches(Consumer<FuncTaskItemListBuilder> consumer) {
final FuncTaskItemListBuilder builder = new FuncTaskItemListBuilder();
final FuncTaskItemListBuilder builder = new FuncTaskItemListBuilder(this.items.size());
consumer.accept(builder);
this.items.addAll(builder.build());
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public class FuncListenTaskBuilder

private UntilPredicate untilPredicate;

FuncListenTaskBuilder() {
super(new FuncTaskItemListBuilder());
FuncListenTaskBuilder(FuncTaskItemListBuilder factory) {
super(factory);
}

public <T> FuncListenTaskBuilder until(Predicate<T> predicate, Class<T> predClass) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public class FuncTaskItemListBuilder extends BaseTaskItemListBuilder<FuncTaskIte

protected static final String TYPE_FUNCTION = "function";

public FuncTaskItemListBuilder() {
super();
public FuncTaskItemListBuilder(final int listOffsetSize) {
super(listOffsetSize);
}

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

@Override
protected FuncTaskItemListBuilder newItemListBuilder() {
return new FuncTaskItemListBuilder();
protected FuncTaskItemListBuilder newItemListBuilder(int listOffsetSize) {
return new FuncTaskItemListBuilder(listOffsetSize);
}

@Override
Expand Down Expand Up @@ -87,7 +87,7 @@ public FuncTaskItemListBuilder emit(String name, Consumer<FuncEmitTaskBuilder> i
public FuncTaskItemListBuilder listen(
String name, Consumer<FuncListenTaskBuilder> itemsConfigurer) {
name = this.defaultNameAndRequireConfig(name, itemsConfigurer, TYPE_LISTEN);
final FuncListenTaskBuilder listenTaskJavaBuilder = new FuncListenTaskBuilder();
final FuncListenTaskBuilder listenTaskJavaBuilder = new FuncListenTaskBuilder(this);
itemsConfigurer.accept(listenTaskJavaBuilder);
return this.addTaskItem(
new TaskItem(name, new Task().withListenTask(listenTaskJavaBuilder.build())));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ public static FuncWorkflowBuilder workflow() {
}

@Override
protected FuncDoTaskBuilder newDo() {
return new FuncDoTaskBuilder();
protected FuncDoTaskBuilder newDo(int listSizeOffset) {
return new FuncDoTaskBuilder(listSizeOffset);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2020-Present The Serverless Workflow Specification Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.serverlessworkflow.fluent.func;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import io.serverlessworkflow.api.types.TaskItem;
import java.util.List;
import org.junit.jupiter.api.Test;

public class FuncTaskItemDefaultNamingTest {

@Test
void testFuncForEachTaskAutoNaming() {
// Testing the Func domain builder directly to ensure the functional DSL
// behaves identically to the spec DSL regarding default naming.
FuncTaskItemListBuilder funcBuilder = new FuncTaskItemListBuilder(0);

funcBuilder.forEach(
null,
f ->
f.each("item")
.in("$.list")
// Inner tasks inside the Func ForEach loop
.tasks(
tb ->
tb.set(null, "$.a = 1") // Using the Func set(name, expr) shortcut
.http(null, hb -> hb.endpoint("http://func"))));

List<TaskItem> topItems = funcBuilder.build();
assertEquals(1, topItems.size());
assertEquals("for-0", topItems.get(0).getName(), "Top level Func ForEach should be for-0");

// Fetch the inner tasks of the Func 'for' loop
List<TaskItem> nestedItems = topItems.get(0).getTask().getForTask().getDo();
assertNotNull(nestedItems, "Nested Func forEach items must not be null");
assertEquals(2, nestedItems.size());

// Verify inner builder list indexes independently starting at 0
assertEquals("set-0", nestedItems.get(0).getName());
assertEquals("http-1", nestedItems.get(1).getName());
}

@Test
void testFuncForkTaskMultipleBranchesAppends() {
FuncForkTaskBuilder forkBuilder = new FuncForkTaskBuilder();

// 1. Call branches() - list is initially empty, offset should be 0
forkBuilder.branches(b -> b.set(null, "$.a = 1"));

// 2. Call branch() - list has 1 item, so it should use index 1
forkBuilder.branch((Object x) -> x);

// 3. Call branches() again - list has 2 items, offset should be 2
forkBuilder.branches(b -> b.set(null, "$.b = 2"));

// Build and verify
List<TaskItem> branches = forkBuilder.build().getFork().getBranches();

assertEquals(3, branches.size(), "All branches should be appended to the list");

assertEquals("set-0", branches.get(0).getName(), "First branches() call starts at 0");
assertEquals("branch-1", branches.get(1).getName(), "branch() call picks up index 1");
assertEquals("set-2", branches.get(2).getName(), "Second branches() call picks up index 2");
}

@Test
void testFuncForTaskMultipleTasksAppends() {
io.serverlessworkflow.fluent.func.FuncForTaskBuilder forBuilder =
new io.serverlessworkflow.fluent.func.FuncForTaskBuilder();

forBuilder.each("item").in("$.list");

// 1. Fluent builder - list is empty, offset is 0
forBuilder.tasks(tb -> tb.set(null, "$.a = 1"));

// 2. Functional LoopFunction shortcut - list has 1 item, index should be 1
forBuilder.tasks(null, (Object ctx, Object item) -> ctx);

// 3. Fluent builder again - list has 2 items, offset should be 2
forBuilder.tasks(tb -> tb.http(null, hb -> hb.endpoint("http://test")));

// Build and verify
List<TaskItem> forTasks = forBuilder.build().getDo();

assertEquals(3, forTasks.size(), "All tasks should be appended to the loop");

assertEquals("set-0", forTasks.get(0).getName(), "First fluent block starts at 0");
assertEquals("for-task-1", forTasks.get(1).getName(), "Functional task picks up index 1");
assertEquals("http-2", forTasks.get(2).getName(), "Second fluent block picks up index 2");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,32 @@ public abstract class BaseTaskItemListBuilder<SELF extends BaseTaskItemListBuild
protected final String TYPE_OPENAPI = "openapi";

private final List<TaskItem> list;
private final int offset;

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

public BaseTaskItemListBuilder(final List<TaskItem> list) {
this.list = list;
this.offset = 0;
}

protected abstract SELF self();

protected abstract SELF newItemListBuilder();
protected abstract SELF newItemListBuilder(int listSizeOffset);

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

if (name == null || name.isBlank()) {
return taskType + "-" + this.list.size();
return taskType + "-" + (this.list.size() + offset);
}

return name;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,19 @@ protected BaseWorkflowBuilder(final String name, final String namespace, final S
this.workflow.setDocument(this.document);
}

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

protected abstract SELF self();

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

final DBuilder doBuilder = newDo();
int currentOffset = this.workflow.getDo() != null ? this.workflow.getDo().size() : 0;

final DBuilder doBuilder = newDo(currentOffset);
configurer.accept(doBuilder);

final List<TaskItem> newItems = doBuilder.build().getDo();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
public class DoTaskBuilder extends BaseDoTaskBuilder<DoTaskBuilder, TaskItemListBuilder>
implements DoFluent<DoTaskBuilder> {

DoTaskBuilder() {
super(new TaskItemListBuilder());
DoTaskBuilder(int listSizeOffset) {
super(new TaskItemListBuilder(listSizeOffset));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

import io.serverlessworkflow.api.types.ForTask;
import io.serverlessworkflow.api.types.ForTaskConfiguration;
import io.serverlessworkflow.api.types.TaskItem;
import io.serverlessworkflow.fluent.spec.spi.ForEachTaskFluent;
import java.util.List;
import java.util.function.Consumer;

public class ForEachTaskBuilder<T extends BaseTaskItemListBuilder<T>>
Expand Down Expand Up @@ -61,9 +63,22 @@ public ForEachTaskBuilder<T> whileC(final String expression) {
}

public ForEachTaskBuilder<T> tasks(Consumer<T> doBuilderConsumer) {
final T taskItemListBuilder = this.taskItemListBuilder.newItemListBuilder();
doBuilderConsumer.accept(taskItemListBuilder);
this.forTask.setDo(taskItemListBuilder.build());
List<TaskItem> existingTasks = this.forTask.getDo();

int currentOffset = (existingTasks == null) ? 0 : existingTasks.size();

final T listBuilder = this.taskItemListBuilder.newItemListBuilder(currentOffset);
doBuilderConsumer.accept(listBuilder);

List<TaskItem> newTasks = listBuilder.build();
if (existingTasks == null || existingTasks.isEmpty()) {
this.forTask.setDo(newTasks);
} else {
List<TaskItem> merged = new java.util.ArrayList<>(existingTasks);
merged.addAll(newTasks);
this.forTask.setDo(merged);
}

return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

import io.serverlessworkflow.api.types.ForkTask;
import io.serverlessworkflow.api.types.ForkTaskConfiguration;
import io.serverlessworkflow.api.types.TaskItem;
import io.serverlessworkflow.fluent.spec.spi.ForkTaskFluent;
import java.util.List;
import java.util.function.Consumer;

public class ForkTaskBuilder extends TaskBaseBuilder<ForkTaskBuilder>
Expand Down Expand Up @@ -45,9 +47,22 @@ public ForkTaskBuilder compete(final boolean compete) {

@Override
public ForkTaskBuilder branches(Consumer<TaskItemListBuilder> branchesConsumer) {
final TaskItemListBuilder doTaskBuilder = new TaskItemListBuilder();
List<TaskItem> existingBranches = this.forkTaskConfiguration.getBranches();

int currentOffset = (existingBranches == null) ? 0 : existingBranches.size();

final TaskItemListBuilder doTaskBuilder = new TaskItemListBuilder(currentOffset);
branchesConsumer.accept(doTaskBuilder);
this.forkTaskConfiguration.setBranches(doTaskBuilder.build());

List<TaskItem> newBranches = doTaskBuilder.build();
if (existingBranches == null || existingBranches.isEmpty()) {
this.forkTaskConfiguration.setBranches(newBranches);
} else {
List<TaskItem> merged = new java.util.ArrayList<>(existingBranches);
merged.addAll(newBranches);
this.forkTaskConfiguration.setBranches(merged);
}

return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
public class ListenTaskBuilder
extends AbstractListenTaskBuilder<TaskItemListBuilder, ListenToBuilder> {

protected ListenTaskBuilder() {
super(new TaskItemListBuilder());
protected ListenTaskBuilder(TaskItemListBuilder factory) {
super(factory);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
package io.serverlessworkflow.fluent.spec;

import io.serverlessworkflow.api.types.SubscriptionIterator;
import io.serverlessworkflow.api.types.TaskItem;
import io.serverlessworkflow.fluent.spec.spi.SubscriptionIteratorFluent;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class SubscriptionIteratorBuilder<T extends BaseTaskItemListBuilder<T>>
Expand Down Expand Up @@ -44,9 +47,22 @@ public SubscriptionIteratorBuilder<T> at(String at) {

@Override
public SubscriptionIteratorBuilder<T> tasks(Consumer<T> doBuilderConsumer) {
final T taskItemListBuilder = this.taskItemListBuilder.newItemListBuilder();
doBuilderConsumer.accept(taskItemListBuilder);
this.subscriptionIterator.setDo(taskItemListBuilder.build());
List<TaskItem> existingTasks = this.subscriptionIterator.getDo();

int currentOffset = (existingTasks == null) ? 0 : existingTasks.size();

final T listBuilder = this.taskItemListBuilder.newItemListBuilder(currentOffset);
doBuilderConsumer.accept(listBuilder);

List<TaskItem> newTasks = listBuilder.build();
if (existingTasks == null || existingTasks.isEmpty()) {
this.subscriptionIterator.setDo(newTasks);
} else {
List<TaskItem> merged = new ArrayList<>(existingTasks);
merged.addAll(newTasks);
this.subscriptionIterator.setDo(merged);
}

return this;
}

Expand Down
Loading
Loading