diff --git a/datastructure/optional/optional.go b/datastructure/optional/optional.go new file mode 100644 index 00000000..fdc954fd --- /dev/null +++ b/datastructure/optional/optional.go @@ -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 +} diff --git a/datastructure/optional/optional_test.go b/datastructure/optional/optional_test.go new file mode 100644 index 00000000..20b3b2b1 --- /dev/null +++ b/datastructure/optional/optional_test.go @@ -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) +} diff --git a/internal/assert.go b/internal/assert.go index 6fb5dba8..aa506487 100644 --- a/internal/assert.go +++ b/internal/assert.go @@ -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 {