Skip to content

Commit

Permalink
Add optional
Browse files Browse the repository at this point in the history
Wrapper container with easy to understand helper methods
  • Loading branch information
donutloop committed Feb 18, 2024
1 parent 3d7600a commit cde15dc
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 0 deletions.
108 changes: 108 additions & 0 deletions datastructure/optional/optional.go
Original file line number Diff line number Diff line change
@@ -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 // Ensures thread-safety similar to the Java implementation's value-based behavior
}

// 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
}
91 changes: 91 additions & 0 deletions datastructure/optional/optional_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
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.ShouldBeFalse(opt.IsPresent())
}

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 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)
}
14 changes: 14 additions & 0 deletions internal/assert.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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
func (a *Assert) NotEqual(expected, actual any) {
if compare(expected, actual) == compareEqual {
Expand Down

0 comments on commit cde15dc

Please sign in to comment.