mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-28 22:52:33 +02:00
Merge pull request #9528 from Roasbeef/res-opt
fn: implement ResultOpt type for operations with optional values
This commit is contained in:
64
fn/result_opt.go
Normal file
64
fn/result_opt.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package fn
|
||||
|
||||
// ResultOpt represents an operation that may either fail (with an error) or
|
||||
// succeed with an optional final value.
|
||||
type ResultOpt[T any] struct {
|
||||
Result[Option[T]]
|
||||
}
|
||||
|
||||
// OkOpt constructs a successful ResultOpt with a present value.
|
||||
func OkOpt[T any](val T) ResultOpt[T] {
|
||||
return ResultOpt[T]{Ok(Some(val))}
|
||||
}
|
||||
|
||||
// NoneOpt constructs a successful ResultOpt with no final value.
|
||||
func NoneOpt[T any]() ResultOpt[T] {
|
||||
return ResultOpt[T]{Ok(None[T]())}
|
||||
}
|
||||
|
||||
// ErrOpt constructs a failed ResultOpt with the provided error.
|
||||
func ErrOpt[T any](err error) ResultOpt[T] {
|
||||
return ResultOpt[T]{Err[Option[T]](err)}
|
||||
}
|
||||
|
||||
// MapResultOpt applies a function to the final value of a successful operation.
|
||||
func MapResultOpt[T, U any](ro ResultOpt[T], f func(T) U) ResultOpt[U] {
|
||||
if ro.IsErr() {
|
||||
return ErrOpt[U](ro.Err())
|
||||
}
|
||||
opt, _ := ro.Unpack()
|
||||
return ResultOpt[U]{Ok(MapOption(f)(opt))}
|
||||
}
|
||||
|
||||
// AndThenResultOpt applies a function to the final value of a successful
|
||||
// operation.
|
||||
func AndThenResultOpt[T, U any](ro ResultOpt[T],
|
||||
f func(T) ResultOpt[U]) ResultOpt[U] {
|
||||
|
||||
if ro.IsErr() {
|
||||
return ErrOpt[U](ro.Err())
|
||||
}
|
||||
opt, _ := ro.Unpack()
|
||||
if opt.IsNone() {
|
||||
return NoneOpt[U]()
|
||||
}
|
||||
return f(opt.some)
|
||||
}
|
||||
|
||||
// IsSome returns true if the operation succeeded and contains a final value.
|
||||
func (ro ResultOpt[T]) IsSome() bool {
|
||||
if ro.IsErr() {
|
||||
return false
|
||||
}
|
||||
opt, _ := ro.Unpack()
|
||||
return opt.IsSome()
|
||||
}
|
||||
|
||||
// IsNone returns true if the operation succeeded but no final value is present.
|
||||
func (ro ResultOpt[T]) IsNone() bool {
|
||||
if ro.IsErr() {
|
||||
return false
|
||||
}
|
||||
opt, _ := ro.Unpack()
|
||||
return opt.IsNone()
|
||||
}
|
136
fn/result_opt_test.go
Normal file
136
fn/result_opt_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package fn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOkOpt(t *testing.T) {
|
||||
value := 42
|
||||
resOpt := OkOpt(value)
|
||||
opt, err := resOpt.Unpack()
|
||||
require.NoError(t, err)
|
||||
require.True(t, opt.IsSome(), "expected Option to be Some")
|
||||
require.Equal(t, value, opt.UnsafeFromSome())
|
||||
require.True(t, resOpt.IsSome())
|
||||
require.False(t, resOpt.IsNone())
|
||||
}
|
||||
|
||||
func TestNoneOpt(t *testing.T) {
|
||||
resOpt := NoneOpt[int]()
|
||||
opt, err := resOpt.Unpack()
|
||||
require.NoError(t, err)
|
||||
require.True(t, opt.IsNone(), "expected Option to be None")
|
||||
require.True(t, resOpt.IsNone())
|
||||
require.False(t, resOpt.IsSome())
|
||||
}
|
||||
|
||||
func TestErrOpt(t *testing.T) {
|
||||
errMsg := "some error"
|
||||
resOpt := ErrOpt[int](errors.New(errMsg))
|
||||
_, err := resOpt.Unpack()
|
||||
require.Error(t, err)
|
||||
require.EqualError(t, err, errMsg)
|
||||
require.False(t, resOpt.IsSome())
|
||||
require.False(t, resOpt.IsNone())
|
||||
}
|
||||
|
||||
func TestMapResultOptOk(t *testing.T) {
|
||||
value := 10
|
||||
resOpt := OkOpt(value)
|
||||
mapped := MapResultOpt(resOpt, func(i int) int {
|
||||
return i * 3
|
||||
})
|
||||
opt, err := mapped.Unpack()
|
||||
require.NoError(t, err)
|
||||
require.True(t, opt.IsSome(), "expected mapped Option to be Some")
|
||||
require.Equal(t, 30, opt.UnsafeFromSome())
|
||||
}
|
||||
|
||||
func TestMapResultOptNone(t *testing.T) {
|
||||
resOpt := NoneOpt[int]()
|
||||
mapped := MapResultOpt(resOpt, func(i int) int {
|
||||
return i * 3
|
||||
})
|
||||
opt, err := mapped.Unpack()
|
||||
require.NoError(t, err)
|
||||
require.True(t, opt.IsNone(), "expected mapped Option to remain None")
|
||||
}
|
||||
|
||||
func TestMapResultOptErr(t *testing.T) {
|
||||
errMsg := "error mapping"
|
||||
resOpt := ErrOpt[int](errors.New(errMsg))
|
||||
mapped := MapResultOpt(resOpt, func(i int) int {
|
||||
return i * 3
|
||||
})
|
||||
_, err := mapped.Unpack()
|
||||
require.Error(t, err)
|
||||
require.EqualError(t, err, errMsg)
|
||||
}
|
||||
|
||||
func incrementOpt(x int) ResultOpt[int] {
|
||||
return OkOpt(x + 1)
|
||||
}
|
||||
|
||||
func TestAndThenResultOptOk(t *testing.T) {
|
||||
resOpt := OkOpt(5)
|
||||
chained := AndThenResultOpt(resOpt, incrementOpt)
|
||||
opt, err := chained.Unpack()
|
||||
require.NoError(t, err)
|
||||
require.True(t, opt.IsSome(), "expected chained Option to be Some")
|
||||
require.Equal(t, 6, opt.UnsafeFromSome())
|
||||
}
|
||||
|
||||
func TestAndThenResultOptNone(t *testing.T) {
|
||||
resOpt := NoneOpt[int]()
|
||||
chained := AndThenResultOpt(resOpt, incrementOpt)
|
||||
opt, err := chained.Unpack()
|
||||
require.NoError(t, err)
|
||||
require.True(t, opt.IsNone(), "expected chained result to remain None")
|
||||
}
|
||||
|
||||
func TestAndThenResultOptErr(t *testing.T) {
|
||||
errMsg := "error in initial result"
|
||||
resOpt := ErrOpt[int](errors.New(errMsg))
|
||||
chained := AndThenResultOpt(resOpt, incrementOpt)
|
||||
_, err := chained.Unpack()
|
||||
require.Error(t, err)
|
||||
require.EqualError(t, err, errMsg)
|
||||
}
|
||||
|
||||
func maybeEvenOpt(x int) ResultOpt[int] {
|
||||
if x%2 == 0 {
|
||||
return OkOpt(x / 2)
|
||||
}
|
||||
return NoneOpt[int]()
|
||||
}
|
||||
|
||||
func TestAndThenResultOptProducesNone(t *testing.T) {
|
||||
// Given an odd number, maybeEvenOpt returns None.
|
||||
resOpt := OkOpt(5)
|
||||
chained := AndThenResultOpt(resOpt, maybeEvenOpt)
|
||||
opt, err := chained.Unpack()
|
||||
require.NoError(t, err)
|
||||
require.True(t, opt.IsNone(), "expected chained result to be None")
|
||||
}
|
||||
|
||||
func TestMapAndThenIntegration(t *testing.T) {
|
||||
resOpt := OkOpt(2)
|
||||
chained := MapResultOpt(
|
||||
AndThenResultOpt(resOpt, func(x int) ResultOpt[int] {
|
||||
return OkOpt(x + 3)
|
||||
}),
|
||||
func(y int) int {
|
||||
return y * 2
|
||||
},
|
||||
)
|
||||
opt, err := chained.Unpack()
|
||||
require.NoError(t, err)
|
||||
require.True(
|
||||
t, opt.IsSome(), "expected integrated mapping and "+
|
||||
"chaining to produce Some",
|
||||
)
|
||||
require.Equal(t, 10, opt.UnsafeFromSome())
|
||||
}
|
Reference in New Issue
Block a user