1package defaults 2 3import ( 4 "reflect" 5 "testing" 6 "time" 7 8 "github.com/creasty/defaults/internal/fixture" 9) 10 11type ( 12 MyInt int 13 MyInt8 int8 14 MyInt16 int16 15 MyInt32 int32 16 MyInt64 int64 17 MyUint uint 18 MyUint8 uint8 19 MyUint16 uint16 20 MyUint32 uint32 21 MyUint64 uint64 22 MyUintptr uintptr 23 MyFloat32 float32 24 MyFloat64 float64 25 MyBool bool 26 MyString string 27 MyMap map[string]int 28 MySlice []int 29) 30 31type Sample struct { 32 Int int `default:"1"` 33 Int8 int8 `default:"8"` 34 Int16 int16 `default:"16"` 35 Int32 int32 `default:"32"` 36 Int64 int64 `default:"64"` 37 Uint uint `default:"1"` 38 Uint8 uint8 `default:"8"` 39 Uint16 uint16 `default:"16"` 40 Uint32 uint32 `default:"32"` 41 Uint64 uint64 `default:"64"` 42 Uintptr uintptr `default:"1"` 43 Float32 float32 `default:"1.32"` 44 Float64 float64 `default:"1.64"` 45 BoolTrue bool `default:"true"` 46 BoolFalse bool `default:"false"` 47 String string `default:"hello"` 48 Duration time.Duration `default:"10s"` 49 50 IntOct int `default:"0o1"` 51 Int8Oct int8 `default:"0o10"` 52 Int16Oct int16 `default:"0o20"` 53 Int32Oct int32 `default:"0o40"` 54 Int64Oct int64 `default:"0o100"` 55 UintOct uint `default:"0o1"` 56 Uint8Oct uint8 `default:"0o10"` 57 Uint16Oct uint16 `default:"0o20"` 58 Uint32Oct uint32 `default:"0o40"` 59 Uint64Oct uint64 `default:"0o100"` 60 61 IntHex int `default:"0x1"` 62 Int8Hex int8 `default:"0x8"` 63 Int16Hex int16 `default:"0x10"` 64 Int32Hex int32 `default:"0x20"` 65 Int64Hex int64 `default:"0x40"` 66 UintHex uint `default:"0x1"` 67 Uint8Hex uint8 `default:"0x8"` 68 Uint16Hex uint16 `default:"0x10"` 69 Uint32Hex uint32 `default:"0x20"` 70 Uint64Hex uint64 `default:"0x40"` 71 72 IntBin int `default:"0b1"` 73 Int8Bin int8 `default:"0b1000"` 74 Int16Bin int16 `default:"0b10000"` 75 Int32Bin int32 `default:"0b100000"` 76 Int64Bin int64 `default:"0b1000000"` 77 UintBin uint `default:"0b1"` 78 Uint8Bin uint8 `default:"0b1000"` 79 Uint16Bin uint16 `default:"0b10000"` 80 Uint32Bin uint32 `default:"0b100000"` 81 Uint64Bin uint64 `default:"0b1000000"` 82 83 Struct Struct `default:"{}"` 84 Map map[string]int `default:"{}"` 85 Slice []string `default:"[]"` 86 87 IntPtr *int `default:"1"` 88 UintPtr *uint `default:"1"` 89 Float32Ptr *float32 `default:"1"` 90 BoolPtr *bool `default:"true"` 91 StringPtr *string `default:"hello"` 92 StructPtr *Struct `default:"{}"` 93 MapPtr *map[string]int `default:"{}"` 94 SlicePtr *[]string `default:"[]"` 95 96 MyInt MyInt `default:"1"` 97 MyInt8 MyInt8 `default:"8"` 98 MyInt16 MyInt16 `default:"16"` 99 MyInt32 MyInt32 `default:"32"` 100 MyInt64 MyInt64 `default:"64"` 101 MyUint MyUint `default:"1"` 102 MyUint8 MyUint8 `default:"8"` 103 MyUint16 MyUint16 `default:"16"` 104 MyUint32 MyUint32 `default:"32"` 105 MyUint64 MyUint64 `default:"64"` 106 MyUintptr MyUintptr `default:"1"` 107 MyFloat32 MyFloat32 `default:"1.32"` 108 MyFloat64 MyFloat64 `default:"1.64"` 109 MyBoolTrue MyBool `default:"true"` 110 MyBoolFalse MyBool `default:"false"` 111 MyString MyString `default:"hello"` 112 MyMap MyMap `default:"{}"` 113 MySlice MySlice `default:"[]"` 114 115 StructWithJSON Struct `default:"{\"Foo\": 123}"` 116 StructPtrWithJSON *Struct `default:"{\"Foo\": 123}"` 117 MapWithJSON map[string]int `default:"{\"foo\": 123}"` 118 SliceWithJSON []string `default:"[\"foo\"]"` 119 120 Empty string `default:""` 121 122 NoDefault *string `default:"-"` 123 NoDefaultStruct Struct `default:"-"` 124 125 MapWithNoTag map[string]int 126 SliceWithNoTag []string 127 StructPtrWithNoTag *Struct 128 StructWithNoTag Struct 129 DeepSliceOfStructWithNoTag [][][]Struct 130 131 NonInitialString string `default:"foo"` 132 NonInitialSlice []int `default:"[123]"` 133 NonInitialStruct Struct `default:"{}"` 134 NonInitialStructPtr *Struct `default:"{}"` 135} 136 137type Struct struct { 138 Embedded `default:"{}"` 139 140 Foo int 141 Bar int 142 WithDefault string `default:"foo"` 143} 144 145func (s *Struct) SetDefaults() { 146 s.Bar = 456 147} 148 149type Embedded struct { 150 Int int `default:"1"` 151} 152 153func TestMustSet(t *testing.T) { 154 155 t.Run("right way", func(t *testing.T) { 156 defer func() { 157 if err := recover(); err != nil { 158 t.Fatalf("it should not panic error: %v", err) 159 } 160 }() 161 sample := &Sample{ 162 NonInitialString: "string", 163 NonInitialSlice: []int{1, 2, 3}, 164 NonInitialStruct: Struct{Foo: 123}, 165 NonInitialStructPtr: &Struct{Foo: 123}, 166 DeepSliceOfStructWithNoTag: [][][]Struct{{{{Foo: 123}}}}, 167 } 168 MustSet(sample) 169 }) 170 171 t.Run("not struct", func(t *testing.T) { 172 defer func() { 173 if err := recover(); err != nil { 174 t.Logf("panic error: %v", err) 175 } 176 }() 177 var a int 178 MustSet(&a) 179 }) 180 181 t.Run("not pointer", func(t *testing.T) { 182 defer func() { 183 if err := recover(); err != nil { 184 t.Logf("panic error: %v", err) 185 } 186 }() 187 sample := Sample{ 188 NonInitialString: "string", 189 NonInitialSlice: []int{1, 2, 3}, 190 NonInitialStruct: Struct{Foo: 123}, 191 NonInitialStructPtr: &Struct{Foo: 123}, 192 DeepSliceOfStructWithNoTag: [][][]Struct{{{{Foo: 123}}}}, 193 } 194 MustSet(sample) 195 }) 196 197} 198 199func TestInit(t *testing.T) { 200 sample := &Sample{ 201 NonInitialString: "string", 202 NonInitialSlice: []int{1, 2, 3}, 203 NonInitialStruct: Struct{Foo: 123}, 204 NonInitialStructPtr: &Struct{Foo: 123}, 205 DeepSliceOfStructWithNoTag: [][][]Struct{{{{Foo: 123}}}}, 206 } 207 208 if err := Set(sample); err != nil { 209 t.Fatalf("it should not return an error: %v", err) 210 } 211 212 nonPtrVal := 1 213 214 if err := Set(nonPtrVal); err == nil { 215 t.Fatalf("it should return an error when used for a non-pointer type") 216 } 217 if err := Set(&nonPtrVal); err == nil { 218 t.Fatalf("it should return an error when used for a non-pointer type") 219 } 220 221 Set(&fixture.Sample{}) // should not panic 222 223 t.Run("primitive types", func(t *testing.T) { 224 if sample.Int != 1 { 225 t.Errorf("it should initialize int") 226 } 227 if sample.Int8 != 8 { 228 t.Errorf("it should initialize int8") 229 } 230 if sample.Int16 != 16 { 231 t.Errorf("it should initialize int16") 232 } 233 if sample.Int32 != 32 { 234 t.Errorf("it should initialize int32") 235 } 236 if sample.Int64 != 64 { 237 t.Errorf("it should initialize int64") 238 } 239 if sample.Uint != 1 { 240 t.Errorf("it should initialize uint") 241 } 242 if sample.Uint8 != 8 { 243 t.Errorf("it should initialize uint8") 244 } 245 if sample.Uint16 != 16 { 246 t.Errorf("it should initialize uint16") 247 } 248 if sample.Uint32 != 32 { 249 t.Errorf("it should initialize uint32") 250 } 251 if sample.Uint64 != 64 { 252 t.Errorf("it should initialize uint64") 253 } 254 if sample.Uintptr != 1 { 255 t.Errorf("it should initialize uintptr") 256 } 257 if sample.Float32 != 1.32 { 258 t.Errorf("it should initialize float32") 259 } 260 if sample.Float64 != 1.64 { 261 t.Errorf("it should initialize float64") 262 } 263 if sample.BoolTrue != true { 264 t.Errorf("it should initialize bool (true)") 265 } 266 if sample.BoolFalse != false { 267 t.Errorf("it should initialize bool (false)") 268 } 269 if *sample.BoolPtr != true { 270 t.Errorf("it should initialize bool (true)") 271 } 272 if sample.String != "hello" { 273 t.Errorf("it should initialize string") 274 } 275 276 if sample.IntOct != 0o1 { 277 t.Errorf("it should initialize int with octal literal") 278 } 279 if sample.Int8Oct != 0o10 { 280 t.Errorf("it should initialize int8 with octal literal") 281 } 282 if sample.Int16Oct != 0o20 { 283 t.Errorf("it should initialize int16 with octal literal") 284 } 285 if sample.Int32Oct != 0o40 { 286 t.Errorf("it should initialize int32 with octal literal") 287 } 288 if sample.Int64Oct != 0o100 { 289 t.Errorf("it should initialize int64 with octal literal") 290 } 291 if sample.UintOct != 0o1 { 292 t.Errorf("it should initialize uint with octal literal") 293 } 294 if sample.Uint8Oct != 0o10 { 295 t.Errorf("it should initialize uint8 with octal literal") 296 } 297 if sample.Uint16Oct != 0o20 { 298 t.Errorf("it should initialize uint16 with octal literal") 299 } 300 if sample.Uint32Oct != 0o40 { 301 t.Errorf("it should initialize uint32 with octal literal") 302 } 303 if sample.Uint64Oct != 0o100 { 304 t.Errorf("it should initialize uint64 with octal literal") 305 } 306 307 if sample.IntHex != 0x1 { 308 t.Errorf("it should initialize int with hexadecimal literal") 309 } 310 if sample.Int8Hex != 0x8 { 311 t.Errorf("it should initialize int8 with hexadecimal literal") 312 } 313 if sample.Int16Hex != 0x10 { 314 t.Errorf("it should initialize int16 with hexadecimal literal") 315 } 316 if sample.Int32Hex != 0x20 { 317 t.Errorf("it should initialize int32 with hexadecimal literal") 318 } 319 if sample.Int64Hex != 0x40 { 320 t.Errorf("it should initialize int64 with hexadecimal literal") 321 } 322 if sample.UintHex != 0x1 { 323 t.Errorf("it should initialize uint with hexadecimal literal") 324 } 325 if sample.Uint8Hex != 0x8 { 326 t.Errorf("it should initialize uint8 with hexadecimal literal") 327 } 328 if sample.Uint16Hex != 0x10 { 329 t.Errorf("it should initialize uint16 with hexadecimal literal") 330 } 331 if sample.Uint32Hex != 0x20 { 332 t.Errorf("it should initialize uint32 with hexadecimal literal") 333 } 334 if sample.Uint64Hex != 0x40 { 335 t.Errorf("it should initialize uint64 with hexadecimal literal") 336 } 337 338 if sample.IntBin != 0b1 { 339 t.Errorf("it should initialize int with binary literal") 340 } 341 if sample.Int8Bin != 0b1000 { 342 t.Errorf("it should initialize int8 with binary literal") 343 } 344 if sample.Int16Bin != 0b10000 { 345 t.Errorf("it should initialize int16 with binary literal") 346 } 347 if sample.Int32Bin != 0b100000 { 348 t.Errorf("it should initialize int32 with binary literal") 349 } 350 if sample.Int64Bin != 0b1000000 { 351 t.Errorf("it should initialize int64 with binary literal") 352 } 353 if sample.UintBin != 0b1 { 354 t.Errorf("it should initialize uint with binary literal") 355 } 356 if sample.Uint8Bin != 0b1000 { 357 t.Errorf("it should initialize uint8 with binary literal") 358 } 359 if sample.Uint16Bin != 0b10000 { 360 t.Errorf("it should initialize uint16 with binary literal") 361 } 362 if sample.Uint32Bin != 0b100000 { 363 t.Errorf("it should initialize uint32 with binary literal") 364 } 365 if sample.Uint64Bin != 0b1000000 { 366 t.Errorf("it should initialize uint64 with binary literal") 367 } 368 }) 369 370 t.Run("complex types", func(t *testing.T) { 371 if sample.StructPtr == nil { 372 t.Errorf("it should initialize struct pointer") 373 } 374 if sample.Map == nil { 375 t.Errorf("it should initialize map") 376 } 377 if sample.Slice == nil { 378 t.Errorf("it should initialize slice") 379 } 380 }) 381 382 t.Run("pointer types", func(t *testing.T) { 383 if sample.IntPtr == nil || *sample.IntPtr != 1 { 384 t.Errorf("it should initialize int pointer") 385 } 386 if sample.UintPtr == nil || *sample.UintPtr != 1 { 387 t.Errorf("it should initialize uint pointer") 388 } 389 if sample.Float32Ptr == nil || *sample.Float32Ptr != 1 { 390 t.Errorf("it should initialize float32 pointer") 391 } 392 if sample.BoolPtr == nil || *sample.BoolPtr != true { 393 t.Errorf("it should initialize bool pointer") 394 } 395 if sample.StringPtr == nil || *sample.StringPtr != "hello" { 396 t.Errorf("it should initialize string pointer") 397 } 398 if sample.MapPtr == nil { 399 t.Errorf("it should initialize map ptr") 400 } 401 if sample.SlicePtr == nil { 402 t.Errorf("it should initialize slice ptr") 403 } 404 }) 405 406 t.Run("aliased types", func(t *testing.T) { 407 if sample.MyInt != 1 { 408 t.Errorf("it should initialize int") 409 } 410 if sample.MyInt8 != 8 { 411 t.Errorf("it should initialize int8") 412 } 413 if sample.MyInt16 != 16 { 414 t.Errorf("it should initialize int16") 415 } 416 if sample.MyInt32 != 32 { 417 t.Errorf("it should initialize int32") 418 } 419 if sample.MyInt64 != 64 { 420 t.Errorf("it should initialize int64") 421 } 422 if sample.MyUint != 1 { 423 t.Errorf("it should initialize uint") 424 } 425 if sample.MyUint8 != 8 { 426 t.Errorf("it should initialize uint8") 427 } 428 if sample.MyUint16 != 16 { 429 t.Errorf("it should initialize uint16") 430 } 431 if sample.MyUint32 != 32 { 432 t.Errorf("it should initialize uint32") 433 } 434 if sample.MyUint64 != 64 { 435 t.Errorf("it should initialize uint64") 436 } 437 if sample.MyUintptr != 1 { 438 t.Errorf("it should initialize uintptr") 439 } 440 if sample.MyFloat32 != 1.32 { 441 t.Errorf("it should initialize float32") 442 } 443 if sample.MyFloat64 != 1.64 { 444 t.Errorf("it should initialize float64") 445 } 446 if sample.MyBoolTrue != true { 447 t.Errorf("it should initialize bool (true)") 448 } 449 if sample.MyBoolFalse != false { 450 t.Errorf("it should initialize bool (false)") 451 } 452 if sample.MyString != "hello" { 453 t.Errorf("it should initialize string") 454 } 455 456 if sample.MyMap == nil { 457 t.Errorf("it should initialize map") 458 } 459 if sample.MySlice == nil { 460 t.Errorf("it should initialize slice") 461 } 462 }) 463 464 t.Run("nested", func(t *testing.T) { 465 if sample.Struct.WithDefault != "foo" { 466 t.Errorf("it should set default on inner field in struct") 467 } 468 if sample.StructPtr == nil || sample.StructPtr.WithDefault != "foo" { 469 t.Errorf("it should set default on inner field in struct pointer") 470 } 471 if sample.Struct.Embedded.Int != 1 { 472 t.Errorf("it should set default on an Embedded struct") 473 } 474 }) 475 476 t.Run("complex types with json", func(t *testing.T) { 477 if sample.StructWithJSON.Foo != 123 { 478 t.Errorf("it should initialize struct with json") 479 } 480 if sample.StructPtrWithJSON == nil || sample.StructPtrWithJSON.Foo != 123 { 481 t.Errorf("it should initialize struct pointer with json") 482 } 483 if sample.MapWithJSON["foo"] != 123 { 484 t.Errorf("it should initialize map with json") 485 } 486 if len(sample.SliceWithJSON) == 0 || sample.SliceWithJSON[0] != "foo" { 487 t.Errorf("it should initialize slice with json") 488 } 489 490 t.Run("invalid json", func(t *testing.T) { 491 if err := Set(&struct { 492 I []int `default:"[!]"` 493 }{}); err == nil { 494 t.Errorf("it should return error") 495 } 496 497 if err := Set(&struct { 498 I map[string]int `default:"{1}"` 499 }{}); err == nil { 500 t.Errorf("it should return error") 501 } 502 503 if err := Set(&struct { 504 S struct { 505 I []int 506 } `default:"{!}"` 507 }{}); err == nil { 508 t.Errorf("it should return error") 509 } 510 511 if err := Set(&struct { 512 S struct { 513 I []int `default:"[!]"` 514 } 515 }{}); err == nil { 516 t.Errorf("it should return error") 517 } 518 }) 519 }) 520 521 t.Run("Setter interface", func(t *testing.T) { 522 if sample.Struct.Bar != 456 { 523 t.Errorf("it should initialize struct") 524 } 525 if sample.StructPtr == nil || sample.StructPtr.Bar != 456 { 526 t.Errorf("it should initialize struct pointer") 527 } 528 }) 529 530 t.Run("non-initial value", func(t *testing.T) { 531 if sample.NonInitialString != "string" { 532 t.Errorf("it should not override non-initial value") 533 } 534 if !reflect.DeepEqual(sample.NonInitialSlice, []int{1, 2, 3}) { 535 t.Errorf("it should not override non-initial value") 536 } 537 if !reflect.DeepEqual(sample.NonInitialStruct, Struct{Embedded: Embedded{Int: 1}, Foo: 123, Bar: 456, WithDefault: "foo"}) { 538 t.Errorf("it should not override non-initial value but set defaults for fields") 539 } 540 if !reflect.DeepEqual(sample.NonInitialStructPtr, &Struct{Embedded: Embedded{Int: 1}, Foo: 123, Bar: 456, WithDefault: "foo"}) { 541 t.Errorf("it should not override non-initial value but set defaults for fields") 542 } 543 }) 544 545 t.Run("no tag", func(t *testing.T) { 546 if sample.MapWithNoTag != nil { 547 t.Errorf("it should not initialize pointer type (map)") 548 } 549 if sample.SliceWithNoTag != nil { 550 t.Errorf("it should not initialize pointer type (slice)") 551 } 552 if sample.StructPtrWithNoTag != nil { 553 t.Errorf("it should not initialize pointer type (struct)") 554 } 555 if sample.StructWithNoTag.WithDefault != "foo" { 556 t.Errorf("it should automatically recurse into a struct even without a tag") 557 } 558 if !reflect.DeepEqual(sample.DeepSliceOfStructWithNoTag, [][][]Struct{{{{Embedded: Embedded{Int: 1}, Foo: 123, Bar: 456, WithDefault: "foo"}}}}) { 559 t.Errorf("it should automatically recurse into a slice of structs even without a tag") 560 } 561 }) 562 563 t.Run("opt-out", func(t *testing.T) { 564 if sample.NoDefault != nil { 565 t.Errorf("it should not be set") 566 } 567 if sample.NoDefaultStruct.WithDefault != "" { 568 t.Errorf("it should not initialize a struct with default values") 569 } 570 }) 571} 572 573func TestCanUpdate(t *testing.T) { 574 type st struct{ Int int } 575 576 var myStructPtr *st 577 578 pairs := map[interface{}]bool{ 579 0: true, 580 123: false, 581 float64(0): true, 582 float64(123): false, 583 "": true, 584 "string": false, 585 false: true, 586 true: false, 587 st{}: true, 588 st{Int: 123}: false, 589 myStructPtr: true, 590 &st{}: false, 591 } 592 for input, expect := range pairs { 593 output := CanUpdate(input) 594 if output != expect { 595 t.Errorf("CanUpdate(%v) returns %v, expected %v", input, output, expect) 596 } 597 } 598} 599 600type Child struct { 601 Name string `default:"Tom"` 602 Age int `default:"20"` 603} 604 605type Parent struct { 606 Child *Child 607} 608 609func TestPointerStructMember(t *testing.T) { 610 m := Parent{Child: &Child{Name: "Jim"}} 611 Set(&m) 612 if m.Child.Age != 20 { 613 t.Errorf("20 is expected") 614 } 615} 616 617type Main struct { 618 MainInt int `default:"-"` 619 *Other `default:"{}"` 620} 621 622type Other struct { 623 OtherInt int `default:"-"` 624} 625 626func (s *Main) SetDefaults() { 627 if CanUpdate(s.MainInt) { 628 s.MainInt = 1 629 } 630} 631 632func (s *Other) SetDefaults() { 633 if CanUpdate(s.OtherInt) { 634 s.OtherInt = 1 635 } 636} 637 638func TestDefaultsSetter(t *testing.T) { 639 main := &Main{} 640 Set(main) 641 if main.OtherInt != 1 { 642 t.Errorf("expected 1 for OtherInt, got %d", main.OtherInt) 643 } 644 if main.MainInt != 1 { 645 t.Errorf("expected 1 for MainInt, got %d", main.MainInt) 646 } 647} 648