mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-04 12:52:28 +08:00
Add optional (#170)
* Add optional Wrapper container with easy to understand helper methods * Add test and rewrite test * Add panic test * Add TestOrElseGetHappyPath
This commit is contained in:
108
datastructure/optional/optional.go
Normal file
108
datastructure/optional/optional.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package optional
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Optional is a type that may or may not contain a non-nil value.
|
||||||
|
type Optional[T any] struct {
|
||||||
|
value *T
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty returns an empty Optional instance.
|
||||||
|
func Empty[T any]() Optional[T] {
|
||||||
|
return Optional[T]{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Of returns an Optional with a non-nil value.
|
||||||
|
func Of[T any](value T) Optional[T] {
|
||||||
|
return Optional[T]{value: &value}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OfNullable returns an Optional for a given value, which may be nil.
|
||||||
|
func OfNullable[T any](value *T) Optional[T] {
|
||||||
|
if value == nil {
|
||||||
|
return Empty[T]()
|
||||||
|
}
|
||||||
|
return Optional[T]{value: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPresent checks if there is a value present.
|
||||||
|
func (o Optional[T]) IsPresent() bool {
|
||||||
|
o.mu.RLock()
|
||||||
|
defer o.mu.RUnlock()
|
||||||
|
|
||||||
|
return o.value != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty checks if the Optional is empty.
|
||||||
|
func (o Optional[T]) IsEmpty() bool {
|
||||||
|
return !o.IsPresent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfPresent performs the given action with the value if a value is present.
|
||||||
|
func (o Optional[T]) IfPresent(action func(value T)) {
|
||||||
|
o.mu.RLock()
|
||||||
|
defer o.mu.RUnlock()
|
||||||
|
|
||||||
|
if o.value != nil {
|
||||||
|
action(*o.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfPresentOrElse performs the action with the value if present, otherwise performs the empty-based action.
|
||||||
|
func (o Optional[T]) IfPresentOrElse(action func(value T), emptyAction func()) {
|
||||||
|
o.mu.RLock()
|
||||||
|
defer o.mu.RUnlock()
|
||||||
|
|
||||||
|
if o.value != nil {
|
||||||
|
action(*o.value)
|
||||||
|
} else {
|
||||||
|
emptyAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the value if present, otherwise panics.
|
||||||
|
func (o Optional[T]) Get() T {
|
||||||
|
o.mu.RLock()
|
||||||
|
defer o.mu.RUnlock()
|
||||||
|
|
||||||
|
if o.value == nil {
|
||||||
|
panic("Optional.Get: no value present")
|
||||||
|
}
|
||||||
|
return *o.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrElse returns the value if present, otherwise returns other.
|
||||||
|
func (o Optional[T]) OrElse(other T) T {
|
||||||
|
o.mu.RLock()
|
||||||
|
defer o.mu.RUnlock()
|
||||||
|
|
||||||
|
if o.value != nil {
|
||||||
|
return *o.value
|
||||||
|
}
|
||||||
|
return other
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrElseGet returns the value if present, otherwise invokes supplier and returns the result.
|
||||||
|
func (o Optional[T]) OrElseGet(supplier func() T) T {
|
||||||
|
o.mu.RLock()
|
||||||
|
defer o.mu.RUnlock()
|
||||||
|
|
||||||
|
if o.value != nil {
|
||||||
|
return *o.value
|
||||||
|
}
|
||||||
|
return supplier()
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrElseThrow returns the value if present, otherwise returns an error.
|
||||||
|
func (o Optional[T]) OrElseThrow(errorSupplier func() error) (T, error) {
|
||||||
|
o.mu.RLock()
|
||||||
|
defer o.mu.RUnlock()
|
||||||
|
|
||||||
|
if o.value == nil {
|
||||||
|
return *new(T), errorSupplier()
|
||||||
|
}
|
||||||
|
return *o.value, nil
|
||||||
|
}
|
||||||
151
datastructure/optional/optional_test.go
Normal file
151
datastructure/optional/optional_test.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package optional
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/duke-git/lancet/v2/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEmpty(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestEmpty")
|
||||||
|
opt := Empty[int]()
|
||||||
|
|
||||||
|
assert.ShouldBeTrue(opt.IsEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOf(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestOf")
|
||||||
|
value := 42
|
||||||
|
opt := Of(value)
|
||||||
|
|
||||||
|
assert.ShouldBeTrue(opt.IsPresent())
|
||||||
|
assert.Equal(opt.Get(), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOfNullable(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestOfNullable")
|
||||||
|
var value *int = nil
|
||||||
|
opt := OfNullable(value)
|
||||||
|
|
||||||
|
assert.ShouldBeFalse(opt.IsPresent())
|
||||||
|
|
||||||
|
value = new(int)
|
||||||
|
*value = 42
|
||||||
|
opt = OfNullable(value)
|
||||||
|
|
||||||
|
assert.ShouldBeTrue(opt.IsPresent())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrElse(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestOrElse")
|
||||||
|
optEmpty := Empty[int]()
|
||||||
|
defaultValue := 100
|
||||||
|
|
||||||
|
val := optEmpty.OrElse(defaultValue)
|
||||||
|
assert.Equal(val, defaultValue)
|
||||||
|
|
||||||
|
optWithValue := Of(42)
|
||||||
|
val = optWithValue.OrElse(defaultValue)
|
||||||
|
assert.Equal(val, 42)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrElseGetHappyPath(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestOrElseGetHappyPath")
|
||||||
|
optWithValue := Of(42)
|
||||||
|
supplier := func() int { return 100 }
|
||||||
|
|
||||||
|
val := optWithValue.OrElseGet(supplier)
|
||||||
|
assert.Equal(val, 42)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrElseGet(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestOrElseGet")
|
||||||
|
optEmpty := Empty[int]()
|
||||||
|
supplier := func() int { return 100 }
|
||||||
|
|
||||||
|
val := optEmpty.OrElseGet(supplier)
|
||||||
|
assert.Equal(val, supplier())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrElseThrow(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestOrElseThrow")
|
||||||
|
optEmpty := Empty[int]()
|
||||||
|
_, err := optEmpty.OrElseThrow(func() error { return errors.New("no value") })
|
||||||
|
|
||||||
|
assert.Equal(err.Error(), "no value")
|
||||||
|
|
||||||
|
optWithValue := Of(42)
|
||||||
|
val, err := optWithValue.OrElseThrow(func() error { return errors.New("no value") })
|
||||||
|
|
||||||
|
assert.IsNil(err)
|
||||||
|
assert.Equal(val, 42)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIfPresent(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestIfPresent")
|
||||||
|
called := false
|
||||||
|
action := func(value int) { called = true }
|
||||||
|
|
||||||
|
optEmpty := Empty[int]()
|
||||||
|
optEmpty.IfPresent(action)
|
||||||
|
|
||||||
|
assert.ShouldBeFalse(called)
|
||||||
|
|
||||||
|
called = false // Reset for next test
|
||||||
|
optWithValue := Of(42)
|
||||||
|
optWithValue.IfPresent(action)
|
||||||
|
|
||||||
|
assert.ShouldBeTrue(called)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIfPresentOrElse(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestIfPresentOrElse")
|
||||||
|
|
||||||
|
// Test when value is present
|
||||||
|
calledWithValue := false
|
||||||
|
valueAction := func(value int) { calledWithValue = true }
|
||||||
|
emptyAction := func() { t.Errorf("Empty action should not be called when value is present") }
|
||||||
|
|
||||||
|
optWithValue := Of(42)
|
||||||
|
optWithValue.IfPresentOrElse(valueAction, emptyAction)
|
||||||
|
|
||||||
|
assert.ShouldBeTrue(calledWithValue)
|
||||||
|
|
||||||
|
// Test when value is not present
|
||||||
|
calledWithEmpty := false
|
||||||
|
valueAction = func(value int) { t.Errorf("Value action should not be called when value is not present") }
|
||||||
|
emptyAction = func() { calledWithEmpty = true }
|
||||||
|
|
||||||
|
optEmpty := Empty[int]()
|
||||||
|
optEmpty.IfPresentOrElse(valueAction, emptyAction)
|
||||||
|
|
||||||
|
assert.ShouldBeTrue(calledWithEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetWithPanicStandard(t *testing.T) {
|
||||||
|
assert := internal.NewAssert(t, "TestGetWithPanicStandard")
|
||||||
|
|
||||||
|
// Test when value is present
|
||||||
|
optWithValue := Of(42)
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
r := recover()
|
||||||
|
assert.IsNil(r)
|
||||||
|
}()
|
||||||
|
val := optWithValue.Get()
|
||||||
|
if val != 42 {
|
||||||
|
t.Errorf("Expected Get to return 42, got %v", val)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Test when value is not present
|
||||||
|
optEmpty := Empty[int]()
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
r := recover()
|
||||||
|
assert.IsNotNil(r)
|
||||||
|
}()
|
||||||
|
_ = optEmpty.Get()
|
||||||
|
}()
|
||||||
|
}
|
||||||
@@ -37,6 +37,20 @@ func (a *Assert) Equal(expected, actual any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShouldBeFalse check if expected is false
|
||||||
|
func (a *Assert) ShouldBeFalse(actual any) {
|
||||||
|
if compare(false, actual) != compareEqual {
|
||||||
|
makeTestFailed(a.T, a.CaseName, false, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldBeTrue check if expected is true
|
||||||
|
func (a *Assert) ShouldBeTrue(actual any) {
|
||||||
|
if compare(true, actual) != compareEqual {
|
||||||
|
makeTestFailed(a.T, a.CaseName, true, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NotEqual check if expected is not equal with actual
|
// NotEqual check if expected is not equal with actual
|
||||||
func (a *Assert) NotEqual(expected, actual any) {
|
func (a *Assert) NotEqual(expected, actual any) {
|
||||||
if compare(expected, actual) == compareEqual {
|
if compare(expected, actual) == compareEqual {
|
||||||
|
|||||||
Reference in New Issue
Block a user