Skip to content

Commit 1c3040d

Browse files
authored
Validate arguments at the public API boundary (#95)
1 parent 7e53a68 commit 1c3040d

9 files changed

Lines changed: 127 additions & 1 deletion

File tree

batch.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
//
2121
// See the package documentation for more information on non-blocking ordered functions and error handling.
2222
func Batch[A any](in <-chan Try[A], size int, timeout time.Duration) <-chan Try[[]A] {
23+
validateMinSize(size, 1)
24+
2325
values, errs := ToChans(in)
2426
batches := core.Batch(values, size, timeout)
2527
return FromChans(batches, errs)

consume.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import (
1515
//
1616
// See the package documentation for more information on blocking unordered functions and error handling.
1717
func ForEach[A any](in <-chan Try[A], n int, f func(A) error) error {
18+
validateN(n)
19+
validateNilFunc(f == nil)
20+
1821
// The n = 1 path is an internal contract, not just an optimization.
1922
// Other sinks (Any, Reduce) build their n = 1 behavior on it and rely on:
2023
// - items processed sequentially, in stream order
@@ -96,6 +99,9 @@ var errFound = errors.New("found")
9699
//
97100
// See the package documentation for more information on blocking unordered functions and error handling.
98101
func Any[A any](in <-chan Try[A], n int, f func(A) (bool, error)) (bool, error) {
102+
validateN(n)
103+
validateNilFunc(f == nil)
104+
99105
err := ForEach(in, n, func(a A) error {
100106
ok, err := f(a)
101107
if err != nil {
@@ -122,6 +128,9 @@ func Any[A any](in <-chan Try[A], n int, f func(A) (bool, error)) (bool, error)
122128
//
123129
// See the package documentation for more information on blocking unordered functions and error handling.
124130
func All[A any](in <-chan Try[A], n int, f func(A) (bool, error)) (bool, error) {
131+
validateN(n)
132+
validateNilFunc(f == nil)
133+
125134
err := ForEach(in, n, func(a A) error {
126135
ok, err := f(a)
127136
if err != nil {

iter.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ func FromSeq[A any](seq iter.Seq[A], err error) <-chan Try[A] {
1919
return out
2020
}
2121

22+
validateNilFunc(seq == nil)
23+
2224
out := make(chan Try[A])
2325
go func() {
2426
for val := range seq {
@@ -31,6 +33,8 @@ func FromSeq[A any](seq iter.Seq[A], err error) <-chan Try[A] {
3133

3234
// FromSeq2 converts an iterator of value-error pairs into a stream.
3335
func FromSeq2[A any](seq iter.Seq2[A, error]) <-chan Try[A] {
36+
validateNilFunc(seq == nil)
37+
3438
out := make(chan Try[A])
3539
go func() {
3640
for val, err := range seq {

iter_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,17 @@ func TestFromSeq(t *testing.T) {
112112

113113
th.ExpectSlice(t, outSlice, expectedSlice)
114114
})
115+
116+
t.Run("error with nil iterator", func(t *testing.T) {
117+
out := FromSeq[int](nil, errors.New("some error"))
118+
119+
outSlice := toItemSlice(out)
120+
121+
var expectedSlice []Item[int]
122+
expectedSlice = appendErr(expectedSlice, errors.New("some error"))
123+
124+
th.ExpectSlice(t, outSlice, expectedSlice)
125+
})
115126
}
116127

117128
func TestFromSeq2(t *testing.T) {

reduce.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ func reduceStage[A any](in Stream[A], n int, f func(A, A) (A, error)) Stream[A]
122122
//
123123
// See the package documentation for more information on blocking unordered functions and error handling.
124124
func Reduce[A any](in <-chan Try[A], n int, f func(A, A) (A, error)) (result A, hasResult bool, err error) {
125+
validateN(n)
126+
validateNilFunc(f == nil)
127+
125128
var zero A
126129

127130
if n == 1 {
@@ -247,6 +250,11 @@ func mapReduceStage[A any, K comparable, V any](in Stream[A], nm int, mapper fun
247250
//
248251
// See the package documentation for more information on blocking unordered functions and error handling.
249252
func MapReduce[A any, K comparable, V any](in <-chan Try[A], nm int, mapper func(A) (K, V, error), nr int, reducer func(V, V) (V, error)) (map[K]V, error) {
253+
validateN(nm)
254+
validateNilFunc(mapper == nil)
255+
validateN(nr)
256+
validateNilFunc(reducer == nil)
257+
250258
if nm == 1 && nr == 1 {
251259
m := make(map[K]V)
252260
err := ForEach(in, 1, func(a A) error {

transform.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import (
1212
//
1313
// See the package documentation for more information on non-blocking unordered functions and error handling.
1414
func Map[A, B any](in <-chan Try[A], n int, f func(A) (B, error)) <-chan Try[B] {
15+
validateN(n)
16+
validateNilFunc(f == nil)
17+
1518
return core.FilterMap(in, n, func(a Try[A]) (Try[B], bool) {
1619
if a.Error != nil {
1720
return Try[B]{Error: a.Error}, true
@@ -28,6 +31,9 @@ func Map[A, B any](in <-chan Try[A], n int, f func(A) (B, error)) <-chan Try[B]
2831

2932
// OrderedMap is the ordered version of [Map].
3033
func OrderedMap[A, B any](in <-chan Try[A], n int, f func(A) (B, error)) <-chan Try[B] {
34+
validateN(n)
35+
validateNilFunc(f == nil)
36+
3137
return core.OrderedFilterMap(in, n, func(a Try[A]) (Try[B], bool) {
3238
if a.Error != nil {
3339
return Try[B]{Error: a.Error}, true
@@ -50,6 +56,9 @@ func OrderedMap[A, B any](in <-chan Try[A], n int, f func(A) (B, error)) <-chan
5056
//
5157
// See the package documentation for more information on non-blocking unordered functions and error handling.
5258
func Filter[A any](in <-chan Try[A], n int, f func(A) (bool, error)) <-chan Try[A] {
59+
validateN(n)
60+
validateNilFunc(f == nil)
61+
5362
return core.FilterMap(in, n, func(a Try[A]) (Try[A], bool) {
5463
if a.Error != nil {
5564
return a, true // never filter out errors
@@ -66,6 +75,9 @@ func Filter[A any](in <-chan Try[A], n int, f func(A) (bool, error)) <-chan Try[
6675

6776
// OrderedFilter is the ordered version of [Filter].
6877
func OrderedFilter[A any](in <-chan Try[A], n int, f func(A) (bool, error)) <-chan Try[A] {
78+
validateN(n)
79+
validateNilFunc(f == nil)
80+
6981
return core.OrderedFilterMap(in, n, func(a Try[A]) (Try[A], bool) {
7082
if a.Error != nil {
7183
return a, true // never filter out errors
@@ -89,6 +101,9 @@ func OrderedFilter[A any](in <-chan Try[A], n int, f func(A) (bool, error)) <-ch
89101
//
90102
// See the package documentation for more information on non-blocking unordered functions and error handling.
91103
func FilterMap[A, B any](in <-chan Try[A], n int, f func(A) (B, bool, error)) <-chan Try[B] {
104+
validateN(n)
105+
validateNilFunc(f == nil)
106+
92107
return core.FilterMap(in, n, func(a Try[A]) (Try[B], bool) {
93108
if a.Error != nil {
94109
return Try[B]{Error: a.Error}, true
@@ -105,6 +120,9 @@ func FilterMap[A, B any](in <-chan Try[A], n int, f func(A) (B, bool, error)) <-
105120

106121
// OrderedFilterMap is the ordered version of [FilterMap].
107122
func OrderedFilterMap[A, B any](in <-chan Try[A], n int, f func(A) (B, bool, error)) <-chan Try[B] {
123+
validateN(n)
124+
validateNilFunc(f == nil)
125+
108126
return core.OrderedFilterMap(in, n, func(a Try[A]) (Try[B], bool) {
109127
if a.Error != nil {
110128
return Try[B]{Error: a.Error}, true
@@ -127,6 +145,9 @@ func OrderedFilterMap[A, B any](in <-chan Try[A], n int, f func(A) (B, bool, err
127145
//
128146
// See the package documentation for more information on non-blocking unordered functions and error handling.
129147
func FlatMap[A, B any](in <-chan Try[A], n int, f func(A) <-chan Try[B]) <-chan Try[B] {
148+
validateN(n)
149+
validateNilFunc(f == nil)
150+
130151
if in == nil {
131152
return nil
132153
}
@@ -150,6 +171,9 @@ func FlatMap[A, B any](in <-chan Try[A], n int, f func(A) <-chan Try[B]) <-chan
150171

151172
// OrderedFlatMap is the ordered version of [FlatMap].
152173
func OrderedFlatMap[A, B any](in <-chan Try[A], n int, f func(A) <-chan Try[B]) <-chan Try[B] {
174+
validateN(n)
175+
validateNilFunc(f == nil)
176+
153177
if in == nil {
154178
return nil
155179
}
@@ -185,6 +209,9 @@ func OrderedFlatMap[A, B any](in <-chan Try[A], n int, f func(A) <-chan Try[B])
185209
//
186210
// See the package documentation for more information on non-blocking unordered functions and error handling.
187211
func Catch[A any](in <-chan Try[A], n int, f func(error) error) <-chan Try[A] {
212+
validateN(n)
213+
validateNilFunc(f == nil)
214+
188215
return core.FilterMap(in, n, func(a Try[A]) (Try[A], bool) {
189216
if a.Error == nil {
190217
return a, true
@@ -201,6 +228,9 @@ func Catch[A any](in <-chan Try[A], n int, f func(error) error) <-chan Try[A] {
201228

202229
// OrderedCatch is the ordered version of [Catch].
203230
func OrderedCatch[A any](in <-chan Try[A], n int, f func(error) error) <-chan Try[A] {
231+
validateN(n)
232+
validateNilFunc(f == nil)
233+
204234
return core.OrderedFilterMap(in, n, func(a Try[A]) (Try[A], bool) {
205235
if a.Error == nil {
206236
return a, true

util.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package rill
22

3-
import "github.com/destel/rill/internal/core"
3+
import (
4+
"fmt"
5+
6+
"github.com/destel/rill/internal/core"
7+
)
48

59
// Drain consumes and discards all items from an input channel, blocking until the channel is closed.
610
func Drain[A any](in <-chan A) {
@@ -31,5 +35,25 @@ func DrainNB[A any](in <-chan A) {
3135
// // Now work with the users channel as usual.
3236
// // Up to 100 users can be buffered if subsequent stages of the pipeline are slow.
3337
func Buffer[A any](in <-chan A, size int) <-chan A {
38+
validateMinSize(size, 1)
39+
3440
return core.Buffer(in, size)
3541
}
42+
43+
func validateN(n int) {
44+
if n < 1 {
45+
panic(fmt.Sprintf("rill: n must be at least 1, got %d", n))
46+
}
47+
}
48+
49+
func validateMinSize(size int, minSize int) {
50+
if size < minSize {
51+
panic(fmt.Sprintf("rill: size must be at least %d, got %d", minSize, size))
52+
}
53+
}
54+
55+
func validateNilFunc(fIsNil bool) {
56+
if fIsNil {
57+
panic("rill: function must not be nil")
58+
}
59+
}

util_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,39 @@ func TestBuffer(t *testing.T) {
4444
Drain(out)
4545
})
4646
}
47+
48+
func TestValidations(t *testing.T) {
49+
t.Run("ok", func(t *testing.T) {
50+
validateN(1)
51+
validateMinSize(5, 5)
52+
validateNilFunc(false)
53+
})
54+
55+
t.Run("n too small", func(t *testing.T) {
56+
defer func() {
57+
if r := recover(); r == nil {
58+
t.Errorf("expected panic")
59+
}
60+
}()
61+
62+
validateN(0)
63+
})
64+
65+
t.Run("size too small", func(t *testing.T) {
66+
defer func() {
67+
if r := recover(); r == nil {
68+
t.Errorf("expected panic")
69+
}
70+
}()
71+
validateMinSize(5, 6)
72+
})
73+
74+
t.Run("function is nil", func(t *testing.T) {
75+
defer func() {
76+
if r := recover(); r == nil {
77+
t.Errorf("expected panic")
78+
}
79+
}()
80+
validateNilFunc(true)
81+
})
82+
}

wrap.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ func ToChans[A any](in <-chan Try[A]) (<-chan A, <-chan error) {
209209
// stream <- rill.Try[int]{Error: someError}
210210
// }()
211211
func Generate[A any](f func(send func(A), sendErr func(error))) <-chan Try[A] {
212+
validateNilFunc(f == nil)
213+
212214
out := make(chan Try[A])
213215
go func() {
214216
defer close(out)

0 commit comments

Comments
 (0)