Skip to content

Commit 4ded8ff

Browse files
author
orestis
committed
Refactor TaskBuilder: rename EffectTaskBuilder to MultiTaskBuilder, add Provides(), RunIfAll/Any
1 parent 036cc99 commit 4ded8ff

File tree

2 files changed

+123
-12
lines changed

2 files changed

+123
-12
lines changed

builder.go

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package taskgraph
22

3+
import "fmt"
4+
35
// TaskBuilder helps construct taskgraph Tasks with a fluent API.
46
type TaskBuilder[T any] struct {
57
name string
@@ -38,6 +40,18 @@ func (b *TaskBuilder[T]) RunIf(cond Condition) *TaskBuilder[T] {
3840
return b
3941
}
4042

43+
// RunIfAll sets a ConditionAnd (logical AND) for the task execution using the provided keys.
44+
func (b *TaskBuilder[T]) RunIfAll(keys ...ReadOnlyKey[bool]) *TaskBuilder[T] {
45+
b.condition = ConditionAnd(keys)
46+
return b
47+
}
48+
49+
// RunIfAny sets a ConditionOr (logical OR) for the task execution using the provided keys.
50+
func (b *TaskBuilder[T]) RunIfAny(keys ...ReadOnlyKey[bool]) *TaskBuilder[T] {
51+
b.condition = ConditionOr(keys)
52+
return b
53+
}
54+
4155
// Default sets the default value for the result key if the condition is false.
4256
func (b *TaskBuilder[T]) Default(val T) *TaskBuilder[T] {
4357
b.defaultVal = val
@@ -80,54 +94,83 @@ func (b *TaskBuilder[T]) Build() TaskSet {
8094
return task
8195
}
8296

83-
// EffectTaskBuilder helps construct taskgraph Tasks that perform side effects (no result key).
84-
type EffectTaskBuilder struct {
97+
// MultiTaskBuilder helps construct taskgraph Tasks that provide multiple outputs or perform side effects.
98+
type MultiTaskBuilder struct {
8599
name string
86100
depends []any
87101
fn any
102+
provides []ID
88103
condition Condition
89104
defaultBindings []Binding
90105
}
91106

92-
// NewEffectTaskBuilder creates a new builder for a side-effect task.
93-
func NewEffectTaskBuilder(name string) *EffectTaskBuilder {
94-
return &EffectTaskBuilder{
107+
// NewMultiTaskBuilder creates a new builder for a multi-output or side-effect task.
108+
func NewMultiTaskBuilder(name string) *MultiTaskBuilder {
109+
return &MultiTaskBuilder{
95110
name: name,
96111
}
97112
}
98113

99114
// DependsOn adds dependencies to the task.
100-
func (b *EffectTaskBuilder) DependsOn(deps ...any) *EffectTaskBuilder {
115+
func (b *MultiTaskBuilder) DependsOn(deps ...any) *MultiTaskBuilder {
101116
b.depends = append(b.depends, deps...)
102117
return b
103118
}
104119

120+
// Provides declares the keys that this task provides.
121+
func (b *MultiTaskBuilder) Provides(keys ...any) *MultiTaskBuilder {
122+
for _, k := range keys {
123+
rk, err := newReflectKey(k)
124+
if err != nil {
125+
panic(fmt.Errorf("invalid key passed to Provides: %w", err))
126+
}
127+
id, err := rk.ID()
128+
if err != nil {
129+
panic(fmt.Errorf("invalid key ID in Provides: %w", err))
130+
}
131+
b.provides = append(b.provides, id)
132+
}
133+
return b
134+
}
135+
105136
// Run sets the function to execute. The function signature must match the dependencies.
106137
// Fn must return []Binding or ([]Binding, error).
107-
func (b *EffectTaskBuilder) Run(fn any) *EffectTaskBuilder {
138+
func (b *MultiTaskBuilder) Run(fn any) *MultiTaskBuilder {
108139
b.fn = fn
109140
return b
110141
}
111142

112143
// RunIf sets a condition for the task execution.
113-
func (b *EffectTaskBuilder) RunIf(cond Condition) *EffectTaskBuilder {
144+
func (b *MultiTaskBuilder) RunIf(cond Condition) *MultiTaskBuilder {
114145
b.condition = cond
115146
return b
116147
}
117148

149+
// RunIfAll sets a ConditionAnd (logical AND) for the task execution using the provided keys.
150+
func (b *MultiTaskBuilder) RunIfAll(keys ...ReadOnlyKey[bool]) *MultiTaskBuilder {
151+
b.condition = ConditionAnd(keys)
152+
return b
153+
}
154+
155+
// RunIfAny sets a ConditionOr (logical OR) for the task execution using the provided keys.
156+
func (b *MultiTaskBuilder) RunIfAny(keys ...ReadOnlyKey[bool]) *MultiTaskBuilder {
157+
b.condition = ConditionOr(keys)
158+
return b
159+
}
160+
118161
// WithDefaultBindings adds arbitrary default bindings if the condition is false.
119-
func (b *EffectTaskBuilder) WithDefaultBindings(bindings ...Binding) *EffectTaskBuilder {
162+
func (b *MultiTaskBuilder) WithDefaultBindings(bindings ...Binding) *MultiTaskBuilder {
120163
b.defaultBindings = append(b.defaultBindings, bindings...)
121164
return b
122165
}
123166

124167
// Build constructs and returns the Task.
125-
func (b *EffectTaskBuilder) Build() TaskSet {
168+
func (b *MultiTaskBuilder) Build() TaskSet {
126169
reflect := ReflectMulti{
127170
Name: b.name,
128171
Depends: b.depends,
129172
Fn: b.fn,
130-
Provides: nil,
173+
Provides: b.provides,
131174
}
132175
reflect.location = getLocation(2)
133176
var task TaskSet = reflect
@@ -143,4 +186,4 @@ func (b *EffectTaskBuilder) Build() TaskSet {
143186
}
144187

145188
return task
146-
}
189+
}

builder_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package taskgraph
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestTaskBuilder_RunIfAll(t *testing.T) {
8+
k1 := NewKey[bool]("k1")
9+
k2 := NewKey[bool]("k2")
10+
res := NewKey[string]("res")
11+
12+
task := NewTaskBuilder[string]("test", res).
13+
Run(func() string { return "ok" }).
14+
RunIfAll(k1, k2).
15+
Default("default").
16+
Build()
17+
18+
// Simulate execution (simplified verification)
19+
tasks := task.Tasks()
20+
if len(tasks) != 1 {
21+
t.Fatalf("expected 1 task, got %d", len(tasks))
22+
}
23+
// We can't easily execute it without a full graph, but we can check if it didn't panic and produced a task.
24+
}
25+
26+
func TestMultiTaskBuilder_Provides(t *testing.T) {
27+
k1 := NewKey[string]("k1")
28+
k2 := NewKey[int]("k2")
29+
30+
task := NewMultiTaskBuilder("multi").
31+
Provides(k1, k2).
32+
Run(func() ([]Binding, error) {
33+
return []Binding{k1.Bind("s"), k2.Bind(1)}, nil
34+
}).
35+
Build()
36+
37+
tasks := task.Tasks()
38+
if len(tasks) != 1 {
39+
t.Fatalf("expected 1 task, got %d", len(tasks))
40+
}
41+
provided := tasks[0].Provides()
42+
if len(provided) != 2 {
43+
t.Fatalf("expected 2 provided keys, got %d", len(provided))
44+
}
45+
}
46+
47+
func TestMultiTaskBuilder_Provides_InvalidKey(t *testing.T) {
48+
defer func() {
49+
if r := recover(); r == nil {
50+
t.Errorf("expected panic on invalid key")
51+
}
52+
}()
53+
NewMultiTaskBuilder("fail").Provides("not a key")
54+
}
55+
56+
func TestMultiTaskBuilder_RunIfAny(t *testing.T) {
57+
k1 := NewKey[bool]("k1")
58+
k2 := NewKey[bool]("k2")
59+
60+
task := NewMultiTaskBuilder("multi_cond").
61+
RunIfAny(k1, k2).
62+
Run(func() []Binding { return nil }).
63+
Build()
64+
65+
if task == nil {
66+
t.Fatal("expected task to be built")
67+
}
68+
}

0 commit comments

Comments
 (0)