feat: adding FilterErr helpers

This commit is contained in:
Samuel Berthe
2026-03-01 20:43:51 +01:00
parent 4bb58fd2c6
commit ff0e293ce3
6 changed files with 187 additions and 0 deletions
+11
View File
@@ -393,6 +393,17 @@ even := lo.Filter([]int{1, 2, 3, 4}, func(x int, index int) bool {
// []int{2, 4}
```
```go
// Use FilterErr when the predicate can return an error
even, err := lo.FilterErr([]int{1, 2, 3, 4}, func(x int, _ int) (bool, error) {
if x == 3 {
return false, fmt.Errorf("number 3 is not allowed")
}
return x%2 == 0, nil
})
// []int(nil), error("number 3 is not allowed")
```
[[play](https://go.dev/play/p/Apjg3WeSi7K)]
Mutable: like `lo.Filter()`, but the slice is updated in place.
+1
View File
@@ -7,6 +7,7 @@ subCategory: slice
playUrl: https://go.dev/play/p/Apjg3WeSi7K
similarHelpers:
- core#slice#reject
- core#slice#filtererr
- core#slice#filtermap
- core#slice#filterreject
- core#slice#rejectmap
+37
View File
@@ -0,0 +1,37 @@
---
name: FilterErr
slug: filtererr
sourceRef: slice.go#L27
category: core
subCategory: slice
signatures:
- "func FilterErr[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) (bool, error)) (Slice, error)"
playUrl:
variantHelpers:
- core#slice#filtererr
similarHelpers:
- core#slice#filter
- core#slice#reject
- core#slice#filtermap
- core#slice#filterreject
position: 5
---
Iterates over a collection and returns a slice of all the elements the predicate function returns `true` for. If the predicate returns an error, iteration stops immediately and returns the error.
```go
even, err := lo.FilterErr([]int{1, 2, 3, 4}, func(x int, index int) (bool, error) {
if x == 3 {
return false, errors.New("number 3 is not allowed")
}
return x%2 == 0, nil
})
// []int(nil), error("number 3 is not allowed")
```
```go
even, err := lo.FilterErr([]int{1, 2, 3, 4}, func(x int, index int) (bool, error) {
return x%2 == 0, nil
})
// []int{2, 4}, nil
```
+20
View File
@@ -2354,6 +2354,26 @@ func ExampleFilter() {
// Output: [2 4]
}
func ExampleFilterErr() {
list := []int64{1, 2, 3, 4}
result, err := FilterErr(list, func(nbr int64, index int) (bool, error) {
if nbr == 3 {
return false, fmt.Errorf("number 3 is not allowed")
}
return nbr%2 == 0, nil
})
fmt.Printf("%v, %v\n", result, err)
result, err = FilterErr([]int64{1, 2, 4, 6}, func(nbr int64, index int) (bool, error) {
return nbr%2 == 0, nil
})
fmt.Printf("%v, %v\n", result, err)
// Output:
// [], number 3 is not allowed
// [2 4 6], <nil>
}
func ExampleMap() {
list := []int64{1, 2, 3, 4}
+19
View File
@@ -21,6 +21,25 @@ func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T, index in
return result
}
// FilterErr iterates over elements of collection, returning a slice of all elements predicate returns true for.
// If the predicate returns an error, iteration stops immediately and returns the error.
// Play: https://go.dev/play/p/Apjg3WeSi7K
func FilterErr[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) (bool, error)) (Slice, error) {
result := make(Slice, 0, len(collection))
for i := range collection {
ok, err := predicate(collection[i], i)
if err != nil {
return nil, err
}
if ok {
result = append(result, collection[i])
}
}
return result, nil
}
// Map manipulates a slice and transforms it to a slice of another type.
// Play: https://go.dev/play/p/OkPcYAhBo0D
func Map[T, R any](collection []T, transform func(item T, index int) R) []R {
+99
View File
@@ -34,6 +34,105 @@ func TestFilter(t *testing.T) {
is.IsType(nonempty, allStrings, "type preserved")
}
func TestFilterErr(t *testing.T) {
t.Parallel()
is := assert.New(t)
tests := []struct {
name string
input []int
predicate func(item int, index int) (bool, error)
want []int
wantErr string
callbacks int // Number of predicates called before error/finish
}{
{
name: "filter even numbers",
input: []int{1, 2, 3, 4},
predicate: func(x int, _ int) (bool, error) {
return x%2 == 0, nil
},
want: []int{2, 4},
callbacks: 4,
},
{
name: "empty slice",
input: []int{},
predicate: func(x int, _ int) (bool, error) {
return true, nil
},
want: []int{},
callbacks: 0,
},
{
name: "filter all out",
input: []int{1, 2, 3, 4},
predicate: func(x int, _ int) (bool, error) {
return false, nil
},
want: []int{},
callbacks: 4,
},
{
name: "filter all in",
input: []int{1, 2, 3, 4},
predicate: func(x int, _ int) (bool, error) {
return true, nil
},
want: []int{1, 2, 3, 4},
callbacks: 4,
},
{
name: "error on specific index",
input: []int{1, 2, 3, 4},
predicate: func(x int, _ int) (bool, error) {
if x == 3 {
return false, fmt.Errorf("number 3 is not allowed")
}
return x%2 == 0, nil
},
callbacks: 3,
wantErr: "number 3 is not allowed",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var callbacks int
wrappedPredicate := func(item int, index int) (bool, error) {
callbacks++
return tt.predicate(item, index)
}
got, err := FilterErr(tt.input, wrappedPredicate)
if tt.wantErr != "" {
is.Error(err)
is.Equal(tt.wantErr, err.Error())
is.Nil(got)
is.Equal(tt.callbacks, callbacks, "callback count should match expected early return")
} else {
is.NoError(err)
is.Equal(tt.want, got)
is.Equal(tt.callbacks, callbacks)
}
})
}
// Test type preservation
type myStrings []string
allStrings := myStrings{"", "foo", "bar"}
nonempty, err := FilterErr(allStrings, func(x string, _ int) (bool, error) {
return len(x) > 0, nil
})
is.NoError(err)
is.IsType(nonempty, allStrings, "type preserved")
is.Equal(myStrings{"foo", "bar"}, nonempty)
}
func TestMap(t *testing.T) {
t.Parallel()
is := assert.New(t)