github.com/go-playground/pkg/v5@v5.29.1/values/option/option_test.go (about)

     1  //go:build go1.18
     2  // +build go1.18
     3  
     4  package optionext
     5  
     6  import (
     7  	"database/sql/driver"
     8  	"encoding/json"
     9  	"math"
    10  	"reflect"
    11  	"testing"
    12  	"time"
    13  
    14  	. "github.com/go-playground/assert/v2"
    15  )
    16  
    17  type valueTest struct {
    18  }
    19  
    20  func (valueTest) Value() (driver.Value, error) {
    21  	return "value", nil
    22  }
    23  
    24  type customStringType string
    25  
    26  type testStructType struct {
    27  	Name string
    28  }
    29  
    30  func TestAndXXX(t *testing.T) {
    31  	s := Some(1)
    32  	Equal(t, Some(3), s.And(func(i int) int { return 3 }))
    33  	Equal(t, Some(3), s.AndThen(func(i int) Option[int] { return Some(3) }))
    34  	Equal(t, None[int](), s.AndThen(func(i int) Option[int] { return None[int]() }))
    35  
    36  	n := None[int]()
    37  	Equal(t, None[int](), n.And(func(i int) int { return 3 }))
    38  	Equal(t, None[int](), n.AndThen(func(i int) Option[int] { return Some(3) }))
    39  	Equal(t, None[int](), n.AndThen(func(i int) Option[int] { return None[int]() }))
    40  	Equal(t, None[int](), s.AndThen(func(i int) Option[int] { return None[int]() }))
    41  }
    42  
    43  func TestUnwraps(t *testing.T) {
    44  	none := None[int]()
    45  	PanicMatches(t, func() { none.Unwrap() }, "Option.Unwrap: option is None")
    46  
    47  	v := none.UnwrapOr(3)
    48  	Equal(t, 3, v)
    49  
    50  	v = none.UnwrapOrElse(func() int { return 2 })
    51  	Equal(t, 2, v)
    52  
    53  	v = none.UnwrapOrDefault()
    54  	Equal(t, 0, v)
    55  
    56  	// now test with a pointer type.
    57  	type myStruct struct {
    58  		S string
    59  	}
    60  
    61  	sNone := None[*myStruct]()
    62  	PanicMatches(t, func() { sNone.Unwrap() }, "Option.Unwrap: option is None")
    63  
    64  	v2 := sNone.UnwrapOr(&myStruct{S: "blah"})
    65  	Equal(t, &myStruct{S: "blah"}, v2)
    66  
    67  	v2 = sNone.UnwrapOrElse(func() *myStruct { return &myStruct{S: "blah 2"} })
    68  	Equal(t, &myStruct{S: "blah 2"}, v2)
    69  
    70  	v2 = sNone.UnwrapOrDefault()
    71  	Equal(t, nil, v2)
    72  }
    73  
    74  func TestSQLDriverValue(t *testing.T) {
    75  
    76  	var v valueTest
    77  	Equal(t, reflect.TypeOf(v).Implements(valuerType), true)
    78  
    79  	// none
    80  	nOpt := None[string]()
    81  	nVal, err := nOpt.Value()
    82  	Equal(t, err, nil)
    83  	Equal(t, nVal, nil)
    84  
    85  	// string + convert custom string type
    86  	sOpt := Some("myString")
    87  	sVal, err := sOpt.Value()
    88  	Equal(t, err, nil)
    89  
    90  	_, ok := sVal.(string)
    91  	Equal(t, ok, true)
    92  	Equal(t, sVal, "myString")
    93  
    94  	sCustOpt := Some(customStringType("string"))
    95  	sCustVal, err := sCustOpt.Value()
    96  	Equal(t, err, nil)
    97  	Equal(t, sCustVal, "string")
    98  
    99  	_, ok = sCustVal.(string)
   100  	Equal(t, ok, true)
   101  
   102  	// bool
   103  	bOpt := Some(true)
   104  	bVal, err := bOpt.Value()
   105  	Equal(t, err, nil)
   106  
   107  	_, ok = bVal.(bool)
   108  	Equal(t, ok, true)
   109  	Equal(t, bVal, true)
   110  
   111  	// int64
   112  	iOpt := Some(int64(2))
   113  	iVal, err := iOpt.Value()
   114  	Equal(t, err, nil)
   115  
   116  	_, ok = iVal.(int64)
   117  	Equal(t, ok, true)
   118  	Equal(t, iVal, int64(2))
   119  
   120  	// float64
   121  	fOpt := Some(1.1)
   122  	fVal, err := fOpt.Value()
   123  	Equal(t, err, nil)
   124  
   125  	_, ok = fVal.(float64)
   126  	Equal(t, ok, true)
   127  	Equal(t, fVal, 1.1)
   128  
   129  	// time.Time
   130  	dt := time.Now().UTC()
   131  	dtOpt := Some(dt)
   132  	dtVal, err := dtOpt.Value()
   133  	Equal(t, err, nil)
   134  
   135  	_, ok = dtVal.(time.Time)
   136  	Equal(t, ok, true)
   137  	Equal(t, dtVal, dt)
   138  
   139  	// Slice []byte
   140  	b := []byte("myBytes")
   141  	bytesOpt := Some(b)
   142  	bytesVal, err := bytesOpt.Value()
   143  	Equal(t, err, nil)
   144  
   145  	_, ok = bytesVal.([]byte)
   146  	Equal(t, ok, true)
   147  	Equal(t, bytesVal, b)
   148  
   149  	// Slice []uint8
   150  	b2 := []uint8("myBytes")
   151  	bytes2Opt := Some(b2)
   152  	bytes2Val, err := bytes2Opt.Value()
   153  	Equal(t, err, nil)
   154  
   155  	_, ok = bytes2Val.([]byte)
   156  	Equal(t, ok, true)
   157  	Equal(t, bytes2Val, b2)
   158  
   159  	// Array []byte
   160  	a := []byte{'1', '2', '3'}
   161  	arrayOpt := Some(a)
   162  	arrayVal, err := arrayOpt.Value()
   163  	Equal(t, err, nil)
   164  
   165  	_, ok = arrayVal.([]byte)
   166  	Equal(t, ok, true)
   167  	Equal(t, arrayVal, a)
   168  
   169  	// Slice []byte
   170  	data := []testStructType{{Name: "test"}}
   171  	b, err = json.Marshal(data)
   172  	Equal(t, err, nil)
   173  
   174  	dataOpt := Some(data)
   175  	dataVal, err := dataOpt.Value()
   176  	Equal(t, err, nil)
   177  
   178  	_, ok = dataVal.([]byte)
   179  	Equal(t, ok, true)
   180  	Equal(t, dataVal, b)
   181  
   182  	// Map
   183  	data2 := map[string]int{"test": 1}
   184  	b, err = json.Marshal(data2)
   185  	Equal(t, err, nil)
   186  
   187  	data2Opt := Some(data2)
   188  	data2Val, err := data2Opt.Value()
   189  	Equal(t, err, nil)
   190  
   191  	_, ok = data2Val.([]byte)
   192  	Equal(t, ok, true)
   193  	Equal(t, data2Val, b)
   194  
   195  	// Struct
   196  	data3 := testStructType{Name: "test"}
   197  	b, err = json.Marshal(data3)
   198  	Equal(t, err, nil)
   199  
   200  	data3Opt := Some(data3)
   201  	data3Val, err := data3Opt.Value()
   202  	Equal(t, err, nil)
   203  
   204  	_, ok = data3Val.([]byte)
   205  	Equal(t, ok, true)
   206  	Equal(t, data3Val, b)
   207  }
   208  
   209  type customScanner struct {
   210  	S string
   211  }
   212  
   213  func (c *customScanner) Scan(src interface{}) error {
   214  	if src == nil {
   215  		return nil
   216  	}
   217  	c.S = src.(string)
   218  	return nil
   219  }
   220  
   221  func TestSQLScanner(t *testing.T) {
   222  	value := int64(123)
   223  	var optionI64 Option[int64]
   224  	var optionI32 Option[int32]
   225  	var optionI16 Option[int16]
   226  	var optionI8 Option[int8]
   227  	var optionI Option[int]
   228  	var optionString Option[string]
   229  	var optionBool Option[bool]
   230  	var optionF32 Option[float32]
   231  	var optionF64 Option[float64]
   232  	var optionByte Option[byte]
   233  	var optionTime Option[time.Time]
   234  	var optionInterface Option[any]
   235  	var optionArrBytes Option[[]byte]
   236  	var optionRawMessage Option[json.RawMessage]
   237  	var optionUint64 Option[uint64]
   238  	var optionUint32 Option[uint32]
   239  	var optionUint16 Option[uint16]
   240  	var optionUint8 Option[uint8]
   241  	var optionUint Option[uint]
   242  
   243  	err := optionInterface.Scan(1)
   244  	Equal(t, err, nil)
   245  	Equal(t, optionInterface, Some(any(1)))
   246  
   247  	err = optionInterface.Scan("blah")
   248  	Equal(t, err, nil)
   249  	Equal(t, optionInterface, Some(any("blah")))
   250  
   251  	err = optionUint64.Scan(uint64(200))
   252  	Equal(t, err, nil)
   253  	Equal(t, optionUint64, Some(uint64(200)))
   254  
   255  	err = optionUint32.Scan(uint32(200))
   256  	Equal(t, err, nil)
   257  	Equal(t, optionUint32, Some(uint32(200)))
   258  
   259  	err = optionUint16.Scan(uint16(200))
   260  	Equal(t, err, nil)
   261  	Equal(t, optionUint16, Some(uint16(200)))
   262  
   263  	err = optionUint8.Scan(uint8(200))
   264  	Equal(t, err, nil)
   265  	Equal(t, optionUint8, Some(uint8(200)))
   266  
   267  	err = optionUint.Scan(uint(200))
   268  	Equal(t, err, nil)
   269  	Equal(t, optionUint, Some(uint(200)))
   270  
   271  	err = optionUint64.Scan("200")
   272  	Equal(t, err.Error(), "value string not convertable to uint64")
   273  
   274  	err = optionI64.Scan(value)
   275  	Equal(t, err, nil)
   276  	Equal(t, optionI64, Some(value))
   277  
   278  	err = optionI32.Scan(value)
   279  	Equal(t, err, nil)
   280  	Equal(t, optionI32, Some(int32(value)))
   281  
   282  	err = optionI16.Scan(value)
   283  	Equal(t, err, nil)
   284  	Equal(t, optionI16, Some(int16(value)))
   285  
   286  	err = optionI8.Scan(math.MaxInt32)
   287  	Equal(t, err.Error(), "value 2147483647 out of range for int8")
   288  	Equal(t, optionI8, None[int8]())
   289  
   290  	err = optionI8.Scan(int8(3))
   291  	Equal(t, err, nil)
   292  	Equal(t, optionI8, Some(int8(3)))
   293  
   294  	err = optionI.Scan(3)
   295  	Equal(t, err, nil)
   296  	Equal(t, optionI, Some(3))
   297  
   298  	err = optionBool.Scan(1)
   299  	Equal(t, err, nil)
   300  	Equal(t, optionBool, Some(true))
   301  
   302  	err = optionString.Scan(value)
   303  	Equal(t, err, nil)
   304  	Equal(t, optionString, Some("123"))
   305  
   306  	err = optionF32.Scan(float32(2.0))
   307  	Equal(t, err, nil)
   308  	Equal(t, optionF32, Some(float32(2.0)))
   309  
   310  	err = optionF32.Scan(math.MaxFloat64)
   311  	Equal(t, err, nil)
   312  	Equal(t, optionF32, Some(float32(math.Inf(1))))
   313  
   314  	err = optionF64.Scan(2.0)
   315  	Equal(t, err, nil)
   316  	Equal(t, optionF64, Some(2.0))
   317  
   318  	err = optionByte.Scan(uint8('1'))
   319  	Equal(t, err, nil)
   320  	Equal(t, optionByte, Some(uint8('1')))
   321  
   322  	err = optionTime.Scan("2023-06-13T06:34:32Z")
   323  	Equal(t, err, nil)
   324  	Equal(t, optionTime, Some(time.Date(2023, 6, 13, 6, 34, 32, 0, time.UTC)))
   325  
   326  	err = optionTime.Scan([]byte("2023-06-13T06:34:32Z"))
   327  	Equal(t, err, nil)
   328  	Equal(t, optionTime, Some(time.Date(2023, 6, 13, 6, 34, 32, 0, time.UTC)))
   329  
   330  	err = optionTime.Scan(time.Date(2023, 6, 13, 6, 34, 32, 0, time.UTC))
   331  	Equal(t, err, nil)
   332  	Equal(t, optionTime, Some(time.Date(2023, 6, 13, 6, 34, 32, 0, time.UTC)))
   333  
   334  	// Test nil
   335  	var nullableOption Option[int64]
   336  	err = nullableOption.Scan(nil)
   337  	Equal(t, err, nil)
   338  	Equal(t, nullableOption, None[int64]())
   339  
   340  	// custom scanner
   341  	var custom Option[customScanner]
   342  	err = custom.Scan("GOT HERE")
   343  	Equal(t, err, nil)
   344  	Equal(t, custom, Some(customScanner{S: "GOT HERE"}))
   345  
   346  	// custom scanner scan nil
   347  	var customNil Option[customScanner]
   348  	err = customNil.Scan(nil)
   349  	Equal(t, err, nil)
   350  	Equal(t, customNil, None[customScanner]())
   351  
   352  	// test unmarshal to struct
   353  	type myStruct struct {
   354  		Name string `json:"name"`
   355  	}
   356  
   357  	var optionMyStruct Option[myStruct]
   358  	err = optionMyStruct.Scan([]byte(`{"name":"test"}`))
   359  	Equal(t, err, nil)
   360  	Equal(t, optionMyStruct, Some(myStruct{Name: "test"}))
   361  
   362  	err = optionMyStruct.Scan(json.RawMessage(`{"name":"test2"}`))
   363  	Equal(t, err, nil)
   364  	Equal(t, optionMyStruct, Some(myStruct{Name: "test2"}))
   365  
   366  	var optionArrayOfMyStruct Option[[]myStruct]
   367  	err = optionArrayOfMyStruct.Scan([]byte(`[{"name":"test"}]`))
   368  	Equal(t, err, nil)
   369  	Equal(t, optionArrayOfMyStruct, Some([]myStruct{{Name: "test"}}))
   370  
   371  	var optionMap Option[map[string]any]
   372  	err = optionMap.Scan([]byte(`{"name":"test"}`))
   373  	Equal(t, err, nil)
   374  	Equal(t, optionMap, Some(map[string]any{"name": "test"}))
   375  
   376  	// test custom types
   377  	var ct Option[customStringType]
   378  	err = ct.Scan("test")
   379  	Equal(t, err, nil)
   380  	Equal(t, ct, Some(customStringType("test")))
   381  
   382  	err = optionArrBytes.Scan([]byte(`[1,2,3]`))
   383  	Equal(t, err, nil)
   384  	Equal(t, optionArrBytes, Some([]byte(`[1,2,3]`)))
   385  
   386  	err = optionArrBytes.Scan([]byte{4, 5, 6})
   387  	Equal(t, err, nil)
   388  	Equal(t, optionArrBytes, Some([]byte{4, 5, 6}))
   389  
   390  	err = optionRawMessage.Scan([]byte(`[1,2,3]`))
   391  	Equal(t, err, nil)
   392  	Equal(t, true, string(optionRawMessage.Unwrap()) == "[1,2,3]")
   393  
   394  	err = optionRawMessage.Scan([]byte{4, 5, 6})
   395  	Equal(t, err, nil)
   396  	Equal(t, true, string(optionRawMessage.Unwrap()) == string([]byte{4, 5, 6}))
   397  }
   398  
   399  func TestNilOption(t *testing.T) {
   400  	value := Some[any](nil)
   401  	Equal(t, false, value.IsNone())
   402  	Equal(t, true, value.IsSome())
   403  	Equal(t, nil, value.Unwrap())
   404  
   405  	ret := returnTypedNoneOption()
   406  	Equal(t, true, ret.IsNone())
   407  	Equal(t, false, ret.IsSome())
   408  	PanicMatches(t, func() {
   409  		ret.Unwrap()
   410  	}, "Option.Unwrap: option is None")
   411  
   412  	ret = returnTypedSomeOption()
   413  	Equal(t, false, ret.IsNone())
   414  	Equal(t, true, ret.IsSome())
   415  	Equal(t, myStruct{}, ret.Unwrap())
   416  
   417  	retPtr := returnTypedNoneOptionPtr()
   418  	Equal(t, true, retPtr.IsNone())
   419  	Equal(t, false, retPtr.IsSome())
   420  
   421  	retPtr = returnTypedSomeOptionPtr()
   422  	Equal(t, false, retPtr.IsNone())
   423  	Equal(t, true, retPtr.IsSome())
   424  	Equal(t, new(myStruct), retPtr.Unwrap())
   425  }
   426  
   427  func TestOptionJSON(t *testing.T) {
   428  	type s struct {
   429  		Timestamp Option[time.Time] `json:"ts"`
   430  	}
   431  	now := time.Now().UTC().Truncate(time.Minute)
   432  	tv := s{Timestamp: Some(now)}
   433  
   434  	b, err := json.Marshal(tv)
   435  	Equal(t, nil, err)
   436  	Equal(t, `{"ts":"`+now.Format(time.RFC3339)+`"}`, string(b))
   437  
   438  	tv = s{}
   439  	b, err = json.Marshal(tv)
   440  	Equal(t, nil, err)
   441  	Equal(t, `{"ts":null}`, string(b))
   442  }
   443  
   444  func TestOptionJSONOmitempty(t *testing.T) {
   445  	type s struct {
   446  		Timestamp Option[time.Time] `json:"ts,omitempty"`
   447  	}
   448  	now := time.Now().UTC().Truncate(time.Minute)
   449  	tv := s{Timestamp: Some(now)}
   450  
   451  	b, err := json.Marshal(tv)
   452  	Equal(t, nil, err)
   453  	Equal(t, `{"ts":"`+now.Format(time.RFC3339)+`"}`, string(b))
   454  
   455  	type s2 struct {
   456  		Timestamp *Option[time.Time] `json:"ts,omitempty"`
   457  	}
   458  	tv2 := &s2{}
   459  	b, err = json.Marshal(tv2)
   460  	Equal(t, nil, err)
   461  	Equal(t, `{}`, string(b))
   462  }
   463  
   464  type myStruct struct{}
   465  
   466  func returnTypedNoneOption() Option[myStruct] {
   467  	return None[myStruct]()
   468  }
   469  
   470  func returnTypedSomeOption() Option[myStruct] {
   471  	return Some(myStruct{})
   472  }
   473  
   474  func returnTypedNoneOptionPtr() Option[*myStruct] {
   475  	return None[*myStruct]()
   476  }
   477  
   478  func returnTypedSomeOptionPtr() Option[*myStruct] {
   479  	return Some(new(myStruct))
   480  }
   481  
   482  func BenchmarkOption(b *testing.B) {
   483  	for i := 0; i < b.N; i++ {
   484  		opt := returnTypedSomeOption()
   485  		if opt.IsSome() {
   486  			_ = opt.Unwrap()
   487  		}
   488  	}
   489  }
   490  
   491  func BenchmarkOptionPtr(b *testing.B) {
   492  	for i := 0; i < b.N; i++ {
   493  		opt := returnTypedSomeOptionPtr()
   494  		if opt.IsSome() {
   495  			_ = opt.Unwrap()
   496  		}
   497  	}
   498  }
   499  
   500  func BenchmarkNoOptionPtr(b *testing.B) {
   501  	for i := 0; i < b.N; i++ {
   502  		result := returnTypedNoOption()
   503  		if result != nil {
   504  			_ = result
   505  		}
   506  	}
   507  }
   508  
   509  func BenchmarkOptionNil(b *testing.B) {
   510  	for i := 0; i < b.N; i++ {
   511  		opt := returnTypedSomeOptionNil()
   512  		if opt.IsSome() {
   513  			_ = opt.Unwrap()
   514  		}
   515  	}
   516  }
   517  
   518  func BenchmarkNoOptionNil(b *testing.B) {
   519  	for i := 0; i < b.N; i++ {
   520  		result, found := returnNoOptionNil()
   521  		if found {
   522  			_ = result
   523  		}
   524  	}
   525  }
   526  
   527  func returnTypedSomeOptionNil() Option[any] {
   528  	return Some[any](nil)
   529  }
   530  
   531  func returnTypedNoOption() *myStruct {
   532  	return new(myStruct)
   533  }
   534  
   535  func returnNoOptionNil() (any, bool) {
   536  	return nil, true
   537  }