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 }