diff --git a/changes/20251209161427.feature b/changes/20251209161427.feature new file mode 100644 index 0000000000..93bcd1bdf6 --- /dev/null +++ b/changes/20251209161427.feature @@ -0,0 +1 @@ +:sparkles: `[collection]` Introduced a new `ForAll` helper function that iterates through the entire collection and returns a single aggregated error containing any individual errors encountered during iteration diff --git a/utils/collection/search.go b/utils/collection/operation.go similarity index 89% rename from utils/collection/search.go rename to utils/collection/operation.go index 7f429188e3..8c165e2444 100644 --- a/utils/collection/search.go +++ b/utils/collection/operation.go @@ -167,6 +167,33 @@ func ForEach[S ~[]E, E any](s S, f func(E)) { }) } +// ForAll iterates over every element in the provided sequence and invokes f +// on each item in order. If f returns an error for one or more elements, +// ForAll continues processing the remaining elements and returns a single +// aggregated error containing all collected errors. If no errors occur, the returned error is nil. +func ForAll[S ~[]E, E any](s S, f func(E) error) error { + return ForAllSequence[E](slices.Values(s), f) +} + +// ForAllSequence iterates over every element in the provided sequence and invokes f +// on each item in order. If f returns an error for one or more elements, +// ForAllSequence continues processing the remaining elements and returns a single +// aggregated error containing all collected errors. If no errors occur, the returned error is nil. +func ForAllSequence[T any](s iter.Seq[T], f func(T) error) error { + var err error + err = commonerrors.Join(err, Each[T](s, func(e T) error { + subErr := f(e) + if commonerrors.Any(subErr, commonerrors.ErrEOF) { + return subErr + } + if subErr != nil { + err = commonerrors.Join(err, commonerrors.Newf(subErr, "error during iteration over value [%v]", e)) + } + return nil + })) + return err +} + // Each iterates over a sequence and executes the passed function against each element. // If passed func returns an error, the iteration stops and the error is returned, unless it is EOF. func Each[T any](s iter.Seq[T], f func(T) error) error { diff --git a/utils/collection/search_test.go b/utils/collection/operation_test.go similarity index 91% rename from utils/collection/search_test.go rename to utils/collection/operation_test.go index 158bd34649..74e8bb5013 100644 --- a/utils/collection/search_test.go +++ b/utils/collection/operation_test.go @@ -276,3 +276,28 @@ func TestForEachSequence(t *testing.T) { })) assert.ElementsMatch(t, visited, []int{9, 22, 35, 48, 61, 74, 87, 100, 113, 126, 139}) } + +func TestForAll(t *testing.T) { + var visited []int + list := Range(9, 1000, field.ToOptionalInt(13)) + errortest.AssertError(t, ForAll(list, func(i int) error { + visited = append(visited, i) + if i > 150 { + return commonerrors.ErrUnsupported + } + return nil + }), commonerrors.ErrUnsupported) + assert.ElementsMatch(t, visited, []int{9, 22, 35, 48, 61, 74, 87, 100, 113, 126, 139, 152, 165, 178, 191, 204, 217, 230, 243, 256, 269, 282, 295, 308, 321, 334, 347, 360, 373, 386, 399, 412, 425, 438, 451, 464, 477, 490, 503, 516, 529, 542, 555, 568, 581, 594, 607, 620, 633, 646, 659, 672, 685, 698, 711, 724, 737, 750, 763, 776, 789, 802, 815, 828, 841, 854, 867, 880, 893, 906, 919, 932, 945, 958, 971, 984, 997}) + visited = []int{} + assert.NoError(t, ForAll(list, func(i int) error { + visited = append(visited, i) + if i > 150 { + return commonerrors.ErrEOF + } + return nil + })) + assert.ElementsMatch(t, visited, []int{9, 22, 35, 48, 61, 74, 87, 100, 113, 126, 139, 152}) + assert.NoError(t, ForAll(list, func(i int) error { + return nil + })) +}