mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-25 22:21:49 +02:00
325 lines
8.6 KiB
Go
325 lines
8.6 KiB
Go
// 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 readerresult
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
F "github.com/IBM/fp-go/v2/function"
|
|
"github.com/IBM/fp-go/v2/internal/utils"
|
|
"github.com/IBM/fp-go/v2/option"
|
|
"github.com/IBM/fp-go/v2/reader"
|
|
"github.com/IBM/fp-go/v2/result"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
type MyContext string
|
|
|
|
const defaultContext MyContext = "default"
|
|
|
|
var (
|
|
testError = errors.New("test error")
|
|
)
|
|
|
|
func TestFromEither(t *testing.T) {
|
|
rr := FromEither[MyContext](result.Of(42))
|
|
assert.Equal(t, result.Of(42), rr(defaultContext))
|
|
|
|
rrErr := FromEither[MyContext](result.Left[int](testError))
|
|
assert.Equal(t, result.Left[int](testError), rrErr(defaultContext))
|
|
}
|
|
|
|
func TestFromResult(t *testing.T) {
|
|
rr := FromResult[MyContext](result.Of(42))
|
|
assert.Equal(t, result.Of(42), rr(defaultContext))
|
|
}
|
|
|
|
func TestRightReader(t *testing.T) {
|
|
r := func(ctx MyContext) int { return 42 }
|
|
rr := RightReader(r)
|
|
assert.Equal(t, result.Of(42), rr(defaultContext))
|
|
}
|
|
|
|
func TestLeftReader(t *testing.T) {
|
|
r := func(ctx MyContext) error { return testError }
|
|
rr := LeftReader[int](r)
|
|
assert.Equal(t, result.Left[int](testError), rr(defaultContext))
|
|
}
|
|
|
|
func TestLeft(t *testing.T) {
|
|
rr := Left[MyContext, int](testError)
|
|
assert.Equal(t, result.Left[int](testError), rr(defaultContext))
|
|
}
|
|
|
|
func TestRight(t *testing.T) {
|
|
rr := Right[MyContext](42)
|
|
assert.Equal(t, result.Of(42), rr(defaultContext))
|
|
}
|
|
|
|
func TestOf(t *testing.T) {
|
|
rr := Of[MyContext](42)
|
|
assert.Equal(t, result.Of(42), rr(defaultContext))
|
|
}
|
|
|
|
func TestFromReader(t *testing.T) {
|
|
r := func(ctx MyContext) string { return string(ctx) }
|
|
rr := FromReader(r)
|
|
assert.Equal(t, result.Of("default"), rr(defaultContext))
|
|
}
|
|
|
|
func TestMap(t *testing.T) {
|
|
g := F.Pipe1(
|
|
Of[MyContext](1),
|
|
Map[MyContext](utils.Double),
|
|
)
|
|
assert.Equal(t, result.Of(2), g(defaultContext))
|
|
|
|
// Test with error
|
|
gErr := F.Pipe1(
|
|
Left[MyContext, int](testError),
|
|
Map[MyContext](utils.Double),
|
|
)
|
|
assert.Equal(t, result.Left[int](testError), gErr(defaultContext))
|
|
}
|
|
|
|
func TestMonadMap(t *testing.T) {
|
|
rr := Of[MyContext](5)
|
|
doubled := MonadMap(rr, func(x int) int { return x * 2 })
|
|
assert.Equal(t, result.Of(10), doubled(defaultContext))
|
|
}
|
|
|
|
func TestChain(t *testing.T) {
|
|
addOne := func(x int) ReaderResult[MyContext, int] {
|
|
return Of[MyContext](x + 1)
|
|
}
|
|
|
|
g := F.Pipe1(
|
|
Of[MyContext](5),
|
|
Chain(addOne),
|
|
)
|
|
assert.Equal(t, result.Of(6), g(defaultContext))
|
|
|
|
// Test error propagation
|
|
gErr := F.Pipe1(
|
|
Left[MyContext, int](testError),
|
|
Chain(addOne),
|
|
)
|
|
assert.Equal(t, result.Left[int](testError), gErr(defaultContext))
|
|
}
|
|
|
|
func TestMonadChain(t *testing.T) {
|
|
addOne := func(x int) ReaderResult[MyContext, int] {
|
|
return Of[MyContext](x + 1)
|
|
}
|
|
|
|
rr := Of[MyContext](5)
|
|
res := MonadChain(rr, addOne)
|
|
assert.Equal(t, result.Of(6), res(defaultContext))
|
|
}
|
|
|
|
func TestAp(t *testing.T) {
|
|
g := F.Pipe1(
|
|
Of[MyContext](utils.Double),
|
|
Ap[int](Of[MyContext](1)),
|
|
)
|
|
assert.Equal(t, result.Of(2), g(defaultContext))
|
|
}
|
|
|
|
func TestMonadAp(t *testing.T) {
|
|
add := func(x int) func(int) int {
|
|
return func(y int) int { return x + y }
|
|
}
|
|
fabr := Of[MyContext](add(5))
|
|
fa := Of[MyContext](3)
|
|
res := MonadAp(fabr, fa)
|
|
assert.Equal(t, result.Of(8), res(defaultContext))
|
|
}
|
|
|
|
func TestFromPredicate(t *testing.T) {
|
|
isPositive := FromPredicate[MyContext](
|
|
func(x int) bool { return x > 0 },
|
|
func(x int) error { return fmt.Errorf("%d is not positive", x) },
|
|
)
|
|
|
|
assert.Equal(t, result.Of(5), isPositive(5)(defaultContext))
|
|
res := isPositive(-1)(defaultContext)
|
|
assert.True(t, result.IsLeft(res))
|
|
}
|
|
|
|
func TestFold(t *testing.T) {
|
|
handleError := func(err error) reader.Reader[MyContext, string] {
|
|
return func(ctx MyContext) string { return "Error: " + err.Error() }
|
|
}
|
|
handleSuccess := func(x int) reader.Reader[MyContext, string] {
|
|
return func(ctx MyContext) string { return fmt.Sprintf("Success: %d", x) }
|
|
}
|
|
|
|
fold := Fold(handleError, handleSuccess)
|
|
|
|
res1 := fold(Of[MyContext](42))(defaultContext)
|
|
assert.Equal(t, "Success: 42", res1)
|
|
|
|
res2 := fold(Left[MyContext, int](testError))(defaultContext)
|
|
assert.Equal(t, "Error: "+testError.Error(), res2)
|
|
}
|
|
|
|
func TestGetOrElse(t *testing.T) {
|
|
defaultVal := func(err error) reader.Reader[MyContext, int] {
|
|
return func(ctx MyContext) int { return 0 }
|
|
}
|
|
|
|
getOrElse := GetOrElse(defaultVal)
|
|
|
|
res1 := getOrElse(Of[MyContext](42))(defaultContext)
|
|
assert.Equal(t, 42, res1)
|
|
|
|
res2 := getOrElse(Left[MyContext, int](testError))(defaultContext)
|
|
assert.Equal(t, 0, res2)
|
|
}
|
|
|
|
func TestOrElse(t *testing.T) {
|
|
fallback := func(err error) ReaderResult[MyContext, int] {
|
|
return Of[MyContext](99)
|
|
}
|
|
|
|
orElse := OrElse(fallback)
|
|
|
|
res1 := F.Pipe1(Of[MyContext](42), orElse)(defaultContext)
|
|
assert.Equal(t, result.Of(42), res1)
|
|
|
|
res2 := F.Pipe1(Left[MyContext, int](testError), orElse)(defaultContext)
|
|
assert.Equal(t, result.Of(99), res2)
|
|
}
|
|
|
|
func TestOrLeft(t *testing.T) {
|
|
enrichErr := func(err error) reader.Reader[MyContext, error] {
|
|
return func(ctx MyContext) error {
|
|
return fmt.Errorf("enriched: %w", err)
|
|
}
|
|
}
|
|
|
|
orLeft := OrLeft[MyContext, int](enrichErr)
|
|
|
|
res1 := F.Pipe1(Of[MyContext](42), orLeft)(defaultContext)
|
|
assert.Equal(t, result.Of(42), res1)
|
|
|
|
res2 := F.Pipe1(Left[MyContext, int](testError), orLeft)(defaultContext)
|
|
assert.True(t, result.IsLeft(res2))
|
|
}
|
|
|
|
func TestAsk(t *testing.T) {
|
|
rr := Ask[MyContext]()
|
|
assert.Equal(t, result.Of(defaultContext), rr(defaultContext))
|
|
}
|
|
|
|
func TestAsks(t *testing.T) {
|
|
getLen := func(ctx MyContext) int { return len(string(ctx)) }
|
|
rr := Asks(getLen)
|
|
assert.Equal(t, result.Of(7), rr(defaultContext)) // "default" has 7 chars
|
|
}
|
|
|
|
func TestChainEitherK(t *testing.T) {
|
|
parseInt := func(s string) result.Result[int] {
|
|
if s == "42" {
|
|
return result.Of(42)
|
|
}
|
|
return result.Left[int](errors.New("parse error"))
|
|
}
|
|
|
|
chain := ChainEitherK[MyContext](parseInt)
|
|
|
|
res1 := F.Pipe1(Of[MyContext]("42"), chain)(defaultContext)
|
|
assert.Equal(t, result.Of(42), res1)
|
|
|
|
res2 := F.Pipe1(Of[MyContext]("invalid"), chain)(defaultContext)
|
|
assert.True(t, result.IsLeft(res2))
|
|
}
|
|
|
|
func TestChainOptionK(t *testing.T) {
|
|
findEven := func(x int) option.Option[int] {
|
|
if x%2 == 0 {
|
|
return option.Some(x)
|
|
}
|
|
return option.None[int]()
|
|
}
|
|
|
|
notFound := func() error { return errors.New("not even") }
|
|
chain := ChainOptionK[MyContext, int, int](notFound)(findEven)
|
|
|
|
res1 := F.Pipe1(Of[MyContext](4), chain)(defaultContext)
|
|
assert.Equal(t, result.Of(4), res1)
|
|
|
|
res2 := F.Pipe1(Of[MyContext](3), chain)(defaultContext)
|
|
assert.True(t, result.IsLeft(res2))
|
|
}
|
|
|
|
func TestFlatten(t *testing.T) {
|
|
g := F.Pipe1(
|
|
Of[MyContext](Of[MyContext]("a")),
|
|
Flatten[MyContext, string],
|
|
)
|
|
assert.Equal(t, result.Of("a"), g(defaultContext))
|
|
}
|
|
|
|
func TestBiMap(t *testing.T) {
|
|
enrichErr := func(e error) error { return fmt.Errorf("enriched: %w", e) }
|
|
double := func(x int) int { return x * 2 }
|
|
|
|
res1 := F.Pipe1(Of[MyContext](5), BiMap[MyContext](enrichErr, double))(defaultContext)
|
|
assert.Equal(t, result.Of(10), res1)
|
|
|
|
res2 := F.Pipe1(Left[MyContext, int](testError), BiMap[MyContext](enrichErr, double))(defaultContext)
|
|
assert.True(t, result.IsLeft(res2))
|
|
}
|
|
|
|
func TestLocal(t *testing.T) {
|
|
type OtherContext int
|
|
toMyContext := func(oc OtherContext) MyContext {
|
|
return MyContext(fmt.Sprintf("ctx-%d", oc))
|
|
}
|
|
|
|
rr := Asks(func(ctx MyContext) string { return string(ctx) })
|
|
adapted := Local[string](toMyContext)(rr)
|
|
|
|
res := adapted(OtherContext(42))
|
|
assert.Equal(t, result.Of("ctx-42"), res)
|
|
}
|
|
|
|
func TestRead(t *testing.T) {
|
|
rr := Of[MyContext](42)
|
|
read := Read[int](defaultContext)
|
|
res := read(rr)
|
|
assert.Equal(t, result.Of(42), res)
|
|
}
|
|
|
|
func TestFlap(t *testing.T) {
|
|
fabr := Of[MyContext](func(x int) int { return x * 2 })
|
|
flapped := MonadFlap(fabr, 5)
|
|
assert.Equal(t, result.Of(10), flapped(defaultContext))
|
|
}
|
|
|
|
func TestMapLeft(t *testing.T) {
|
|
enrichErr := func(e error) error { return fmt.Errorf("DB error: %w", e) }
|
|
|
|
res1 := F.Pipe1(Of[MyContext](42), MapLeft[MyContext, int](enrichErr))(defaultContext)
|
|
assert.Equal(t, result.Of(42), res1)
|
|
|
|
res2 := F.Pipe1(Left[MyContext, int](testError), MapLeft[MyContext, int](enrichErr))(defaultContext)
|
|
assert.True(t, result.IsLeft(res2))
|
|
}
|