// Copyright (c) 2023 - 2025 IBM Corp. // All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package either import ( "errors" "fmt" "strconv" "testing" F "github.com/IBM/fp-go/v2/function" M "github.com/IBM/fp-go/v2/monoid" O "github.com/IBM/fp-go/v2/option" "github.com/stretchr/testify/assert" ) // Test BiMap func TestBiMap(t *testing.T) { errToStr := error.Error intToStr := strconv.Itoa // Test Right case result := BiMap(errToStr, intToStr)(Right[error](42)) assert.Equal(t, Right[string]("42"), result) // Test Left case result = BiMap(errToStr, intToStr)(Left[int](errors.New("error"))) assert.Equal(t, Left[string]("error"), result) } // Test MonadBiMap func TestMonadBiMap(t *testing.T) { errToStr := error.Error intToStr := strconv.Itoa result := MonadBiMap(Right[error](42), errToStr, intToStr) assert.Equal(t, Right[string]("42"), result) result = MonadBiMap(Left[int](errors.New("error")), errToStr, intToStr) assert.Equal(t, Left[string]("error"), result) } // Test MapLeft func TestMapLeft(t *testing.T) { errToStr := error.Error result := MapLeft[int](errToStr)(Left[int](errors.New("error"))) assert.Equal(t, Left[int]("error"), result) result = MapLeft[int](errToStr)(Right[error](42)) assert.Equal(t, Right[string](42), result) } // Test MonadMapLeft func TestMonadMapLeft(t *testing.T) { errToStr := error.Error result := MonadMapLeft(Left[int](errors.New("error")), errToStr) assert.Equal(t, Left[int]("error"), result) result = MonadMapLeft(Right[error](42), errToStr) assert.Equal(t, Right[string](42), result) } // Test MonadMapTo func TestMonadMapTo(t *testing.T) { result := MonadMapTo(Right[error](42), "success") assert.Equal(t, Right[error]("success"), result) result = MonadMapTo(Left[int](errors.New("error")), "success") assert.Equal(t, Left[string](errors.New("error")), result) } // Test MonadChainFirst func TestMonadChainFirst(t *testing.T) { f := func(x int) Either[error, string] { return Right[error](strconv.Itoa(x)) } result := MonadChainFirst(Right[error](42), f) assert.Equal(t, Right[error](42), result) result = MonadChainFirst(Left[int](errors.New("error")), f) assert.Equal(t, Left[int](errors.New("error")), result) } // Test MonadChainTo func TestMonadChainTo(t *testing.T) { result := MonadChainTo(Right[error](42), Right[error]("hello")) assert.Equal(t, Right[error]("hello"), result) result = MonadChainTo(Left[int](errors.New("error")), Right[error]("hello")) assert.Equal(t, Right[error]("hello"), result) } // Test Flatten func TestFlatten(t *testing.T) { nested := Right[error](Right[error](42)) result := Flatten(nested) assert.Equal(t, Right[error](42), result) nestedLeft := Right[error](Left[int](errors.New("error"))) result = Flatten(nestedLeft) assert.Equal(t, Left[int](errors.New("error")), result) outerLeft := Left[Either[error, int]](errors.New("outer error")) result = Flatten(outerLeft) assert.Equal(t, Left[int](errors.New("outer error")), result) } // Test ToOption func TestToOption(t *testing.T) { result := ToOption(Right[error](42)) assert.Equal(t, O.Some(42), result) result = ToOption(Left[int](errors.New("error"))) assert.Equal(t, O.None[int](), result) } // Test FromError func TestFromError(t *testing.T) { validate := func(x int) error { if x < 0 { return errors.New("negative") } return nil } toEither := FromError(validate) result := toEither(42) assert.Equal(t, Right[error](42), result) result = toEither(-1) assert.True(t, IsLeft(result)) } // Test ToError func TestToError(t *testing.T) { err := ToError(Left[int](errors.New("error"))) assert.Error(t, err) assert.Equal(t, "error", err.Error()) err = ToError(Right[error](42)) assert.NoError(t, err) } // Test OrElse func TestOrElse(t *testing.T) { recover := OrElse(func(e error) Either[error, int] { return Right[error](0) }) result := recover(Left[int](errors.New("error"))) assert.Equal(t, Right[error](0), result) result = recover(Right[error](42)) assert.Equal(t, Right[error](42), result) } // Test ToType func TestToType(t *testing.T) { convert := ToType[int](func(v any) error { return fmt.Errorf("cannot convert %v to int", v) }) result := convert(42) assert.Equal(t, Right[error](42), result) result = convert("string") assert.True(t, IsLeft(result)) } // Test Memoize func TestMemoize(t *testing.T) { val := Right[error](42) result := Memoize(val) assert.Equal(t, val, result) } // Test Swap func TestSwap(t *testing.T) { result := Swap(Right[error](42)) assert.Equal(t, Left[error](42), result) result = Swap(Left[int](errors.New("error"))) assert.Equal(t, Right[int](errors.New("error")), result) } // Test MonadFlap and Flap func TestFlap(t *testing.T) { fab := Right[error](strconv.Itoa) result := MonadFlap(fab, 42) assert.Equal(t, Right[error]("42"), result) result = Flap[error, string](42)(fab) assert.Equal(t, Right[error]("42"), result) fabLeft := Left[func(int) string](errors.New("error")) result = MonadFlap(fabLeft, 42) assert.Equal(t, Left[string](errors.New("error")), result) } // Test Sequence2 and MonadSequence2 func TestSequence2(t *testing.T) { f := func(a int, b int) Either[error, int] { return Right[error](a + b) } result := Sequence2(f)(Right[error](1), Right[error](2)) assert.Equal(t, Right[error](3), result) result = Sequence2(f)(Left[int](errors.New("error")), Right[error](2)) assert.Equal(t, Left[int](errors.New("error")), result) result = MonadSequence2(Right[error](1), Right[error](2), f) assert.Equal(t, Right[error](3), result) } // Test Sequence3 and MonadSequence3 func TestSequence3(t *testing.T) { f := func(a, b, c int) Either[error, int] { return Right[error](a + b + c) } result := Sequence3(f)(Right[error](1), Right[error](2), Right[error](3)) assert.Equal(t, Right[error](6), result) result = Sequence3(f)(Left[int](errors.New("error")), Right[error](2), Right[error](3)) assert.Equal(t, Left[int](errors.New("error")), result) result = MonadSequence3(Right[error](1), Right[error](2), Right[error](3), f) assert.Equal(t, Right[error](6), result) } // Test Let func TestLet(t *testing.T) { type State struct{ value int } result := F.Pipe2( Right[error](State{value: 10}), Let[error]( func(v int) func(State) State { return func(s State) State { return State{value: s.value + v} } }, func(s State) int { return 32 }, ), Map[error](F.Identity[State]), ) assert.Equal(t, Right[error](State{value: 42}), result) } // Test LetTo func TestLetTo(t *testing.T) { type State struct{ name string } result := F.Pipe2( Right[error](State{}), LetTo[error]( func(n string) func(State) State { return func(s State) State { return State{name: n} } }, "Alice", ), Map[error](F.Identity[State]), ) assert.Equal(t, Right[error](State{name: "Alice"}), result) } // Test BindTo func TestBindTo(t *testing.T) { type State struct{ value int } result := F.Pipe2( Right[error](42), BindTo[error](func(v int) State { return State{value: v} }), Map[error](F.Identity[State]), ) assert.Equal(t, Right[error](State{value: 42}), result) } // Test TraverseArray func TestTraverseArray(t *testing.T) { parse := func(s string) Either[error, int] { v, err := strconv.Atoi(s) return TryCatchError(v, err) } result := TraverseArray(parse)([]string{"1", "2", "3"}) assert.Equal(t, Right[error]([]int{1, 2, 3}), result) result = TraverseArray(parse)([]string{"1", "bad", "3"}) assert.True(t, IsLeft(result)) } // Test TraverseArrayWithIndex func TestTraverseArrayWithIndex(t *testing.T) { validate := func(i int, s string) Either[error, string] { if len(s) > 0 { return Right[error](fmt.Sprintf("%d:%s", i, s)) } return Left[string](fmt.Errorf("empty at index %d", i)) } result := TraverseArrayWithIndex(validate)([]string{"a", "b"}) assert.Equal(t, Right[error]([]string{"0:a", "1:b"}), result) result = TraverseArrayWithIndex(validate)([]string{"a", ""}) assert.True(t, IsLeft(result)) } // Test TraverseRecord func TestTraverseRecord(t *testing.T) { parse := func(s string) Either[error, int] { v, err := strconv.Atoi(s) return TryCatchError(v, err) } input := map[string]string{"a": "1", "b": "2"} result := TraverseRecord[string](parse)(input) expected := Right[error](map[string]int{"a": 1, "b": 2}) assert.Equal(t, expected, result) } // Test TraverseRecordWithIndex func TestTraverseRecordWithIndex(t *testing.T) { validate := func(k string, v string) Either[error, string] { if len(v) > 0 { return Right[error](k + ":" + v) } return Left[string](fmt.Errorf("empty value for key %s", k)) } input := map[string]string{"a": "1"} result := TraverseRecordWithIndex(validate)(input) expected := Right[error](map[string]string{"a": "a:1"}) assert.Equal(t, expected, result) } // Test SequenceRecord func TestSequenceRecord(t *testing.T) { eithers := map[string]Either[error, int]{ "a": Right[error](1), "b": Right[error](2), } result := SequenceRecord(eithers) expected := Right[error](map[string]int{"a": 1, "b": 2}) assert.Equal(t, expected, result) eithersWithError := map[string]Either[error, int]{ "a": Right[error](1), "b": Left[int](errors.New("error")), } result = SequenceRecord(eithersWithError) assert.True(t, IsLeft(result)) } // Test Curry functions func TestCurry0(t *testing.T) { getConfig := func() (string, error) { return "config", nil } curried := Curry0(getConfig) result := curried() assert.Equal(t, Right[error]("config"), result) } func TestCurry1(t *testing.T) { parse := func(s string) (int, error) { return strconv.Atoi(s) } curried := Curry1(parse) result := curried("42") assert.Equal(t, Right[error](42), result) result = curried("bad") assert.True(t, IsLeft(result)) } func TestCurry2(t *testing.T) { divide := func(a, b int) (int, error) { if b == 0 { return 0, errors.New("div by zero") } return a / b, nil } curried := Curry2(divide) result := curried(10)(2) assert.Equal(t, Right[error](5), result) result = curried(10)(0) assert.True(t, IsLeft(result)) } func TestCurry3(t *testing.T) { sum3 := func(a, b, c int) (int, error) { return a + b + c, nil } curried := Curry3(sum3) result := curried(1)(2)(3) assert.Equal(t, Right[error](6), result) } func TestCurry4(t *testing.T) { sum4 := func(a, b, c, d int) (int, error) { return a + b + c + d, nil } curried := Curry4(sum4) result := curried(1)(2)(3)(4) assert.Equal(t, Right[error](10), result) } // Test Uncurry functions func TestUncurry0(t *testing.T) { curried := func() Either[error, string] { return Right[error]("value") } uncurried := Uncurry0(curried) result, err := uncurried() assert.NoError(t, err) assert.Equal(t, "value", result) } func TestUncurry1(t *testing.T) { curried := func(x int) Either[error, string] { return Right[error](strconv.Itoa(x)) } uncurried := Uncurry1(curried) result, err := uncurried(42) assert.NoError(t, err) assert.Equal(t, "42", result) } func TestUncurry2(t *testing.T) { curried := func(a int) func(int) Either[error, int] { return func(b int) Either[error, int] { return Right[error](a + b) } } uncurried := Uncurry2(curried) result, err := uncurried(1, 2) assert.NoError(t, err) assert.Equal(t, 3, result) } func TestUncurry3(t *testing.T) { curried := func(a int) func(int) func(int) Either[error, int] { return func(b int) func(int) Either[error, int] { return func(c int) Either[error, int] { return Right[error](a + b + c) } } } uncurried := Uncurry3(curried) result, err := uncurried(1, 2, 3) assert.NoError(t, err) assert.Equal(t, 6, result) } func TestUncurry4(t *testing.T) { curried := func(a int) func(int) func(int) func(int) Either[error, int] { return func(b int) func(int) func(int) Either[error, int] { return func(c int) func(int) Either[error, int] { return func(d int) Either[error, int] { return Right[error](a + b + c + d) } } } } uncurried := Uncurry4(curried) result, err := uncurried(1, 2, 3, 4) assert.NoError(t, err) assert.Equal(t, 10, result) } // Test Variadic functions func TestVariadic0(t *testing.T) { sum := func(nums []int) (int, error) { total := 0 for _, n := range nums { total += n } return total, nil } variadicSum := Variadic0(sum) result := variadicSum(1, 2, 3) assert.Equal(t, Right[error](6), result) } func TestVariadic1(t *testing.T) { multiply := func(factor int, nums []int) ([]int, error) { result := make([]int, len(nums)) for i, n := range nums { result[i] = n * factor } return result, nil } variadicMultiply := Variadic1(multiply) result := variadicMultiply(2, 1, 2, 3) assert.Equal(t, Right[error]([]int{2, 4, 6}), result) } func TestVariadic2(t *testing.T) { combine := func(a, b int, nums []int) (int, error) { total := a + b for _, n := range nums { total += n } return total, nil } variadicCombine := Variadic2(combine) result := variadicCombine(1, 2, 3, 4) assert.Equal(t, Right[error](10), result) } func TestVariadic3(t *testing.T) { combine := func(a, b, c int, nums []int) (int, error) { total := a + b + c for _, n := range nums { total += n } return total, nil } variadicCombine := Variadic3(combine) result := variadicCombine(1, 2, 3, 4, 5) assert.Equal(t, Right[error](15), result) } func TestVariadic4(t *testing.T) { combine := func(a, b, c, d int, nums []int) (int, error) { total := a + b + c + d for _, n := range nums { total += n } return total, nil } variadicCombine := Variadic4(combine) result := variadicCombine(1, 2, 3, 4, 5, 6) assert.Equal(t, Right[error](21), result) } // Test Unvariadic functions func TestUnvariadic0(t *testing.T) { variadic := func(nums ...int) (int, error) { total := 0 for _, n := range nums { total += n } return total, nil } unvariadic := Unvariadic0(variadic) result := unvariadic([]int{1, 2, 3}) assert.Equal(t, Right[error](6), result) } func TestUnvariadic1(t *testing.T) { variadic := func(factor int, nums ...int) ([]int, error) { result := make([]int, len(nums)) for i, n := range nums { result[i] = n * factor } return result, nil } unvariadic := Unvariadic1(variadic) result := unvariadic(2, []int{1, 2, 3}) assert.Equal(t, Right[error]([]int{2, 4, 6}), result) } func TestUnvariadic2(t *testing.T) { variadic := func(a, b int, nums ...int) (int, error) { total := a + b for _, n := range nums { total += n } return total, nil } unvariadic := Unvariadic2(variadic) result := unvariadic(1, 2, []int{3, 4}) assert.Equal(t, Right[error](10), result) } func TestUnvariadic3(t *testing.T) { variadic := func(a, b, c int, nums ...int) (int, error) { total := a + b + c for _, n := range nums { total += n } return total, nil } unvariadic := Unvariadic3(variadic) result := unvariadic(1, 2, 3, []int{4, 5}) assert.Equal(t, Right[error](15), result) } func TestUnvariadic4(t *testing.T) { variadic := func(a, b, c, d int, nums ...int) (int, error) { total := a + b + c + d for _, n := range nums { total += n } return total, nil } unvariadic := Unvariadic4(variadic) result := unvariadic(1, 2, 3, 4, []int{5, 6}) assert.Equal(t, Right[error](21), result) } // Test Monad func TestMonad(t *testing.T) { m := Monad[error, int, string]() // Test Of result := m.Of(42) assert.Equal(t, Right[error](42), result) // Test Map mapFn := m.Map(strconv.Itoa) result2 := mapFn(Right[error](42)) assert.Equal(t, Right[error]("42"), result2) // Test Chain chainFn := m.Chain(func(x int) Either[error, string] { return Right[error](strconv.Itoa(x)) }) result3 := chainFn(Right[error](42)) assert.Equal(t, Right[error]("42"), result3) // Test Ap apFn := m.Ap(Right[error](42)) result4 := apFn(Right[error](strconv.Itoa)) assert.Equal(t, Right[error]("42"), result4) } // Test AltSemigroup func TestAltSemigroup(t *testing.T) { sg := AltSemigroup[error, int]() result := sg.Concat(Left[int](errors.New("error")), Right[error](42)) assert.Equal(t, Right[error](42), result) result = sg.Concat(Right[error](1), Right[error](2)) assert.Equal(t, Right[error](1), result) } // Test AlternativeMonoid func TestAlternativeMonoid(t *testing.T) { intAdd := M.MakeMonoid(func(a, b int) int { return a + b }, 0) m := AlternativeMonoid[error](intAdd) result := m.Concat(Right[error](1), Right[error](2)) assert.Equal(t, Right[error](3), result) empty := m.Empty() assert.Equal(t, Right[error](0), empty) } // Test AltMonoid func TestAltMonoid(t *testing.T) { zero := func() Either[error, int] { return Left[int](errors.New("empty")) } m := AltMonoid(zero) result := m.Concat(Left[int](errors.New("err1")), Right[error](42)) assert.Equal(t, Right[error](42), result) empty := m.Empty() assert.Equal(t, Left[int](errors.New("empty")), empty) } // Test core.go Format func TestFormat(t *testing.T) { e := Right[error](42) formatted := fmt.Sprintf("%s", e) assert.Contains(t, formatted, "Right") assert.Contains(t, formatted, "42") e2 := Left[int](errors.New("error")) formatted2 := fmt.Sprintf("%v", e2) assert.Contains(t, formatted2, "Left") } // Test TryCatch with error func TestTryCatchWithError(t *testing.T) { result := TryCatch(0, errors.New("error"), func(err error) string { return err.Error() }) assert.Equal(t, Left[int]("error"), result) } // Test Unwrap with else branch func TestUnwrapElseBranch(t *testing.T) { val, err := Unwrap(Right[error](42)) assert.Equal(t, 42, val) assert.Equal(t, error(nil), err) } // Test eitherFormat with different rune func TestEitherFormatDifferentRune(t *testing.T) { e := Right[error](42) formatted := fmt.Sprintf("%v", e) assert.Contains(t, formatted, "Right") }