fn: fix Result[T].FlatMap

For a Result[T], FlatMap should apply f when the result is Ok, and
propagate the error unchanged when it's Err. The original code returns r
on Ok and tries to use r.left when Err, which is wrong. This commit
fixes that.

Secondly, the group of FlatMap/AndThen and OrElse functions and methods
are now properly tested with new unit tests.

fixes #10401
This commit is contained in:
Gijs van Dam committed 2026-01-06 10:51:49 +01:00
1 parent 801de79cf1
commit e7b9515edd
2 files changed
+129 -2

No files matched your search

+2 -2
View File
@@ -149,10 +149,10 @@ func FlattenResult[A any](r Result[Result[A]]) Result[A] {
// success value if it exists.
func (r Result[T]) FlatMap(f func(T) Result[T]) Result[T] {
if r.IsOk() {
return r
return f(r.left)
}
return f(r.left)
return r
}
// AndThen is an alias for FlatMap. This along with OrElse can be used to
+127
View File
@@ -96,3 +96,130 @@ func TestSinkOnOkContinuationCall(t *testing.T) {
require.True(t, called)
require.Nil(t, res)
}
var errFlatMap = errors.New("fail")
var errFlatMapOrig = errors.New("original")
var flatMapTestCases = []struct {
name string
input Result[int]
fnA func(int) Result[int]
fnB func(int) Result[string]
expectedA Result[int]
expectedB Result[string]
}{
{
name: "Ok to Ok",
input: Ok(1),
fnA: func(i int) Result[int] { return Ok(i + 1) },
fnB: func(i int) Result[string] {
return Ok(fmt.Sprintf("%d", i+1))
},
expectedA: Ok(2),
expectedB: Ok("2"),
},
{
name: "Ok to Err",
input: Ok(1),
fnA: func(i int) Result[int] {
return Err[int](errFlatMap)
},
fnB: func(i int) Result[string] {
return Err[string](errFlatMap)
},
expectedA: Err[int](errFlatMap),
expectedB: Err[string](errFlatMap),
},
{
name: "Err to Err (function not called)",
input: Err[int](errFlatMapOrig),
fnA: func(i int) Result[int] { return Ok(i + 1) },
fnB: func(i int) Result[string] {
return Ok("should not happen")
},
expectedA: Err[int](errFlatMapOrig),
expectedB: Err[string](errFlatMapOrig),
},
}
var orElseTestCases = []struct {
name string
input Result[int]
fn func(error) Result[int]
expected Result[int]
}{
{
name: "Ok to Ok (function not called)",
input: Ok(1),
fn: func(err error) Result[int] { return Ok(2) },
expected: Ok(1),
},
{
name: "Err to Ok",
input: Err[int](errFlatMapOrig),
fn: func(err error) Result[int] { return Ok(2) },
expected: Ok(2),
},
{
name: "Err to Err",
input: Err[int](errFlatMapOrig),
fn: func(err error) Result[int] {
return Err[int](errFlatMap)
},
expected: Err[int](errFlatMap),
},
}
func TestFlatMap(t *testing.T) {
for _, tc := range flatMapTestCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
actual := tc.input.FlatMap(tc.fnA)
require.Equal(t, tc.expectedA, actual)
})
}
}
func TestAndThenMethod(t *testing.T) {
// Since AndThen is just an alias for FlatMap, we can reuse the same
// test cases.
for _, tc := range flatMapTestCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
actual := tc.input.AndThen(tc.fnA)
require.Equal(t, tc.expectedA, actual)
})
}
}
func TestOrElseMethod(t *testing.T) {
for _, tc := range orElseTestCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
actual := tc.input.OrElse(tc.fn)
require.Equal(t, tc.expected, actual)
})
}
}
func TestFlatMapResult(t *testing.T) {
for _, tc := range flatMapTestCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
actual := FlatMapResult(tc.input, tc.fnB)
require.Equal(t, tc.expectedB, actual)
})
}
}
func TestAndThenFunc(t *testing.T) {
// Since AndThen is just an alias for FlatMapResult, we can reuse the
// same test cases.
for _, tc := range flatMapTestCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
actual := AndThen(tc.input, tc.fnB)
require.Equal(t, tc.expectedB, actual)
})
}
}