Skip to content

Commit

Permalink
feat: v1
Browse files Browse the repository at this point in the history
  • Loading branch information
levikline committed Sep 16, 2024
1 parent f858cb2 commit e137927
Show file tree
Hide file tree
Showing 5 changed files with 1,050 additions and 0 deletions.
11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/tapp-ai/go-optional-v2

go 1.22.3

require github.com/stretchr/testify v1.9.0

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
236 changes: 236 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package optionalv2

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"
)

var (
// ErrNoneValueTaken represents the error that is raised when None value is taken.
ErrNoneValueTaken = errors.New("none value taken")
// NullBytes is a byte slice representation of the string "null"
NullBytes = []byte("null")
)

// Option is a data type that must be Some (i.e. having a value) or None (i.e. doesn't have a value).
type Option[T any] map[bool]T

// --- Private ---

// Null is a function to make an Option type value that has an explicit null value.
func null[T any]() Option[T] {
var defaultVal T
return Option[T]{
false: defaultVal,
}
}

// IsNull returns whether the Option has an explicit null value or not.
func (o Option[T]) isNull() bool {
if len(o) == 0 {
return false
}
_, ok := o[false]
return ok
}

// --- Public ---

// Some is a function to make an Option type value with the actual value.
func Some[T any](v T) Option[T] {
// Check if the value is the zero value of its type
if reflect.ValueOf(v).IsZero() {
return null[T]()
}

return Option[T]{
true: v,
}
}

// None is a function to make an Option type value that doesn't have a value.
func None[T any]() Option[T] {
return map[bool]T{}
}

// IsSome returns whether the Option has a value or not.
func (o Option[T]) IsSome() bool {
return len(o) != 0
}

// IsNone returns whether the Option doesn't have a value or not.
func (o Option[T]) IsNone() bool {
return len(o) == 0
}

// Unwrap returns the value regardless of Some/None status.
// If the Option value is Some, this method returns the actual value.
// On the other hand, if the Option value is None, this method returns the *default* value according to the type.
func (o Option[T]) Unwrap() T {
if o.IsNone() || o.isNull() {
var defaultValue T
return defaultValue
}

return o[true]
}

// UnwrapAsPtr returns the contained value in receiver Option as a pointer.
// This is similar to `Unwrap()` method but the difference is this method returns a pointer value instead of the actual value.
// If the receiver Option value is None, this method returns nil.
func (o Option[T]) UnwrapAsPtr() *T {
if o.IsNone() {
return nil
}

if o.isNull() {
var defaultValue T
return &defaultValue
}

var v = o[true]
return &v
}

// Take takes the contained value in Option.
// If Option value is Some, this returns the value.
// If Option value is None, this returns an ErrNoneValueTaken as the second return value.
func (o Option[T]) Take() (T, error) {
if o.IsNone() {
var defaultValue T
return defaultValue, ErrNoneValueTaken
}

return o.Unwrap(), nil
}

// TakeOr returns the actual value if the Option has a value (Some).
// Otherwise, it returns the provided fallback value.
func (o Option[T]) TakeOr(fallbackValue T) T {
if o.IsNone() {
return fallbackValue
}

return o.Unwrap()
}

// TakeOrElse returns the actual value if the Option has a value (Some).
// Otherwise, it executes the fallback function and returns the result.
func (o Option[T]) TakeOrElse(fallbackFunc func() T) T {
if o.IsNone() {
return fallbackFunc()
}

return o.Unwrap()
}

// Or returns the current Option if it has a value (Some).
// If the current Option is None, it returns the fallback Option.
func (o Option[T]) Or(fallbackOptionValue Option[T]) Option[T] {
if o.IsNone() {
return fallbackOptionValue
}

return o
}

// Filter returns the current Option if it has a value and the value matches the predicate.
// If the current Option is None or the value doesn't match the predicate, it returns None.
func (o Option[T]) Filter(predicate func(v T) bool) Option[T] {
if o.IsNone() || !predicate(o.Unwrap()) {
return None[T]()
}

return o
}

// IfSome calls the provided function with the value of Option if it is Some.
func (o Option[T]) IfSome(f func(v T)) {
if o.IsNone() {
return
}

f(o.Unwrap())
}

// IfSomeWithError calls the provided function with the value of Option if it is Some.
// This propagates the error from the provided function.
func (o Option[T]) IfSomeWithError(f func(v T) error) error {
if o.IsNone() {
return nil
}

return f(o.Unwrap())
}

// IfNone calls the provided function if the Option is None.
func (o Option[T]) IfNone(f func()) {
if !o.IsNone() {
return
}

f()
}

// IfNoneWithError calls the provided function if the Option is None.
// This propagates the error from the provided function.
func (o Option[T]) IfNoneWithError(f func() error) error {
if !o.IsNone() {
return nil
}

return f()
}

// String returns a string representation of the Option.
// It includes the unwrapped value for Some, and if the value implements fmt.Stringer, it uses its custom string representation.
func (o Option[T]) String() string {
if o.IsNone() {
return "None[]"
}

// Unwrap the value for both Some and Null
v := o.Unwrap()

// Check if the value implements fmt.Stringer for custom string formatting
if stringer, ok := interface{}(v).(fmt.Stringer); ok {
return fmt.Sprintf("Some[%s]", stringer.String())
}

// Default formatting when fmt.Stringer is not implemented
return fmt.Sprintf("Some[%v]", v)
}

// MarshalJSON implements the json.Marshaler interface for Option.
func (o Option[T]) MarshalJSON() ([]byte, error) {
// if field was specified, and `null`, marshal it
if o.isNull() {
return NullBytes, nil
}

// if field was unspecified, and `omitempty` is set on the field's tags, `json.Marshal` will omit this field

// otherwise: we have a value, so marshal it
return json.Marshal(o[true])
}

// UnmarshalJSON implements the json.Unmarshaler interface for Option.
func (o *Option[T]) UnmarshalJSON(data []byte) error {
// if field is unspecified, UnmarshalJSON won't be called

// if field is specified, and `null`
if bytes.Equal(data, NullBytes) {
*o = null[T]()
return nil
}
// otherwise, we have an actual value, so parse it
var v T
if err := json.Unmarshal(data, &v); err != nil {
return err
}
*o = Some(v)
return nil
}
Loading

0 comments on commit e137927

Please sign in to comment.