github.com/vishnusomank/figtree@v0.1.0/figtree_test.go (about) 1 package figtree 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "os" 8 "path" 9 "reflect" 10 "runtime" 11 "strconv" 12 "strings" 13 "testing" 14 "unicode" 15 16 "emperror.dev/errors" 17 logging "gopkg.in/op/go-logging.v1" 18 yaml "gopkg.in/yaml.v3" 19 20 "github.com/coryb/walky" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23 ) 24 25 type info struct { 26 name string 27 line string 28 } 29 30 func line() string { 31 _, file, line, _ := runtime.Caller(1) 32 return fmt.Sprintf("%s:%d", path.Base(file), line) 33 } 34 35 func init() { 36 StringifyValue = false 37 logging.SetLevel(logging.NOTICE, "") 38 } 39 40 func newFigTreeFromEnv(opts ...CreateOption) *FigTree { 41 cwd, _ := os.Getwd() 42 opts = append([]CreateOption{ 43 WithHome(os.Getenv("HOME")), 44 WithCwd(cwd), 45 WithEnvPrefix("FIGTREE"), 46 }, opts...) 47 48 return NewFigTree(opts...) 49 } 50 51 func tSrc(s string, l, c int) SourceLocation { 52 return NewSource(s, WithLocation(&FileCoordinate{Line: l, Column: c})) 53 } 54 55 type TestOptions struct { 56 String1 StringOption `json:"str1,omitempty" yaml:"str1,omitempty"` 57 LeaveEmpty StringOption `json:"leave-empty,omitempty" yaml:"leave-empty,omitempty"` 58 Array1 ListStringOption `json:"arr1,omitempty" yaml:"arr1,omitempty"` 59 Map1 MapStringOption `json:"map1,omitempty" yaml:"map1,omitempty"` 60 Int1 IntOption `json:"int1,omitempty" yaml:"int1,omitempty"` 61 Float1 Float32Option `json:"float1,omitempty" yaml:"float1,omitempty"` 62 Bool1 BoolOption `json:"bool1,omitempty" yaml:"bool1,omitempty"` 63 } 64 65 type TestBuiltin struct { 66 String1 string `yaml:"str1,omitempty"` 67 LeaveEmpty string `yaml:"leave-empty,omitempty"` 68 Array1 []string `yaml:"arr1,omitempty"` 69 Map1 map[string]string `yaml:"map1,omitempty"` 70 Int1 int `yaml:"int1,omitempty"` 71 Float1 float32 `yaml:"float1,omitempty"` 72 Bool1 bool `yaml:"bool1,omitempty"` 73 } 74 75 func TestOptionsLoadConfigD3(t *testing.T) { 76 opts := TestOptions{} 77 require.NoError(t, os.Chdir("d1/d2/d3")) 78 t.Cleanup(func() { 79 _ = os.Chdir("../../..") 80 }) 81 82 arr1 := []StringOption{} 83 arr1 = append(arr1, StringOption{tSrc("figtree.yml", 3, 5), true, "d3arr1val1"}) 84 arr1 = append(arr1, StringOption{tSrc("figtree.yml", 4, 5), true, "d3arr1val2"}) 85 arr1 = append(arr1, StringOption{tSrc("figtree.yml", 5, 5), true, "dupval"}) 86 arr1 = append(arr1, StringOption{tSrc("../figtree.yml", 3, 5), true, "211"}) 87 arr1 = append(arr1, StringOption{tSrc("../figtree.yml", 4, 5), true, "d2arr1val2"}) 88 arr1 = append(arr1, StringOption{tSrc("../../figtree.yml", 3, 5), true, "d1arr1val1"}) 89 arr1 = append(arr1, StringOption{tSrc("../../figtree.yml", 4, 5), true, "d1arr1val2"}) 90 91 expected := TestOptions{ 92 String1: StringOption{tSrc("figtree.yml", 1, 7), true, "d3str1val1"}, 93 LeaveEmpty: StringOption{}, 94 Array1: arr1, 95 Map1: map[string]StringOption{ 96 "key0": {tSrc("../../figtree.yml", 7, 9), true, "d1map1val0"}, 97 "key1": {tSrc("../figtree.yml", 7, 9), true, "211"}, 98 "key2": {tSrc("figtree.yml", 7, 9), true, "d3map1val2"}, 99 "key3": {tSrc("figtree.yml", 8, 9), true, "d3map1val3"}, 100 "dup": {tSrc("figtree.yml", 9, 9), true, "d3dupval"}, 101 }, 102 Int1: IntOption{tSrc("figtree.yml", 10, 7), true, 333}, 103 Float1: Float32Option{tSrc("figtree.yml", 11, 9), true, 3.33}, 104 Bool1: BoolOption{tSrc("figtree.yml", 12, 8), true, true}, 105 } 106 107 fig := newFigTreeFromEnv() 108 err := fig.LoadAllConfigs("figtree.yml", &opts) 109 assert.NoError(t, err) 110 assert.Exactly(t, expected, opts) 111 } 112 113 func TestOptionsLoadConfigD2(t *testing.T) { 114 opts := TestOptions{} 115 require.NoError(t, os.Chdir("d1/d2")) 116 t.Cleanup(func() { 117 _ = os.Chdir("../..") 118 }) 119 120 arr1 := []StringOption{} 121 arr1 = append(arr1, StringOption{tSrc("figtree.yml", 3, 5), true, "211"}) 122 arr1 = append(arr1, StringOption{tSrc("figtree.yml", 4, 5), true, "d2arr1val2"}) 123 arr1 = append(arr1, StringOption{tSrc("figtree.yml", 5, 5), true, "dupval"}) 124 arr1 = append(arr1, StringOption{tSrc("../figtree.yml", 3, 5), true, "d1arr1val1"}) 125 arr1 = append(arr1, StringOption{tSrc("../figtree.yml", 4, 5), true, "d1arr1val2"}) 126 127 expected := TestOptions{ 128 String1: StringOption{tSrc("figtree.yml", 1, 7), true, "d2str1val1"}, 129 LeaveEmpty: StringOption{}, 130 Array1: arr1, 131 Map1: map[string]StringOption{ 132 "key0": {tSrc("../figtree.yml", 7, 9), true, "d1map1val0"}, 133 "key1": {tSrc("figtree.yml", 7, 9), true, "211"}, 134 "key2": {tSrc("figtree.yml", 8, 9), true, "d2map1val2"}, 135 "dup": {tSrc("figtree.yml", 9, 9), true, "d2dupval"}, 136 }, 137 Int1: IntOption{tSrc("figtree.yml", 10, 7), true, 222}, 138 Float1: Float32Option{tSrc("figtree.yml", 11, 9), true, 2.22}, 139 Bool1: BoolOption{tSrc("figtree.yml", 12, 8), true, false}, 140 } 141 142 fig := newFigTreeFromEnv() 143 err := fig.LoadAllConfigs("figtree.yml", &opts) 144 assert.NoError(t, err) 145 assert.Exactly(t, expected, opts) 146 } 147 148 func TestOptionsLoadConfigD1(t *testing.T) { 149 opts := TestOptions{} 150 require.NoError(t, os.Chdir("d1")) 151 t.Cleanup(func() { 152 _ = os.Chdir("..") 153 }) 154 155 arr1 := []StringOption{} 156 arr1 = append(arr1, StringOption{tSrc("figtree.yml", 3, 5), true, "d1arr1val1"}) 157 arr1 = append(arr1, StringOption{tSrc("figtree.yml", 4, 5), true, "d1arr1val2"}) 158 arr1 = append(arr1, StringOption{tSrc("figtree.yml", 5, 5), true, "dupval"}) 159 160 expected := TestOptions{ 161 String1: StringOption{tSrc("figtree.yml", 1, 7), true, "d1str1val1"}, 162 LeaveEmpty: StringOption{}, 163 Array1: arr1, 164 Map1: map[string]StringOption{ 165 "key0": {tSrc("figtree.yml", 7, 9), true, "d1map1val0"}, 166 "key1": {tSrc("figtree.yml", 8, 9), true, "d1map1val1"}, 167 "dup": {tSrc("figtree.yml", 9, 9), true, "d1dupval"}, 168 }, 169 Int1: IntOption{tSrc("figtree.yml", 10, 7), true, 111}, 170 Float1: Float32Option{tSrc("figtree.yml", 11, 9), true, 1.11}, 171 Bool1: BoolOption{tSrc("figtree.yml", 12, 8), true, true}, 172 } 173 174 fig := newFigTreeFromEnv() 175 err := fig.LoadAllConfigs("figtree.yml", &opts) 176 assert.NoError(t, err) 177 assert.Exactly(t, expected, opts) 178 } 179 180 func TestOptionsCorrupt(t *testing.T) { 181 opts := TestOptions{} 182 require.NoError(t, os.Chdir("d1")) 183 t.Cleanup(func() { 184 _ = os.Chdir("..") 185 }) 186 187 fig := newFigTreeFromEnv() 188 err := fig.LoadAllConfigs("corrupt.yml", &opts) 189 assert.NotNil(t, err) 190 } 191 192 func TestBuiltinLoadConfigD3(t *testing.T) { 193 opts := TestBuiltin{} 194 require.NoError(t, os.Chdir("d1/d2/d3")) 195 t.Cleanup(func() { 196 _ = os.Chdir("../../..") 197 }) 198 199 arr1 := []string{} 200 arr1 = append(arr1, "d3arr1val1") 201 arr1 = append(arr1, "d3arr1val2") 202 arr1 = append(arr1, "dupval") 203 arr1 = append(arr1, "211") 204 arr1 = append(arr1, "d2arr1val2") 205 arr1 = append(arr1, "d1arr1val1") 206 arr1 = append(arr1, "d1arr1val2") 207 208 expected := TestBuiltin{ 209 String1: "d3str1val1", 210 LeaveEmpty: "", 211 Array1: arr1, 212 Map1: map[string]string{ 213 "key0": "d1map1val0", 214 "key1": "211", 215 "key2": "d3map1val2", 216 "key3": "d3map1val3", 217 "dup": "d3dupval", 218 }, 219 Int1: 333, 220 Float1: 3.33, 221 Bool1: true, 222 } 223 224 fig := newFigTreeFromEnv() 225 err := fig.LoadAllConfigs("figtree.yml", &opts) 226 assert.NoError(t, err) 227 assert.Exactly(t, expected, opts) 228 } 229 230 func TestBuiltinLoadConfigD2(t *testing.T) { 231 opts := TestBuiltin{} 232 require.NoError(t, os.Chdir("d1/d2")) 233 t.Cleanup(func() { 234 _ = os.Chdir("../..") 235 }) 236 237 arr1 := []string{} 238 arr1 = append(arr1, "211") 239 arr1 = append(arr1, "d2arr1val2") 240 arr1 = append(arr1, "dupval") 241 arr1 = append(arr1, "d1arr1val1") 242 arr1 = append(arr1, "d1arr1val2") 243 244 expected := TestBuiltin{ 245 String1: "d2str1val1", 246 LeaveEmpty: "", 247 Array1: arr1, 248 Map1: map[string]string{ 249 "key0": "d1map1val0", 250 "key1": "211", 251 "key2": "d2map1val2", 252 "dup": "d2dupval", 253 }, 254 Int1: 222, 255 Float1: 2.22, 256 // note this will be true from d1/figtree.yml since the 257 // d1/d2/figtree.yml set it to false which is a zero value 258 Bool1: true, 259 } 260 261 fig := newFigTreeFromEnv() 262 err := fig.LoadAllConfigs("figtree.yml", &opts) 263 assert.NoError(t, err) 264 assert.Exactly(t, expected, opts) 265 } 266 267 func TestBuiltinLoadConfigD1(t *testing.T) { 268 opts := TestBuiltin{} 269 require.NoError(t, os.Chdir("d1")) 270 t.Cleanup(func() { 271 _ = os.Chdir("..") 272 }) 273 274 arr1 := []string{} 275 arr1 = append(arr1, "d1arr1val1") 276 arr1 = append(arr1, "d1arr1val2") 277 arr1 = append(arr1, "dupval") 278 279 expected := TestBuiltin{ 280 String1: "d1str1val1", 281 LeaveEmpty: "", 282 Array1: arr1, 283 Map1: map[string]string{ 284 "key0": "d1map1val0", 285 "key1": "d1map1val1", 286 "dup": "d1dupval", 287 }, 288 Int1: 111, 289 Float1: 1.11, 290 Bool1: true, 291 } 292 293 fig := newFigTreeFromEnv() 294 err := fig.LoadAllConfigs("figtree.yml", &opts) 295 assert.NoError(t, err) 296 assert.Exactly(t, expected, opts) 297 } 298 299 func TestBuiltinCorrupt(t *testing.T) { 300 opts := TestBuiltin{} 301 require.NoError(t, os.Chdir("d1")) 302 t.Cleanup(func() { 303 _ = os.Chdir("..") 304 }) 305 306 fig := newFigTreeFromEnv() 307 err := fig.LoadAllConfigs("corrupt.yml", &opts) 308 assert.NotNil(t, err) 309 } 310 311 func TestOptionsLoadConfigDefaults(t *testing.T) { 312 opts := TestOptions{ 313 String1: NewStringOption("defaultVal1"), 314 LeaveEmpty: NewStringOption("emptyVal1"), 315 Int1: NewIntOption(999), 316 Float1: NewFloat32Option(9.99), 317 Bool1: NewBoolOption(true), 318 } 319 require.NoError(t, os.Chdir("d1/d2")) 320 t.Cleanup(func() { 321 _ = os.Chdir("../..") 322 }) 323 324 arr1 := []StringOption{} 325 arr1 = append(arr1, StringOption{tSrc("figtree.yml", 3, 5), true, "211"}) 326 arr1 = append(arr1, StringOption{tSrc("figtree.yml", 4, 5), true, "d2arr1val2"}) 327 arr1 = append(arr1, StringOption{tSrc("figtree.yml", 5, 5), true, "dupval"}) 328 arr1 = append(arr1, StringOption{tSrc("../figtree.yml", 3, 5), true, "d1arr1val1"}) 329 arr1 = append(arr1, StringOption{tSrc("../figtree.yml", 4, 5), true, "d1arr1val2"}) 330 331 expected := TestOptions{ 332 String1: StringOption{tSrc("figtree.yml", 1, 7), true, "d2str1val1"}, 333 LeaveEmpty: StringOption{NewSource("default"), true, "emptyVal1"}, 334 Array1: arr1, 335 Map1: map[string]StringOption{ 336 "key0": {tSrc("../figtree.yml", 7, 9), true, "d1map1val0"}, 337 "key1": {tSrc("figtree.yml", 7, 9), true, "211"}, 338 "key2": {tSrc("figtree.yml", 8, 9), true, "d2map1val2"}, 339 "dup": {tSrc("figtree.yml", 9, 9), true, "d2dupval"}, 340 }, 341 Int1: IntOption{tSrc("figtree.yml", 10, 7), true, 222}, 342 Float1: Float32Option{tSrc("figtree.yml", 11, 9), true, 2.22}, 343 Bool1: BoolOption{tSrc("figtree.yml", 12, 8), true, false}, 344 } 345 346 fig := newFigTreeFromEnv() 347 err := fig.LoadAllConfigs("figtree.yml", &opts) 348 assert.NoError(t, err) 349 require.Exactly(t, expected, opts) 350 } 351 352 func TestMergeMapsWithNull(t *testing.T) { 353 dest := map[string]interface{}{ 354 "requires": map[string]interface{}{ 355 "pkgA": nil, 356 "pkgB": ">1.2.3", 357 }, 358 } 359 360 src := map[string]interface{}{ 361 "requires": map[string]interface{}{ 362 "pkgC": "<1.2.3", 363 "pkgD": nil, 364 }, 365 } 366 367 err := Merge(dest, src) 368 require.NoError(t, err) 369 370 expected := map[string]interface{}{ 371 "requires": map[string]interface{}{ 372 "pkgA": nil, 373 "pkgB": ">1.2.3", 374 "pkgC": "<1.2.3", 375 "pkgD": nil, 376 }, 377 } 378 assert.Equal(t, expected, dest) 379 } 380 381 func TestMergeMapsIntoStructWithNull(t *testing.T) { 382 src1 := map[string]interface{}{ 383 "requires": map[string]interface{}{ 384 "pkgA": nil, 385 "pkgB": ">1.2.3", 386 }, 387 } 388 389 src2 := map[string]interface{}{ 390 "requires": map[string]interface{}{ 391 "pkgC": "<1.2.3", 392 "pkgD": nil, 393 }, 394 } 395 396 dest := MakeMergeStruct(src1, src2) 397 err := Merge(dest, src1) 398 require.NoError(t, err) 399 err = Merge(dest, src2) 400 require.NoError(t, err) 401 402 expected := &struct { 403 Requires struct { 404 PkgA interface{} `json:"pkgA" yaml:"pkgA"` 405 PkgB string `json:"pkgB" yaml:"pkgB"` 406 PkgC string `json:"pkgC" yaml:"pkgC"` 407 PkgD interface{} `json:"pkgD" yaml:"pkgD"` 408 } `json:"requires" yaml:"requires"` 409 }{ 410 struct { 411 PkgA interface{} `json:"pkgA" yaml:"pkgA"` 412 PkgB string `json:"pkgB" yaml:"pkgB"` 413 PkgC string `json:"pkgC" yaml:"pkgC"` 414 PkgD interface{} `json:"pkgD" yaml:"pkgD"` 415 }{ 416 PkgA: nil, 417 PkgB: ">1.2.3", 418 PkgC: "<1.2.3", 419 PkgD: nil, 420 }, 421 } 422 assert.Equal(t, expected, dest) 423 } 424 425 func TestMergeStringIntoStringOption(t *testing.T) { 426 src1 := struct { 427 Value StringOption 428 }{} 429 430 src2 := struct { 431 Value string 432 }{"val1"} 433 434 dest := MakeMergeStruct(src1, src2) 435 436 err := Merge(dest, src1) 437 require.NoError(t, err) 438 err = Merge(dest, src2) 439 require.NoError(t, err) 440 441 expected := &struct { 442 Value StringOption 443 }{StringOption{NewSource("merge"), true, "val1"}} 444 assert.Equal(t, expected, dest) 445 } 446 447 func TestMergeStringOptions(t *testing.T) { 448 src1 := struct { 449 Value StringOption 450 }{} 451 452 src2 := struct { 453 Value StringOption 454 }{NewStringOption("val1")} 455 456 dest := MakeMergeStruct(src1, src2) 457 458 err := Merge(dest, src1) 459 require.NoError(t, err) 460 err = Merge(dest, src2) 461 require.NoError(t, err) 462 463 expected := &struct { 464 Value StringOption 465 }{StringOption{NewSource("default"), true, "val1"}} 466 assert.Equal(t, expected, dest) 467 } 468 469 func TestMergeMapStringIntoStringOption(t *testing.T) { 470 src1 := map[string]interface{}{ 471 "map": MapStringOption{}, 472 } 473 474 src2 := map[string]interface{}{ 475 "map": MapStringOption{ 476 "key": NewStringOption("val1"), 477 }, 478 } 479 dest := MakeMergeStruct(src1, src2) 480 481 err := Merge(dest, src1) 482 require.NoError(t, err) 483 err = Merge(dest, src2) 484 require.NoError(t, err) 485 486 expected := &struct { 487 Map struct { 488 Key StringOption `json:"key" yaml:"key"` 489 } `json:"map" yaml:"map"` 490 }{ 491 Map: struct { 492 Key StringOption `json:"key" yaml:"key"` 493 }{StringOption{NewSource("default"), true, "val1"}}, 494 } 495 assert.Equal(t, expected, dest) 496 } 497 498 func TestMergeMapStringOptions(t *testing.T) { 499 src1 := struct { 500 Value StringOption 501 }{} 502 503 src2 := struct { 504 Value StringOption 505 }{NewStringOption("val1")} 506 507 dest := MakeMergeStruct(src1, src2) 508 509 err := Merge(dest, src1) 510 require.NoError(t, err) 511 err = Merge(dest, src2) 512 require.NoError(t, err) 513 514 expected := &struct { 515 Value StringOption 516 }{StringOption{NewSource("default"), true, "val1"}} 517 assert.Equal(t, expected, dest) 518 } 519 520 func TestMergeMapWithStruct(t *testing.T) { 521 dest := map[string]interface{}{ 522 "mapkey": "mapval1", 523 "map": map[string]interface{}{ 524 "mapkey": "mapval2", 525 "nullkey": nil, 526 }, 527 } 528 529 src := struct { 530 StructField string 531 Map struct { 532 StructField string 533 } 534 }{ 535 StructField: "field1", 536 Map: struct { 537 StructField string 538 }{ 539 StructField: "field2", 540 }, 541 } 542 543 m := NewMerger() 544 changed, err := m.mergeStructs(reflect.ValueOf(&dest), newMergeSource(reflect.ValueOf(&src)), false) 545 require.NoError(t, err) 546 require.True(t, changed) 547 548 expected := map[string]interface{}{ 549 "mapkey": "mapval1", 550 "struct-field": "field1", 551 "map": map[string]interface{}{ 552 "mapkey": "mapval2", 553 "nullkey": nil, 554 "struct-field": "field2", 555 }, 556 } 557 assert.Equal(t, expected, dest) 558 } 559 560 func TestMergeStructWithMap(t *testing.T) { 561 dest := struct { 562 StructField string 563 Mapkey string 564 Map struct { 565 StructField string 566 Mapkey string 567 } 568 }{ 569 StructField: "field1", 570 Map: struct { 571 StructField string 572 Mapkey string 573 }{ 574 StructField: "field2", 575 }, 576 } 577 578 src := map[string]interface{}{ 579 "mapkey": "mapval1", 580 "map": map[string]interface{}{ 581 "mapkey": "mapval2", 582 "nullkey": nil, 583 }, 584 } 585 586 merged := MakeMergeStruct(&dest, &src) 587 err := Merge(merged, &dest) 588 require.NoError(t, err) 589 err = Merge(merged, &src) 590 require.NoError(t, err) 591 592 expected := struct { 593 Map struct { 594 Mapkey string 595 Nullkey interface{} `json:"nullkey" yaml:"nullkey"` 596 StructField string 597 } 598 Mapkey string 599 StructField string 600 }{ 601 Map: struct { 602 Mapkey string 603 Nullkey interface{} `json:"nullkey" yaml:"nullkey"` 604 StructField string 605 }{ 606 Mapkey: "mapval2", 607 StructField: "field2", 608 }, 609 Mapkey: "mapval1", 610 StructField: "field1", 611 } 612 assert.Equal(t, &expected, merged) 613 } 614 615 func TestMergeStructWithMapArbitraryNaming(t *testing.T) { 616 // Go struct field names should not matter 617 dest := struct { 618 MyStructField string `yaml:"struct-field"` 619 MyMapkey string `yaml:"mapkey"` 620 MyMap struct { 621 MyStructField string `yaml:"struct-field"` 622 MyMapkey string `yaml:"mapkey"` 623 } `yaml:"map"` 624 }{ 625 MyStructField: "field1", 626 MyMap: struct { 627 MyStructField string `yaml:"struct-field"` 628 MyMapkey string `yaml:"mapkey"` 629 }{ 630 MyStructField: "field2", 631 }, 632 } 633 634 src := map[string]interface{}{ 635 "mapkey": "mapval1", 636 "map": map[string]interface{}{ 637 "mapkey": "mapval2", 638 "nullkey": nil, 639 }, 640 } 641 642 merged := MakeMergeStruct(&dest, &src) 643 err := Merge(merged, &dest) 644 require.NoError(t, err) 645 err = Merge(merged, &src) 646 require.NoError(t, err) 647 648 expected := struct { 649 Map struct { 650 Mapkey string `yaml:"mapkey"` 651 Nullkey interface{} `json:"nullkey" yaml:"nullkey"` 652 StructField string `yaml:"struct-field"` 653 } `yaml:"map"` 654 Mapkey string `yaml:"mapkey"` 655 StructField string `yaml:"struct-field"` 656 }{ 657 Map: struct { 658 Mapkey string `yaml:"mapkey"` 659 Nullkey interface{} `json:"nullkey" yaml:"nullkey"` 660 StructField string `yaml:"struct-field"` 661 }{ 662 Mapkey: "mapval2", 663 StructField: "field2", 664 }, 665 Mapkey: "mapval1", 666 StructField: "field1", 667 } 668 assert.Equal(t, &expected, merged) 669 } 670 671 func TestMergeStructUsingOptionsWithMap(t *testing.T) { 672 dest := struct { 673 Bool BoolOption 674 Byte ByteOption 675 Float32 Float32Option 676 Float64 Float64Option 677 Int16 Int16Option 678 Int32 Int32Option 679 Int64 Int64Option 680 Int8 Int8Option 681 Int IntOption 682 Rune RuneOption 683 String StringOption 684 Uint16 Uint16Option 685 Uint32 Uint32Option 686 Uint64 Uint64Option 687 Uint8 Uint8Option 688 Uint UintOption 689 }{} 690 691 src := map[string]interface{}{ 692 "bool": true, 693 "byte": byte(10), 694 "float-32": float32(1.23), 695 "float-64": float64(2.34), 696 "int-16": int16(123), 697 "int-32": int32(234), 698 "int-64": int64(345), 699 "int-8": int8(127), 700 "int": int(456), 701 "rune": rune('a'), 702 "string": "stringval", 703 "uint-16": uint16(123), 704 "uint-32": uint32(234), 705 "uint-64": uint64(345), 706 "uint-8": uint8(255), 707 "uint": uint(456), 708 } 709 710 err := Merge(&dest, &src) 711 require.NoError(t, err) 712 713 expected := struct { 714 Bool BoolOption 715 Byte ByteOption 716 Float32 Float32Option 717 Float64 Float64Option 718 Int16 Int16Option 719 Int32 Int32Option 720 Int64 Int64Option 721 Int8 Int8Option 722 Int IntOption 723 Rune RuneOption 724 String StringOption 725 Uint16 Uint16Option 726 Uint32 Uint32Option 727 Uint64 Uint64Option 728 Uint8 Uint8Option 729 Uint UintOption 730 }{ 731 Bool: BoolOption{NewSource("merge"), true, true}, 732 Byte: ByteOption{NewSource("merge"), true, byte(10)}, 733 Float32: Float32Option{NewSource("merge"), true, float32(1.23)}, 734 Float64: Float64Option{NewSource("merge"), true, float64(2.34)}, 735 Int16: Int16Option{NewSource("merge"), true, int16(123)}, 736 Int32: Int32Option{NewSource("merge"), true, int32(234)}, 737 Int64: Int64Option{NewSource("merge"), true, int64(345)}, 738 Int8: Int8Option{NewSource("merge"), true, int8(127)}, 739 Int: IntOption{NewSource("merge"), true, int(456)}, 740 Rune: RuneOption{NewSource("merge"), true, rune('a')}, 741 String: StringOption{NewSource("merge"), true, "stringval"}, 742 Uint16: Uint16Option{NewSource("merge"), true, uint16(123)}, 743 Uint32: Uint32Option{NewSource("merge"), true, uint32(234)}, 744 Uint64: Uint64Option{NewSource("merge"), true, uint64(345)}, 745 Uint8: Uint8Option{NewSource("merge"), true, uint8(255)}, 746 Uint: UintOption{NewSource("merge"), true, uint(456)}, 747 } 748 assert.Equal(t, expected, dest) 749 } 750 751 func TestMergeMapWithStructUsingOptions(t *testing.T) { 752 dest := map[string]interface{}{ 753 "bool": false, 754 "byte": byte(0), 755 "float32": float32(0), 756 "float64": float64(0), 757 "int16": int16(0), 758 "int32": int32(0), 759 "int64": int64(0), 760 "int8": int8(0), 761 "int": int(0), 762 "rune": rune(0), 763 "string": "", 764 "uint16": uint16(0), 765 "uint32": uint32(0), 766 "uint64": uint64(0), 767 "uint8": uint8(0), 768 "uint": uint(0), 769 } 770 771 src := struct { 772 Bool BoolOption 773 Byte ByteOption 774 Float32 Float32Option `yaml:"float32"` 775 Float64 Float64Option `yaml:"float64"` 776 Int16 Int16Option `yaml:"int16"` 777 Int32 Int32Option `yaml:"int32"` 778 Int64 Int64Option `yaml:"int64"` 779 Int8 Int8Option `yaml:"int8"` 780 Int IntOption 781 Rune RuneOption 782 String StringOption 783 Uint16 Uint16Option `yaml:"uint16"` 784 Uint32 Uint32Option `yaml:"uint32"` 785 Uint64 Uint64Option `yaml:"uint64"` 786 Uint8 Uint8Option `yaml:"uint8"` 787 Uint UintOption 788 }{ 789 Bool: NewBoolOption(true), 790 Byte: NewByteOption(10), 791 Float32: NewFloat32Option(1.23), 792 Float64: NewFloat64Option(2.34), 793 Int16: NewInt16Option(123), 794 Int32: NewInt32Option(234), 795 Int64: NewInt64Option(345), 796 Int8: NewInt8Option(127), 797 Int: NewIntOption(456), 798 Rune: NewRuneOption('a'), 799 String: NewStringOption("stringval"), 800 Uint16: NewUint16Option(123), 801 Uint32: NewUint32Option(234), 802 Uint64: NewUint64Option(345), 803 Uint8: NewUint8Option(255), 804 Uint: NewUintOption(456), 805 } 806 807 err := Merge(&dest, &src) 808 require.NoError(t, err) 809 810 expected := map[string]interface{}{ 811 "bool": true, 812 "byte": byte(10), 813 "float32": float32(1.23), 814 "float64": float64(2.34), 815 "int16": int16(123), 816 "int32": int32(234), 817 "int64": int64(345), 818 "int8": int8(127), 819 "int": int(456), 820 "rune": rune('a'), 821 "string": "stringval", 822 "uint16": uint16(123), 823 "uint32": uint32(234), 824 "uint64": uint64(345), 825 "uint8": uint8(255), 826 "uint": uint(456), 827 } 828 assert.Equal(t, expected, dest) 829 } 830 831 func TestMergeStructUsingListOptionsWithMap(t *testing.T) { 832 dest := struct { 833 Strings ListStringOption 834 }{ 835 Strings: ListStringOption{ 836 NewStringOption("abc"), 837 }, 838 } 839 840 src := map[string]interface{}{ 841 "strings": []string{ 842 "abc", 843 "def", 844 }, 845 } 846 847 err := Merge(&dest, &src) 848 require.NoError(t, err) 849 850 expected := struct { 851 Strings ListStringOption 852 }{ 853 ListStringOption{ 854 StringOption{NewSource("default"), true, "abc"}, 855 StringOption{NewSource("merge"), true, "def"}, 856 }, 857 } 858 assert.Equal(t, expected, dest) 859 } 860 861 func TestMergeMapWithStructUsingListOptions(t *testing.T) { 862 dest := map[string]interface{}{ 863 "strings": []string{"abc"}, 864 } 865 866 src := struct { 867 Strings ListStringOption 868 }{ 869 Strings: ListStringOption{ 870 NewStringOption("abc"), 871 NewStringOption("def"), 872 }, 873 } 874 875 err := Merge(&dest, &src) 876 require.NoError(t, err) 877 878 expected := map[string]interface{}{ 879 "strings": []string{"abc", "def"}, 880 } 881 assert.Equal(t, expected, dest) 882 } 883 884 func TestMergeStructWithListUsingListOptions(t *testing.T) { 885 dest := struct { 886 Property []interface{} 887 }{ 888 Property: []interface{}{ 889 "abc", 890 }, 891 } 892 893 src := struct { 894 Property ListStringOption 895 }{ 896 Property: ListStringOption{ 897 NewStringOption("abc"), 898 NewStringOption("def"), 899 }, 900 } 901 902 err := Merge(&dest, &src) 903 require.NoError(t, err) 904 905 expected := struct { 906 Property []interface{} 907 }{ 908 Property: []interface{}{ 909 "abc", 910 NewStringOption("def"), 911 }, 912 } 913 assert.Equal(t, expected, dest) 914 } 915 916 func TestMergeStructUsingMapOptionsWithMap(t *testing.T) { 917 dest := struct { 918 Strings MapStringOption 919 }{} 920 921 src := map[string]interface{}{ 922 "strings": map[string]interface{}{ 923 "key1": "val1", 924 "key2": "val2", 925 }, 926 } 927 928 err := Merge(&dest, &src) 929 require.NoError(t, err) 930 931 expected := struct { 932 Strings MapStringOption 933 }{ 934 Strings: MapStringOption{ 935 "key1": StringOption{NewSource("merge"), true, "val1"}, 936 "key2": StringOption{NewSource("merge"), true, "val2"}, 937 }, 938 } 939 assert.Equal(t, expected, dest) 940 } 941 942 func TestMergeMapWithStructUsingMapOptions(t *testing.T) { 943 dest := map[string]interface{}{ 944 "strings": map[string]string{}, 945 } 946 947 src := struct { 948 Strings MapStringOption 949 }{ 950 Strings: MapStringOption{ 951 "key1": NewStringOption("val1"), 952 "key2": NewStringOption("val2"), 953 }, 954 } 955 956 err := Merge(&dest, &src) 957 require.NoError(t, err) 958 959 expected := map[string]interface{}{ 960 "strings": map[string]string{ 961 "key1": "val1", 962 "key2": "val2", 963 }, 964 } 965 assert.Equal(t, expected, dest) 966 } 967 968 func TestMergeStructsWithSrcEmbedded(t *testing.T) { 969 dest := struct { 970 FieldName string 971 }{} 972 973 type embedded struct { 974 FieldName string 975 } 976 977 src := struct { 978 embedded 979 }{ 980 embedded: embedded{ 981 FieldName: "field1", 982 }, 983 } 984 985 m := NewMerger() 986 changed, err := m.mergeStructs(reflect.ValueOf(&dest), newMergeSource(reflect.ValueOf(&src)), false) 987 require.NoError(t, err) 988 require.True(t, changed) 989 990 expected := struct { 991 FieldName string 992 }{ 993 FieldName: "field1", 994 } 995 assert.Equal(t, expected, dest) 996 } 997 998 func TestMergeStructsWithDestEmbedded(t *testing.T) { 999 type embedded struct { 1000 FieldName string 1001 } 1002 1003 dest := struct { 1004 embedded 1005 }{} 1006 1007 src := struct { 1008 FieldName string 1009 }{ 1010 FieldName: "field1", 1011 } 1012 1013 m := NewMerger() 1014 changed, err := m.mergeStructs(reflect.ValueOf(&dest), newMergeSource(reflect.ValueOf(&src)), false) 1015 require.NoError(t, err) 1016 require.True(t, changed) 1017 1018 expected := struct { 1019 embedded 1020 }{ 1021 embedded: embedded{ 1022 FieldName: "field1", 1023 }, 1024 } 1025 assert.Equal(t, expected, dest) 1026 } 1027 1028 func TestMakeMergeStruct(t *testing.T) { 1029 input := map[string]interface{}{ 1030 "mapkey": "mapval1", 1031 "map": map[string]interface{}{ 1032 "mapkey": "mapval2", 1033 }, 1034 "nilmap": nil, 1035 } 1036 1037 got := MakeMergeStruct(input) 1038 1039 err := Merge(got, &input) 1040 require.NoError(t, err) 1041 1042 assert.Equal(t, input["mapkey"], reflect.ValueOf(got).Elem().FieldByName("Mapkey").Interface()) 1043 assert.Equal(t, struct { 1044 Mapkey string `json:"mapkey" yaml:"mapkey"` 1045 }{"mapval2"}, reflect.ValueOf(got).Elem().FieldByName("Map").Interface()) 1046 assert.Equal(t, input["map"].(map[string]interface{})["mapkey"], reflect.ValueOf(got).Elem().FieldByName("Map").FieldByName("Mapkey").Interface()) 1047 } 1048 1049 func TestMakeMergeStructWithDups(t *testing.T) { 1050 input := map[string]interface{}{ 1051 "mapkey": "mapval1", 1052 } 1053 1054 s := struct { 1055 Mapkey string 1056 }{ 1057 Mapkey: "mapval2", 1058 } 1059 1060 got := MakeMergeStruct(input, s) 1061 err := Merge(got, &input) 1062 require.NoError(t, err) 1063 1064 assert.Equal(t, &struct { 1065 Mapkey string `json:"mapkey" yaml:"mapkey"` 1066 }{"mapval1"}, got) 1067 1068 got = MakeMergeStruct(s, input) 1069 err = Merge(got, &s) 1070 require.NoError(t, err) 1071 1072 assert.Equal(t, &struct{ Mapkey string }{"mapval2"}, got) 1073 } 1074 1075 func TestMakeMergeStructWithInline(t *testing.T) { 1076 type Inner struct { 1077 InnerString StringOption `json:"inner-string" yaml:"inner-string"` 1078 } 1079 1080 outer := struct { 1081 Inner `figtree:",inline"` 1082 OuterString string 1083 }{} 1084 1085 other := struct { 1086 InnerString string 1087 OtherString string 1088 }{} 1089 1090 got := MakeMergeStruct(outer, other) 1091 assert.IsType(t, (*struct { 1092 InnerString StringOption `json:"inner-string" yaml:"inner-string"` 1093 OtherString string 1094 OuterString string 1095 })(nil), got) 1096 1097 otherMap := map[string]interface{}{ 1098 "inner-string": "inner", 1099 "other-string": "other", 1100 } 1101 1102 got = MakeMergeStruct(outer, otherMap) 1103 assert.IsType(t, (*struct { 1104 InnerString StringOption `json:"inner-string" yaml:"inner-string"` 1105 OtherString string `json:"other-string" yaml:"other-string"` 1106 OuterString string 1107 })(nil), got) 1108 } 1109 1110 func TestMakeMergeStructWithYaml(t *testing.T) { 1111 input := "foo-bar: foo-val\n" 1112 data := map[string]interface{}{} 1113 err := yaml.Unmarshal([]byte(input), &data) 1114 assert.NoError(t, err) 1115 1116 // turn map data into a struct 1117 got := MakeMergeStruct(data) 1118 // then assign the data back into that struct 1119 err = Merge(got, data) 1120 require.NoError(t, err) 1121 1122 expected := &struct { 1123 FooBar string `json:"foo-bar" yaml:"foo-bar"` 1124 }{ 1125 "foo-val", 1126 } 1127 assert.Equal(t, expected, got) 1128 1129 // make sure the new structure serializes back to the original document 1130 output, err := yaml.Marshal(expected) 1131 assert.NoError(t, err) 1132 assert.Equal(t, input, string(output)) 1133 } 1134 1135 func TestMakeMergeStructWithJson(t *testing.T) { 1136 input := `{"foo-bar":"foo-val"}` 1137 data := map[string]interface{}{} 1138 err := json.Unmarshal([]byte(input), &data) 1139 assert.NoError(t, err) 1140 1141 // turn map data into a struct 1142 got := MakeMergeStruct(data) 1143 // then assign the data back into that struct 1144 err = Merge(got, data) 1145 require.NoError(t, err) 1146 1147 expected := &struct { 1148 FooBar string `json:"foo-bar" yaml:"foo-bar"` 1149 }{ 1150 "foo-val", 1151 } 1152 assert.Equal(t, expected, got) 1153 1154 // make sure the new structure serializes back to the original document 1155 output, err := json.Marshal(expected) 1156 assert.NoError(t, err) 1157 assert.Equal(t, input, string(output)) 1158 } 1159 1160 func TestMergeWithZeros(t *testing.T) { 1161 var zero interface{} 1162 tests := []struct { 1163 info info 1164 dest map[string]interface{} 1165 src map[string]interface{} 1166 want map[string]interface{} 1167 }{ 1168 { 1169 info: info{"zero to nil", line()}, 1170 dest: map[string]interface{}{}, 1171 src: map[string]interface{}{ 1172 "value": zero, 1173 }, 1174 want: map[string]interface{}{ 1175 "value": zero, 1176 }, 1177 }, 1178 { 1179 info: info{"zero to zero", line()}, 1180 dest: map[string]interface{}{ 1181 "value": zero, 1182 }, 1183 src: map[string]interface{}{ 1184 "value": zero, 1185 }, 1186 want: map[string]interface{}{ 1187 "value": zero, 1188 }, 1189 }, 1190 { 1191 info: info{"zero to StringOption", line()}, 1192 dest: map[string]interface{}{ 1193 "value": StringOption{}, 1194 }, 1195 src: map[string]interface{}{ 1196 "value": zero, 1197 }, 1198 want: map[string]interface{}{ 1199 "value": StringOption{}, 1200 }, 1201 }, 1202 { 1203 info: info{"StringOption to zero", line()}, 1204 dest: map[string]interface{}{ 1205 "value": zero, 1206 }, 1207 src: map[string]interface{}{ 1208 "value": StringOption{}, 1209 }, 1210 want: map[string]interface{}{ 1211 "value": StringOption{}, 1212 }, 1213 }, 1214 { 1215 info: info{"list zero to nil", line()}, 1216 dest: map[string]interface{}{ 1217 "value": nil, 1218 }, 1219 src: map[string]interface{}{ 1220 "value": []interface{}{zero}, 1221 }, 1222 want: map[string]interface{}{ 1223 "value": []interface{}{zero}, 1224 }, 1225 }, 1226 { 1227 info: info{"list zero to empty", line()}, 1228 dest: map[string]interface{}{ 1229 "value": []interface{}{}, 1230 }, 1231 src: map[string]interface{}{ 1232 "value": []interface{}{zero}, 1233 }, 1234 want: map[string]interface{}{ 1235 "value": []interface{}{}, 1236 }, 1237 }, 1238 { 1239 info: info{"list zero to StringOption", line()}, 1240 dest: map[string]interface{}{ 1241 "value": []interface{}{StringOption{}}, 1242 }, 1243 src: map[string]interface{}{ 1244 "value": []interface{}{zero}, 1245 }, 1246 want: map[string]interface{}{ 1247 "value": []interface{}{StringOption{}}, 1248 }, 1249 }, 1250 { 1251 info: info{"list StringOption to zero", line()}, 1252 dest: map[string]interface{}{ 1253 "value": []interface{}{zero}, 1254 }, 1255 src: map[string]interface{}{ 1256 "value": []interface{}{StringOption{}}, 1257 }, 1258 want: map[string]interface{}{ 1259 "value": []interface{}{zero}, 1260 }, 1261 }, 1262 { 1263 info: info{"list StringOption to empty", line()}, 1264 dest: map[string]interface{}{ 1265 "value": []interface{}{}, 1266 }, 1267 src: map[string]interface{}{ 1268 "value": []interface{}{StringOption{}}, 1269 }, 1270 want: map[string]interface{}{ 1271 "value": []interface{}{}, 1272 }, 1273 }, 1274 { 1275 info: info{"zero to ListStringOption", line()}, 1276 dest: map[string]interface{}{ 1277 "value": ListStringOption{StringOption{}}, 1278 }, 1279 src: map[string]interface{}{ 1280 "value": []interface{}{zero}, 1281 }, 1282 want: map[string]interface{}{ 1283 "value": ListStringOption{StringOption{}}, 1284 }, 1285 }, 1286 { 1287 info: info{"ListStringOption to zero", line()}, 1288 dest: map[string]interface{}{ 1289 "value": []interface{}{zero}, 1290 }, 1291 src: map[string]interface{}{ 1292 "value": ListStringOption{StringOption{}}, 1293 }, 1294 want: map[string]interface{}{ 1295 "value": []interface{}{zero}, 1296 }, 1297 }, 1298 { 1299 info: info{"map zero to nil", line()}, 1300 dest: map[string]interface{}{ 1301 "value": nil, 1302 }, 1303 src: map[string]interface{}{ 1304 "value": map[string]interface{}{ 1305 "key": zero, 1306 }, 1307 }, 1308 want: map[string]interface{}{ 1309 "value": map[string]interface{}{ 1310 "key": zero, 1311 }, 1312 }, 1313 }, 1314 { 1315 info: info{"map zero to empty", line()}, 1316 dest: map[string]interface{}{ 1317 "value": map[string]interface{}{}, 1318 }, 1319 src: map[string]interface{}{ 1320 "value": map[string]interface{}{ 1321 "key": zero, 1322 }, 1323 }, 1324 want: map[string]interface{}{ 1325 "value": map[string]interface{}{ 1326 "key": zero, 1327 }, 1328 }, 1329 }, 1330 { 1331 info: info{"MapStringOption to zero", line()}, 1332 dest: map[string]interface{}{ 1333 "value": zero, 1334 }, 1335 src: map[string]interface{}{ 1336 "value": MapStringOption{ 1337 "key": StringOption{}, 1338 }, 1339 }, 1340 want: map[string]interface{}{ 1341 "value": MapStringOption{ 1342 "key": StringOption{}, 1343 }, 1344 }, 1345 }, 1346 { 1347 info: info{"map zero to StringOption", line()}, 1348 dest: map[string]interface{}{ 1349 "value": MapStringOption{ 1350 "key": StringOption{}, 1351 }, 1352 }, 1353 src: map[string]interface{}{ 1354 "value": zero, 1355 }, 1356 want: map[string]interface{}{ 1357 "value": MapStringOption{ 1358 "key": StringOption{}, 1359 }, 1360 }, 1361 }, 1362 { 1363 info: info{"map zero key to StringOption", line()}, 1364 dest: map[string]interface{}{ 1365 "value": MapStringOption{ 1366 "key": StringOption{}, 1367 }, 1368 }, 1369 src: map[string]interface{}{ 1370 "value": map[string]interface{}{ 1371 "key": zero, 1372 }, 1373 }, 1374 want: map[string]interface{}{ 1375 "value": MapStringOption{ 1376 "key": StringOption{}, 1377 }, 1378 }, 1379 }, 1380 { 1381 info: info{"map StringOption to zero key", line()}, 1382 dest: map[string]interface{}{ 1383 "value": map[string]interface{}{ 1384 "key": zero, 1385 }, 1386 }, 1387 src: map[string]interface{}{ 1388 "value": MapStringOption{ 1389 "key": StringOption{}, 1390 }, 1391 }, 1392 want: map[string]interface{}{ 1393 "value": map[string]interface{}{ 1394 "key": StringOption{}, 1395 }, 1396 }, 1397 }, 1398 } 1399 1400 for _, tt := range tests { 1401 require.True(t, 1402 t.Run(tt.info.name, func(t *testing.T) { 1403 // assert.NotPanics(t, func() { 1404 Log.Debugf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") 1405 Log.Debugf("%s", tt.info.name) 1406 Log.Debugf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") 1407 err := Merge(&tt.dest, &tt.src) 1408 require.NoError(t, err) 1409 // }) 1410 assert.Equal(t, tt.want, tt.dest, tt.info.line) 1411 1412 got := MakeMergeStruct(tt.dest) 1413 err = Merge(got, tt.dest) 1414 require.NoError(t, err) 1415 err = Merge(got, tt.src) 1416 require.NoError(t, err) 1417 1418 expected := MakeMergeStruct(tt.want) 1419 err = Merge(expected, tt.want) 1420 require.NoError(t, err) 1421 1422 assert.Equal(t, expected, got, tt.info.line) 1423 }), 1424 ) 1425 } 1426 } 1427 1428 func TestMergeStructsWithZeros(t *testing.T) { 1429 var zero interface{} 1430 tests := []struct { 1431 info info 1432 dest interface{} 1433 src interface{} 1434 want interface{} 1435 line string 1436 }{ 1437 { 1438 info: info{"bare nil", line()}, 1439 dest: struct { 1440 Value interface{} 1441 }{}, 1442 src: struct { 1443 Value interface{} 1444 }{zero}, 1445 want: struct { 1446 Value interface{} 1447 }{zero}, 1448 }, 1449 { 1450 info: info{"bare zero", line()}, 1451 dest: struct { 1452 Value interface{} 1453 }{zero}, 1454 src: struct { 1455 Value interface{} 1456 }{zero}, 1457 want: struct { 1458 Value interface{} 1459 }{zero}, 1460 }, 1461 { 1462 info: info{"bare StringOption", line()}, 1463 dest: struct { 1464 Value interface{} 1465 }{StringOption{}}, 1466 src: struct { 1467 Value interface{} 1468 }{StringOption{}}, 1469 want: struct { 1470 Value interface{} 1471 }{StringOption{}}, 1472 }, 1473 { 1474 info: info{"bare StringOptions to zero", line()}, 1475 dest: struct { 1476 Value interface{} 1477 }{zero}, 1478 src: struct { 1479 Value StringOption 1480 }{StringOption{}}, 1481 want: struct { 1482 Value interface{} 1483 }{zero}, 1484 }, 1485 { 1486 info: info{"list zero to nil", line()}, 1487 dest: struct { 1488 Value interface{} 1489 }{}, 1490 src: struct { 1491 Value interface{} 1492 }{[]interface{}{zero}}, 1493 want: struct { 1494 Value interface{} 1495 }{[]interface{}{zero}}, 1496 }, 1497 { 1498 info: info{"list zero to empty", line()}, 1499 dest: struct { 1500 Value interface{} 1501 }{[]interface{}{}}, 1502 src: struct { 1503 Value interface{} 1504 }{[]interface{}{zero}}, 1505 want: struct { 1506 Value interface{} 1507 }{[]interface{}{}}, 1508 }, 1509 { 1510 info: info{"list zero to StringOption", line()}, 1511 dest: struct { 1512 Value interface{} 1513 }{[]interface{}{StringOption{}}}, 1514 src: struct { 1515 Value interface{} 1516 }{[]interface{}{zero}}, 1517 want: struct { 1518 Value interface{} 1519 }{[]interface{}{StringOption{}}}, 1520 }, 1521 { 1522 info: info{"list StringOption to zero", line()}, 1523 dest: struct { 1524 Value interface{} 1525 }{[]interface{}{zero}}, 1526 src: struct { 1527 Value interface{} 1528 }{[]interface{}{StringOption{}}}, 1529 want: struct { 1530 Value interface{} 1531 }{[]interface{}{zero}}, 1532 }, 1533 { 1534 info: info{"list ListStringOption to empty list", line()}, 1535 line: line(), 1536 dest: struct { 1537 Value interface{} 1538 }{[]interface{}{}}, 1539 src: struct { 1540 Value ListStringOption 1541 }{ListStringOption{StringOption{}}}, 1542 want: struct { 1543 Value interface{} 1544 }{[]interface{}{}}, 1545 }, 1546 { 1547 info: info{"list zero list to ListStringOption", line()}, 1548 dest: struct { 1549 Value ListStringOption 1550 }{ListStringOption{StringOption{}}}, 1551 src: struct { 1552 Value interface{} 1553 }{[]interface{}{zero}}, 1554 want: struct { 1555 Value ListStringOption 1556 }{ListStringOption{StringOption{}}}, 1557 }, 1558 { 1559 info: info{"list ListStringOption to zero list", line()}, 1560 dest: struct { 1561 Value interface{} 1562 }{[]interface{}{zero}}, 1563 src: struct { 1564 Value ListStringOption 1565 }{ListStringOption{StringOption{}}}, 1566 want: struct { 1567 Value interface{} 1568 }{[]interface{}{zero}}, 1569 }, 1570 { 1571 info: info{"map zero to nil", line()}, 1572 dest: struct { 1573 Value interface{} 1574 }{}, 1575 src: struct { 1576 Value interface{} 1577 }{map[string]interface{}{"key": zero}}, 1578 want: struct { 1579 Value interface{} 1580 }{map[string]interface{}{"key": zero}}, 1581 }, 1582 { 1583 info: info{"map zero to empty", line()}, 1584 dest: struct { 1585 Value interface{} 1586 }{map[string]interface{}{}}, 1587 src: struct { 1588 Value interface{} 1589 }{map[string]interface{}{"key": zero}}, 1590 want: struct { 1591 Value interface{} 1592 }{map[string]interface{}{"key": zero}}, 1593 }, 1594 { 1595 info: info{"map StringOption to zero", line()}, 1596 dest: struct { 1597 Value interface{} 1598 }{zero}, 1599 src: struct { 1600 Value interface{} 1601 }{map[string]interface{}{"key": zero}}, 1602 want: struct { 1603 Value interface{} 1604 }{map[string]interface{}{"key": zero}}, 1605 }, 1606 { 1607 info: info{"MapStringOption StringOption to zero", line()}, 1608 dest: struct { 1609 Value interface{} 1610 }{zero}, 1611 src: struct { 1612 Value interface{} 1613 }{MapStringOption{ 1614 "key": StringOption{}, 1615 }}, 1616 want: struct { 1617 Value interface{} 1618 }{MapStringOption{ 1619 "key": StringOption{}, 1620 }}, 1621 }, 1622 { 1623 info: info{"zero to MapStringOption StringOption", line()}, 1624 dest: struct { 1625 Value interface{} 1626 }{MapStringOption{ 1627 "key": StringOption{}, 1628 }}, 1629 src: struct { 1630 Value interface{} 1631 }{zero}, 1632 want: struct { 1633 Value interface{} 1634 }{MapStringOption{ 1635 "key": StringOption{}, 1636 }}, 1637 }, 1638 { 1639 info: info{"map zero to MapStringOption StringOption", line()}, 1640 dest: struct { 1641 Value interface{} 1642 }{MapStringOption{ 1643 "key": StringOption{}, 1644 }}, 1645 src: struct { 1646 Value interface{} 1647 }{map[string]interface{}{ 1648 "key": zero, 1649 }}, 1650 want: struct { 1651 Value interface{} 1652 }{MapStringOption{ 1653 "key": StringOption{}, 1654 }}, 1655 }, 1656 } 1657 for _, tt := range tests { 1658 require.True(t, 1659 t.Run(tt.info.name, func(t *testing.T) { 1660 // assert.NotPanics(t, func() { 1661 Log.Debugf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") 1662 Log.Debugf("%s", tt.info.name) 1663 Log.Debugf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++") 1664 err := Merge(&tt.dest, &tt.src) 1665 require.NoError(t, err) 1666 // }) 1667 assert.Equal(t, tt.want, tt.dest, tt.info.line) 1668 1669 got := MakeMergeStruct(tt.dest) 1670 err = Merge(got, tt.dest) 1671 require.NoError(t, err) 1672 err = Merge(got, tt.src) 1673 require.NoError(t, err) 1674 1675 expected := MakeMergeStruct(tt.want) 1676 err = Merge(expected, tt.want) 1677 require.NoError(t, err) 1678 1679 assert.Equal(t, expected, got, tt.info.line) 1680 }), 1681 ) 1682 } 1683 } 1684 1685 func TestMergeStructsWithPreservedMaps(t *testing.T) { 1686 tests := []struct { 1687 info info 1688 src interface{} 1689 want interface{} 1690 merger *Merger 1691 }{ 1692 { 1693 info: info{"convert map to struct by default", line()}, 1694 src: map[string]interface{}{ 1695 "map": map[string]string{"key": "value"}, 1696 }, 1697 want: &struct { 1698 Map struct { 1699 Key string `json:"key" yaml:"key"` 1700 } `json:"map" yaml:"map"` 1701 }{}, 1702 merger: NewMerger(), 1703 }, { 1704 info: info{"preserve map when converting to struct", line()}, 1705 src: map[string]interface{}{ 1706 "map": map[string]string{"key": "value"}, 1707 "other": map[string]string{"key": "value"}, 1708 }, 1709 want: &struct { 1710 Map map[string]string `json:"map" yaml:"map"` 1711 Other struct { 1712 Key string `json:"key" yaml:"key"` 1713 } `json:"other" yaml:"other"` 1714 }{}, 1715 merger: NewMerger(PreserveMap("map")), 1716 }, 1717 } 1718 1719 for _, tt := range tests { 1720 require.True(t, 1721 t.Run(tt.info.name, func(t *testing.T) { 1722 got := tt.merger.MakeMergeStruct(tt.src) 1723 assert.Equal(t, tt.want, got) 1724 }), 1725 ) 1726 } 1727 } 1728 1729 func TestFigtreePreProcessor(t *testing.T) { 1730 var input yaml.Node 1731 err := yaml.Unmarshal([]byte(` 1732 bad-name: good-value 1733 good-name: bad-value 1734 ok-name: want-array 1735 `), &input) 1736 assert.NoError(t, err) 1737 1738 pp := func(node *yaml.Node) error { 1739 // rename "bad-name" key to "fixed-name" 1740 if keyNode, _ := walky.GetKeyValue(node, walky.NewStringNode("bad-name")); keyNode != nil { 1741 keyNode.Value = "fixed-name" 1742 } 1743 1744 // reset "bad-value" key to "fixed-value", under "good-name" key 1745 if valNode := walky.GetKey(node, "good-name"); valNode != nil { 1746 valNode.Value = "fixed-value" 1747 } 1748 1749 // migrate "ok-name" value from string to list of strings 1750 if keyNode, valNode := walky.GetKeyValue(node, walky.NewStringNode("ok-name")); keyNode != nil { 1751 if valNode.Kind == yaml.ScalarNode { 1752 seqNode := walky.NewSequenceNode() 1753 require.NoError(t, 1754 walky.AppendNode(seqNode, walky.ShallowCopyNode(valNode)), 1755 ) 1756 require.NoError(t, 1757 walky.AssignMapNode(node, keyNode, seqNode), 1758 ) 1759 } 1760 } 1761 return nil 1762 } 1763 1764 fig := newFigTreeFromEnv(WithPreProcessor(pp)) 1765 1766 dest := struct { 1767 FixedName string `yaml:"fixed-name"` 1768 GoodName string `yaml:"good-name"` 1769 OkName []string `yaml:"ok-name"` 1770 }{} 1771 1772 want := struct { 1773 FixedName string `yaml:"fixed-name"` 1774 GoodName string `yaml:"good-name"` 1775 OkName []string `yaml:"ok-name"` 1776 }{"good-value", "fixed-value", []string{"want-array"}} 1777 1778 err = fig.LoadConfigSource(&input, "test", &dest) 1779 assert.NoError(t, err) 1780 assert.Equal(t, want, dest) 1781 } 1782 1783 func TestMergeMapWithCopy(t *testing.T) { 1784 type mss = map[string]string 1785 1786 dest := struct { 1787 Map mss 1788 }{} 1789 1790 src1 := struct { 1791 Map mss 1792 }{ 1793 mss{ 1794 "key": "value", 1795 }, 1796 } 1797 1798 src2 := struct { 1799 Map mss 1800 }{ 1801 mss{ 1802 "otherkey": "othervalue", 1803 }, 1804 } 1805 1806 err := Merge(&dest, &src1) 1807 require.NoError(t, err) 1808 assert.Equal(t, mss{"key": "value"}, dest.Map) 1809 1810 err = Merge(&dest, &src2) 1811 require.NoError(t, err) 1812 assert.Equal(t, mss{"key": "value", "otherkey": "othervalue"}, dest.Map) 1813 1814 // verify that src1 was unmodified 1815 assert.Equal(t, mss{"key": "value"}, src1.Map) 1816 } 1817 1818 func TestMergeBoolString(t *testing.T) { 1819 src1 := struct { 1820 EnableThing BoolOption 1821 }{NewBoolOption(true)} 1822 1823 src2 := map[string]interface{}{ 1824 "enable-thing": "true", 1825 } 1826 1827 dest := MakeMergeStruct(src1, src2) 1828 err := Merge(dest, src1) 1829 require.NoError(t, err) 1830 err = Merge(dest, src2) 1831 require.NoError(t, err) 1832 1833 expected := &struct { 1834 EnableThing BoolOption 1835 }{BoolOption{Source: NewSource("merge"), Defined: true, Value: true}} 1836 1837 assert.Equal(t, expected, dest) 1838 } 1839 1840 func TestMergeStringBool(t *testing.T) { 1841 src1 := struct { 1842 EnableThing StringOption 1843 }{NewStringOption("true")} 1844 1845 src2 := map[string]interface{}{ 1846 "enable-thing": true, 1847 } 1848 1849 dest := MakeMergeStruct(src1, src2) 1850 err := Merge(dest, src1) 1851 require.NoError(t, err) 1852 err = Merge(dest, src2) 1853 require.NoError(t, err) 1854 1855 expected := &struct { 1856 EnableThing StringOption 1857 }{StringOption{Source: NewSource("merge"), Defined: true, Value: "true"}} 1858 1859 assert.Equal(t, expected, dest) 1860 } 1861 1862 func TestMergeStringFloat64(t *testing.T) { 1863 src1 := struct { 1864 SomeThing StringOption 1865 }{NewStringOption("true")} 1866 1867 src2 := map[string]interface{}{ 1868 "some-thing": 42.0, 1869 } 1870 1871 dest := MakeMergeStruct(src1, src2) 1872 err := Merge(dest, src1) 1873 require.NoError(t, err) 1874 err = Merge(dest, src2) 1875 require.NoError(t, err) 1876 1877 expected := &struct { 1878 SomeThing StringOption 1879 }{StringOption{Source: NewSource("merge"), Defined: true, Value: "42"}} 1880 1881 assert.Equal(t, expected, dest) 1882 } 1883 1884 func TestMergeDefaults(t *testing.T) { 1885 src1 := &struct { 1886 SomeThing StringOption 1887 }{NewStringOption("foo")} 1888 1889 src2 := &struct { 1890 SomeThing StringOption 1891 }{NewStringOption("bar")} 1892 1893 dest := MakeMergeStruct(src1, src2) 1894 err := Merge(dest, src1) 1895 require.NoError(t, err) 1896 1897 expected := &struct { 1898 SomeThing StringOption 1899 }{StringOption{Source: NewSource("default"), Defined: true, Value: "foo"}} 1900 1901 assert.Equal(t, expected, dest) 1902 1903 err = Merge(dest, src2) 1904 require.NoError(t, err) 1905 1906 assert.Equal(t, expected, dest) 1907 } 1908 1909 // TestMergeCopySlices verifies when we merge a non-nil slice onto a nil-slice 1910 // that the result is a copy of the original rather than direct reference 1911 // assignment. Otherwise we will get into conditions where we have multiple 1912 // merged objects using the exact same reference to a slice where if we change 1913 // one slice it modified all merged structs. 1914 func TestMergeCopySlice(t *testing.T) { 1915 type stuffer = struct { 1916 Stuff []string 1917 } 1918 1919 stuffers := []*stuffer{} 1920 common := &stuffer{Stuff: []string{"common"}} 1921 1922 stuff1 := &stuffer{Stuff: nil} 1923 stuff2 := &stuffer{Stuff: nil} 1924 1925 for _, stuff := range []*stuffer{stuff1, stuff2} { 1926 err := Merge(stuff, common) 1927 require.NoError(t, err) 1928 stuffers = append(stuffers, stuff) 1929 } 1930 1931 assert.Equal(t, []string{"common"}, stuffers[0].Stuff) 1932 assert.Equal(t, []string{"common"}, stuffers[1].Stuff) 1933 1934 stuffers[0].Stuff[0] = "updated" 1935 assert.Equal(t, []string{"updated"}, stuffers[0].Stuff) 1936 assert.Equal(t, []string{"common"}, stuffers[1].Stuff) 1937 } 1938 1939 func TestMergeCopyArray(t *testing.T) { 1940 type stuffer = struct { 1941 Stuff [2]string 1942 } 1943 1944 stuffers := []*stuffer{} 1945 common := &stuffer{Stuff: [2]string{"common"}} 1946 1947 stuff1 := &stuffer{Stuff: [2]string{}} 1948 stuff2 := &stuffer{Stuff: [2]string{}} 1949 1950 for _, stuff := range []*stuffer{stuff1, stuff2} { 1951 err := Merge(stuff, common) 1952 require.NoError(t, err) 1953 stuffers = append(stuffers, stuff) 1954 } 1955 1956 assert.Equal(t, [2]string{"common", ""}, stuffers[0].Stuff) 1957 assert.Equal(t, [2]string{"common", ""}, stuffers[1].Stuff) 1958 1959 stuffers[0].Stuff[0] = "updated" 1960 assert.Equal(t, [2]string{"updated", ""}, stuffers[0].Stuff) 1961 assert.Equal(t, [2]string{"common", ""}, stuffers[1].Stuff) 1962 } 1963 1964 func TestListOfStructs(t *testing.T) { 1965 type myStruct struct { 1966 ID string `yaml:"id"` 1967 Name string `yaml:"name"` 1968 } 1969 type myStructs []myStruct 1970 type data struct { 1971 Structs myStructs `yaml:"list"` 1972 } 1973 1974 config := ` 1975 list: 1976 - id: abc 1977 name: def 1978 - id: foo 1979 name: bar 1980 ` 1981 expected := data{ 1982 Structs: myStructs{ 1983 {ID: "abc", Name: "def"}, 1984 {ID: "foo", Name: "bar"}, 1985 }, 1986 } 1987 var node yaml.Node 1988 err := yaml.Unmarshal([]byte(config), &node) 1989 require.NoError(t, err) 1990 dest := data{} 1991 fig := newFigTreeFromEnv() 1992 err = fig.LoadConfigSource(&node, "test", &dest) 1993 require.NoError(t, err) 1994 require.Equal(t, expected, dest) 1995 1996 content, err := yaml.Marshal(dest) 1997 require.NoError(t, err) 1998 raw := map[string]any{} 1999 err = yaml.Unmarshal(content, &raw) 2000 require.NoError(t, err) 2001 2002 dest = data{} 2003 err = Merge(&dest, &raw) 2004 require.NoError(t, err) 2005 require.Equal(t, expected, dest) 2006 } 2007 2008 func TestLoadConfigToNode(t *testing.T) { 2009 type SubData struct { 2010 Field yaml.Node `yaml:"field"` 2011 } 2012 type data struct { 2013 SubData `yaml:",inline"` 2014 List []yaml.Node `yaml:"list"` 2015 Map map[string]yaml.Node `yaml:"map"` 2016 Stuff yaml.Node `yaml:"stuff"` 2017 Sub SubData `yaml:"sub"` 2018 } 2019 2020 config := ` 2021 field: 123 2022 list: [a, 99] 2023 map: 2024 key1: abc 2025 key2: 123 2026 stuff: {a: 1, b: 2} 2027 sub: 2028 field: ghi 2029 ` 2030 expected := data{ 2031 SubData: SubData{ 2032 Field: yaml.Node{Kind: yaml.ScalarNode, Tag: "!!int", Value: "123", Line: 2, Column: 8}, 2033 }, 2034 List: []yaml.Node{ 2035 {Kind: yaml.ScalarNode, Tag: "!!str", Value: "a", Line: 3, Column: 8}, 2036 {Kind: yaml.ScalarNode, Tag: "!!int", Value: "99", Line: 3, Column: 11}, 2037 }, 2038 Map: map[string]yaml.Node{ 2039 "key1": {Kind: yaml.ScalarNode, Tag: "!!str", Value: "abc", Line: 5, Column: 9}, 2040 "key2": {Kind: yaml.ScalarNode, Tag: "!!int", Value: "123", Line: 6, Column: 9}, 2041 }, 2042 Stuff: yaml.Node{ 2043 Kind: yaml.MappingNode, 2044 Tag: "!!map", 2045 Style: yaml.FlowStyle, 2046 Content: []*yaml.Node{ 2047 {Kind: yaml.ScalarNode, Tag: "!!str", Value: "a", Line: 7, Column: 9}, 2048 {Kind: yaml.ScalarNode, Tag: "!!int", Value: "1", Line: 7, Column: 12}, 2049 {Kind: yaml.ScalarNode, Tag: "!!str", Value: "b", Line: 7, Column: 15}, 2050 {Kind: yaml.ScalarNode, Tag: "!!int", Value: "2", Line: 7, Column: 18}, 2051 }, 2052 Line: 7, 2053 Column: 8, 2054 }, 2055 Sub: SubData{ 2056 Field: yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "ghi", Line: 9, Column: 10}, 2057 }, 2058 } 2059 var node yaml.Node 2060 err := yaml.Unmarshal([]byte(config), &node) 2061 require.NoError(t, err) 2062 dest := data{} 2063 fig := newFigTreeFromEnv() 2064 err = fig.LoadConfigSource(&node, "test", &dest) 2065 require.NoError(t, err) 2066 require.Equal(t, expected, dest) 2067 } 2068 2069 type UnmarshalInt int 2070 2071 func (t *UnmarshalInt) UnmarshalYAML(unmarshal func(any) error) error { 2072 var rawType string 2073 if err := unmarshal(&rawType); err != nil { 2074 return errors.WithStack(err) 2075 } 2076 switch strings.ToLower(rawType) { 2077 case "foo": 2078 *t = 1 2079 case "bar": 2080 *t = 2 2081 default: 2082 return errors.Errorf("Unknown unmarshal test value: %s", rawType) 2083 } 2084 return nil 2085 } 2086 2087 func TestLoadConfigWithUnmarshalInt(t *testing.T) { 2088 type Property struct { 2089 Type UnmarshalInt `yaml:"type"` 2090 Ptr *UnmarshalInt `yaml:"ptr"` 2091 } 2092 type data struct { 2093 Properties []Property `yaml:"properties"` 2094 } 2095 2096 config := ` 2097 properties: 2098 - type: foo 2099 ptr: foo 2100 - type: bar 2101 ptr: bar 2102 ` 2103 foo := UnmarshalInt(1) 2104 bar := UnmarshalInt(2) 2105 2106 expected := data{ 2107 Properties: []Property{ 2108 {Type: foo, Ptr: &foo}, 2109 {Type: bar, Ptr: &bar}, 2110 }, 2111 } 2112 2113 var node yaml.Node 2114 err := yaml.Unmarshal([]byte(config), &node) 2115 require.NoError(t, err) 2116 dest := data{} 2117 fig := newFigTreeFromEnv() 2118 err = fig.LoadConfigSource(&node, "test", &dest) 2119 require.NoError(t, err) 2120 require.Equal(t, expected, dest) 2121 2122 content, err := yaml.Marshal(dest) 2123 require.NoError(t, err) 2124 raw := map[string]any{} 2125 err = yaml.Unmarshal(content, &raw) 2126 require.NoError(t, err) 2127 2128 dest = data{} 2129 err = Merge(&dest, &raw) 2130 require.NoError(t, err) 2131 require.Equal(t, expected, dest) 2132 } 2133 2134 type UnmarshalString string 2135 2136 func (t *UnmarshalString) UnmarshalYAML(unmarshal func(any) error) error { 2137 var rawType any 2138 if err := unmarshal(&rawType); err != nil { 2139 return errors.WithStack(err) 2140 } 2141 switch c := rawType.(type) { 2142 case string: 2143 *t = UnmarshalString(strings.ToUpper(c)) 2144 case int: 2145 *t = UnmarshalString(fmt.Sprint(c)) 2146 default: 2147 panic(fmt.Sprintf("can't handle %T", c)) 2148 } 2149 return nil 2150 } 2151 2152 type UnmarshalStringList []UnmarshalString 2153 2154 // UnmarshalYAML will unmarshal a list of PortSpecs and return an error if any items 2155 // could not be unmarshalled. 2156 func (pl *UnmarshalStringList) UnmarshalYAML(unmarshal func(any) error) error { 2157 var ps []UnmarshalString 2158 if err := unmarshal(&ps); err != nil { 2159 return err 2160 } 2161 2162 *pl = ps 2163 return nil 2164 } 2165 2166 func TestLoadConfigWithUnmarshalString(t *testing.T) { 2167 type Property struct { 2168 Type UnmarshalString `yaml:"type"` 2169 Ptr *UnmarshalString `yaml:"ptr"` 2170 } 2171 type data struct { 2172 Properties []Property `yaml:"properties"` 2173 Prop Property `yaml:"prop"` 2174 Str UnmarshalString `yaml:"str"` 2175 Ptr *UnmarshalString `yaml:"ptr"` 2176 Strs []UnmarshalString `yaml:"strs"` 2177 Ptrs []*UnmarshalString `yaml:"ptrs"` 2178 PtrStrs *[]UnmarshalString `yaml:"ptr-strs"` 2179 StrList UnmarshalStringList `yaml:"str-list"` 2180 } 2181 2182 config := ` 2183 properties: 2184 - type: foo 2185 ptr: foo 2186 - type: bar 2187 ptr: bar 2188 prop: 2189 type: 123 2190 ptr: 123 2191 ptr: a 2192 str: a 2193 strs: [a, b] 2194 ptrs: [a, b] 2195 str-list: [a, b] 2196 ptr-strs: [a, b] 2197 ` 2198 foo := UnmarshalString("FOO") 2199 bar := UnmarshalString("BAR") 2200 baz := UnmarshalString("123") 2201 a := UnmarshalString("A") 2202 b := UnmarshalString("B") 2203 2204 expected := data{ 2205 Properties: []Property{ 2206 {Type: foo, Ptr: &foo}, 2207 {Type: bar, Ptr: &bar}, 2208 }, 2209 Prop: Property{Type: baz, Ptr: &baz}, 2210 Str: a, 2211 Ptr: &a, 2212 Strs: []UnmarshalString{a, b}, 2213 Ptrs: []*UnmarshalString{&a, &b}, 2214 StrList: UnmarshalStringList{a, b}, 2215 PtrStrs: &[]UnmarshalString{a, b}, 2216 } 2217 2218 var node yaml.Node 2219 err := yaml.Unmarshal([]byte(config), &node) 2220 require.NoError(t, err) 2221 dest := data{} 2222 fig := newFigTreeFromEnv() 2223 err = fig.LoadConfigSource(&node, "test", &dest) 2224 require.NoError(t, err) 2225 require.Equal(t, expected, dest) 2226 2227 content, err := yaml.Marshal(dest) 2228 require.NoError(t, err) 2229 raw := map[string]any{} 2230 err = yaml.Unmarshal(content, &raw) 2231 require.NoError(t, err) 2232 2233 dest = data{} 2234 err = Merge(&dest, &raw) 2235 require.NoError(t, err) 2236 require.Equal(t, expected, dest) 2237 } 2238 2239 func TestLoadConfigWithSliceDups(t *testing.T) { 2240 type data struct { 2241 Strs []UnmarshalString `yaml:"strs"` 2242 Simple []string `yaml:"simple"` 2243 Options ListStringOption `yaml:"options"` 2244 } 2245 configs := []struct { 2246 Name string 2247 Body string 2248 }{{ 2249 Name: "test", 2250 Body: ` 2251 strs: [a, b] 2252 simple: [a, b] 2253 options: [a, b] 2254 `, 2255 }, { 2256 Name: "../test", 2257 Body: ` 2258 strs: [b ,c] 2259 simple: [b, c] 2260 options: [b, c] 2261 `, 2262 }} 2263 expected := data{ 2264 Strs: []UnmarshalString{"A", "B", "C"}, 2265 Simple: []string{"a", "b", "c"}, 2266 Options: []StringOption{ 2267 {tSrc("test", 4, 11), true, "a"}, 2268 {tSrc("test", 4, 14), true, "b"}, 2269 {tSrc("../test", 4, 14), true, "c"}, 2270 }, 2271 } 2272 sources := []ConfigSource{} 2273 for _, c := range configs { 2274 var node yaml.Node 2275 err := yaml.Unmarshal([]byte(c.Body), &node) 2276 require.NoError(t, err) 2277 sources = append(sources, ConfigSource{ 2278 Config: &node, 2279 Filename: c.Name, 2280 }) 2281 } 2282 fig := newFigTreeFromEnv() 2283 got := data{} 2284 err := fig.LoadAllConfigSources(sources, &got) 2285 require.NoError(t, err) 2286 require.Equal(t, expected, got) 2287 } 2288 2289 func TestMapOfOptionLists(t *testing.T) { 2290 type data struct { 2291 Stuff map[string]ListStringOption `yaml:"stuff"` 2292 } 2293 2294 config := ` 2295 stuff: 2296 foo: 2297 - abc 2298 - def 2299 bar: 2300 - ghi 2301 - jkl 2302 ` 2303 expected := data{ 2304 Stuff: map[string]ListStringOption{ 2305 "bar": { 2306 StringOption{tSrc("test", 7, 7), true, "ghi"}, 2307 StringOption{tSrc("test", 8, 7), true, "jkl"}, 2308 }, 2309 "foo": { 2310 StringOption{tSrc("test", 4, 7), true, "abc"}, 2311 StringOption{tSrc("test", 5, 7), true, "def"}, 2312 }, 2313 }, 2314 } 2315 var node yaml.Node 2316 err := yaml.Unmarshal([]byte(config), &node) 2317 require.NoError(t, err) 2318 dest := data{} 2319 fig := newFigTreeFromEnv() 2320 err = fig.LoadConfigSource(&node, "test", &dest) 2321 require.NoError(t, err) 2322 require.Equal(t, expected, dest) 2323 } 2324 2325 func TestMapOfStructs(t *testing.T) { 2326 type myStruct struct { 2327 Name string 2328 } 2329 var dest map[string]myStruct 2330 2331 config := ` 2332 foo: 2333 name: abc 2334 bar: 2335 name: def 2336 ` 2337 expected := map[string]myStruct{ 2338 "bar": {Name: "def"}, 2339 "foo": {Name: "abc"}, 2340 } 2341 var node yaml.Node 2342 err := yaml.Unmarshal([]byte(config), &node) 2343 require.NoError(t, err) 2344 fig := newFigTreeFromEnv() 2345 err = fig.LoadConfigSource(&node, "test", &dest) 2346 require.NoError(t, err) 2347 require.Equal(t, expected, dest) 2348 2349 content, err := yaml.Marshal(dest) 2350 require.NoError(t, err) 2351 raw := map[string]any{} 2352 err = yaml.Unmarshal(content, &raw) 2353 require.NoError(t, err) 2354 2355 dest = map[string]myStruct{} 2356 err = Merge(&dest, &raw) 2357 require.NoError(t, err) 2358 require.Equal(t, expected, dest) 2359 } 2360 2361 func TestZeroYAML(t *testing.T) { 2362 type myStruct struct { 2363 Name string 2364 } 2365 var dest map[string]myStruct 2366 2367 config := ` 2368 foo: 2369 bar: 2370 ` 2371 expected := map[string]myStruct{ 2372 "bar": {}, 2373 "foo": {}, 2374 } 2375 var node yaml.Node 2376 err := yaml.Unmarshal([]byte(config), &node) 2377 require.NoError(t, err) 2378 fig := newFigTreeFromEnv() 2379 err = fig.LoadConfigSource(&node, "test", &dest) 2380 require.NoError(t, err) 2381 require.Equal(t, expected, dest) 2382 2383 content, err := yaml.Marshal(dest) 2384 require.NoError(t, err) 2385 raw := map[string]any{} 2386 err = yaml.Unmarshal(content, &raw) 2387 require.NoError(t, err) 2388 2389 dest = map[string]myStruct{} 2390 err = Merge(&dest, &raw) 2391 require.NoError(t, err) 2392 require.Equal(t, expected, dest) 2393 } 2394 2395 func TestBoolToStringOption(t *testing.T) { 2396 type myStruct struct { 2397 Name StringOption 2398 } 2399 var data map[string]myStruct 2400 2401 // true/false will be parsed as `!!bool` 2402 // but we want to assign it to a StringOption 2403 config := ` 2404 foo: 2405 name: true 2406 bar: 2407 name: false 2408 ` 2409 expected := map[string]myStruct{ 2410 "bar": {Name: StringOption{tSrc("test", 5, 9), true, "false"}}, 2411 "foo": {Name: StringOption{tSrc("test", 3, 9), true, "true"}}, 2412 } 2413 var node yaml.Node 2414 err := yaml.Unmarshal([]byte(config), &node) 2415 require.NoError(t, err) 2416 fig := newFigTreeFromEnv() 2417 err = fig.LoadConfigSource(&node, "test", &data) 2418 require.NoError(t, err) 2419 require.Equal(t, expected, data) 2420 } 2421 2422 func TestBoolToString(t *testing.T) { 2423 type myStruct struct { 2424 Name string 2425 } 2426 var dest map[string]myStruct 2427 2428 // true/false will be parsed as `!!bool` 2429 // but we want to assign it to a string 2430 config := ` 2431 foo: 2432 name: true 2433 bar: 2434 name: false 2435 ` 2436 expected := map[string]myStruct{ 2437 "bar": {Name: "false"}, 2438 "foo": {Name: "true"}, 2439 } 2440 var node yaml.Node 2441 err := yaml.Unmarshal([]byte(config), &node) 2442 require.NoError(t, err) 2443 fig := newFigTreeFromEnv() 2444 err = fig.LoadConfigSource(&node, "test", &dest) 2445 require.NoError(t, err) 2446 require.Equal(t, expected, dest) 2447 2448 content, err := yaml.Marshal(dest) 2449 require.NoError(t, err) 2450 raw := map[string]any{} 2451 err = yaml.Unmarshal(content, &raw) 2452 require.NoError(t, err) 2453 2454 dest = map[string]myStruct{} 2455 err = Merge(&dest, &raw) 2456 require.NoError(t, err) 2457 require.Equal(t, expected, dest) 2458 } 2459 2460 func TestIntToStringOption(t *testing.T) { 2461 var data map[string]StringOption 2462 2463 config := ` 2464 a: 11 2465 b: 11.1 2466 c: 11.1.1 2467 ` 2468 expected := map[string]StringOption{ 2469 "a": {tSrc("test", 2, 4), true, "11"}, 2470 "b": {tSrc("test", 3, 4), true, "11.1"}, 2471 "c": {tSrc("test", 4, 4), true, "11.1.1"}, 2472 } 2473 var node yaml.Node 2474 err := yaml.Unmarshal([]byte(config), &node) 2475 require.NoError(t, err) 2476 fig := newFigTreeFromEnv() 2477 err = fig.LoadConfigSource(&node, "test", &data) 2478 require.NoError(t, err) 2479 require.Equal(t, expected, data) 2480 } 2481 2482 func TestIntToString(t *testing.T) { 2483 var dest map[string]string 2484 config := ` 2485 a: 11 2486 b: 11.1 2487 c: 11.1.1 2488 ` 2489 expected := map[string]string{ 2490 "a": "11", 2491 "b": "11.1", 2492 "c": "11.1.1", 2493 } 2494 var node yaml.Node 2495 err := yaml.Unmarshal([]byte(config), &node) 2496 require.NoError(t, err) 2497 fig := newFigTreeFromEnv() 2498 err = fig.LoadConfigSource(&node, "test", &dest) 2499 require.NoError(t, err) 2500 require.Equal(t, expected, dest) 2501 2502 content, err := yaml.Marshal(dest) 2503 require.NoError(t, err) 2504 raw := map[string]any{} 2505 err = yaml.Unmarshal(content, &raw) 2506 require.NoError(t, err) 2507 2508 dest = map[string]string{} 2509 err = Merge(&dest, &raw) 2510 require.NoError(t, err) 2511 require.Equal(t, expected, dest) 2512 } 2513 2514 func TestAssignToAnyOption(t *testing.T) { 2515 var data map[string]Option[any] 2516 config := ` 2517 a: foo 2518 b: 12 2519 c: 12.2 2520 d: 12.2.2 2521 ` 2522 expected := map[string]Option[any]{ 2523 "a": {tSrc("test", 2, 4), true, "foo"}, 2524 "b": {tSrc("test", 3, 4), true, 12}, 2525 "c": {tSrc("test", 4, 4), true, 12.2}, 2526 "d": {tSrc("test", 5, 4), true, "12.2.2"}, 2527 } 2528 var node yaml.Node 2529 err := yaml.Unmarshal([]byte(config), &node) 2530 require.NoError(t, err) 2531 fig := newFigTreeFromEnv() 2532 err = fig.LoadConfigSource(&node, "test", &data) 2533 require.NoError(t, err) 2534 require.Equal(t, expected, data) 2535 } 2536 2537 func TestAssignToAny(t *testing.T) { 2538 var dest map[string]any 2539 config := ` 2540 a: foo 2541 b: 12 2542 c: 12.2 2543 d: 12.2.2 2544 ` 2545 expected := map[string]any{ 2546 "a": "foo", 2547 "b": 12, 2548 "c": 12.2, 2549 "d": "12.2.2", 2550 } 2551 var node yaml.Node 2552 err := yaml.Unmarshal([]byte(config), &node) 2553 require.NoError(t, err) 2554 fig := newFigTreeFromEnv() 2555 err = fig.LoadConfigSource(&node, "test", &dest) 2556 require.NoError(t, err) 2557 require.Equal(t, expected, dest) 2558 2559 content, err := yaml.Marshal(dest) 2560 require.NoError(t, err) 2561 raw := map[string]any{} 2562 err = yaml.Unmarshal(content, &raw) 2563 require.NoError(t, err) 2564 2565 dest = map[string]any{} 2566 err = Merge(&dest, &raw) 2567 require.NoError(t, err) 2568 require.Equal(t, expected, dest) 2569 } 2570 2571 func TestAssignInterfaceListToListStringOption(t *testing.T) { 2572 type data struct { 2573 MyList ListStringOption `yaml:"mylist"` 2574 } 2575 config := ` 2576 mylist: [] 2577 ` 2578 expected := data{} 2579 var node yaml.Node 2580 err := yaml.Unmarshal([]byte(config), &node) 2581 require.NoError(t, err) 2582 fig := newFigTreeFromEnv() 2583 dest := data{} 2584 err = fig.LoadConfigSource(&node, "test", &dest) 2585 require.NoError(t, err) 2586 require.Equal(t, expected, dest) 2587 2588 content, err := yaml.Marshal(dest) 2589 require.NoError(t, err) 2590 raw := map[string]any{} 2591 err = yaml.Unmarshal(content, &raw) 2592 require.NoError(t, err) 2593 2594 dest = data{} 2595 err = Merge(&dest, &raw) 2596 require.NoError(t, err) 2597 require.Equal(t, expected, dest) 2598 } 2599 2600 func TestAssignStringIntoList(t *testing.T) { 2601 type data struct { 2602 MyList ListStringOption `yaml:"mylist"` 2603 } 2604 config := ` 2605 mylist: foobar 2606 ` 2607 var node yaml.Node 2608 err := yaml.Unmarshal([]byte(config), &node) 2609 require.NoError(t, err) 2610 fig := newFigTreeFromEnv() 2611 dest := data{} 2612 err = fig.LoadConfigSource(&node, "test", &dest) 2613 require.Error(t, err) 2614 } 2615 2616 func TestAssignStringToOptionPointer(t *testing.T) { 2617 type data struct { 2618 MyStr *StringOption `yaml:"my-str"` 2619 } 2620 config := ` 2621 my-str: abc 2622 ` 2623 expected := data{ 2624 MyStr: &StringOption{tSrc("test", 2, 9), true, "abc"}, 2625 } 2626 var node yaml.Node 2627 err := yaml.Unmarshal([]byte(config), &node) 2628 require.NoError(t, err) 2629 fig := newFigTreeFromEnv() 2630 2631 got := data{MyStr: &StringOption{}} 2632 err = fig.LoadConfigSource(&node, "test", &got) 2633 require.NoError(t, err) 2634 require.Equal(t, expected, got) 2635 2636 got = data{} 2637 err = fig.LoadConfigSource(&node, "test", &got) 2638 require.NoError(t, err) 2639 require.Equal(t, expected, got) 2640 } 2641 2642 func TestAssignYAMLMergeMap(t *testing.T) { 2643 type data struct { 2644 MyMap map[string]int `yaml:"my-map"` 2645 ExtraMap map[string]int `yaml:"extra-map"` 2646 } 2647 config := ` 2648 defs: 2649 - &common 2650 a: 1 2651 b: 2 2652 - &extra 2653 d: 4 2654 e: 5 2655 my-map: 2656 <<: *common 2657 c: 3 2658 extra-map: 2659 <<: [*common, *extra] 2660 c: 3 2661 ` 2662 expected := data{ 2663 MyMap: map[string]int{ 2664 "a": 1, 2665 "b": 2, 2666 "c": 3, 2667 }, 2668 ExtraMap: map[string]int{ 2669 "a": 1, 2670 "b": 2, 2671 "c": 3, 2672 "d": 4, 2673 "e": 5, 2674 }, 2675 } 2676 var node yaml.Node 2677 err := yaml.Unmarshal([]byte(config), &node) 2678 require.NoError(t, err) 2679 fig := newFigTreeFromEnv() 2680 2681 got := data{} 2682 err = fig.LoadConfigSource(&node, "test", &got) 2683 require.NoError(t, err) 2684 require.Equal(t, expected, got) 2685 } 2686 2687 func TestDecodeWithSource(t *testing.T) { 2688 StringifyValue = false 2689 defer func() { 2690 StringifyValue = true 2691 }() 2692 2693 type data struct { 2694 MyMap map[string]IntOption `yaml:"my-map"` 2695 ExtraMap map[string]IntOption `yaml:"extra-map"` 2696 } 2697 config := ` 2698 defs: 2699 - &common 2700 a: 1 2701 b: 2 2702 c: 2703 - &extra 2704 e: 4 2705 f: 5 2706 g: 2707 my-map: 2708 <<: *common 2709 d: 3 2710 extra-map: 2711 <<: [*common, *extra] 2712 d: 3 2713 ` 2714 2715 var node yaml.Node 2716 err := yaml.Unmarshal([]byte(config), &node) 2717 if err != nil { 2718 panic(err) 2719 } 2720 fig := newFigTreeFromEnv() 2721 2722 got := data{} 2723 err = fig.LoadConfigSource(&node, "test", &got) 2724 require.NoError(t, err) 2725 2726 var buf bytes.Buffer 2727 err = yaml.NewEncoder(&buf).Encode(&got) 2728 require.NoError(t, err) 2729 2730 expected := ` 2731 my-map: 2732 a: 2733 value: 1 2734 source: test:4:8 2735 defined: true 2736 b: 2737 value: 2 2738 source: test:5:8 2739 defined: true 2740 c: 2741 value: 0 2742 source: test:6:7 2743 defined: false 2744 d: 2745 value: 3 2746 source: test:13:6 2747 defined: true 2748 extra-map: 2749 a: 2750 value: 1 2751 source: test:4:8 2752 defined: true 2753 b: 2754 value: 2 2755 source: test:5:8 2756 defined: true 2757 c: 2758 value: 0 2759 source: test:6:7 2760 defined: false 2761 d: 2762 value: 3 2763 source: test:16:6 2764 defined: true 2765 e: 2766 value: 4 2767 source: test:8:8 2768 defined: true 2769 f: 2770 value: 5 2771 source: test:9:8 2772 defined: true 2773 g: 2774 value: 0 2775 source: test:10:7 2776 defined: false 2777 ` 2778 expected = strings.TrimLeftFunc(expected, unicode.IsSpace) 2779 expected = strings.ReplaceAll(expected, "\t", "") 2780 require.Equal(t, expected, buf.String()) 2781 } 2782 2783 func TestPreserveRawYAMLStrings(t *testing.T) { 2784 type data struct { 2785 MyMap map[string]string `yaml:"my-map"` 2786 } 2787 config := ` 2788 my-map: 2789 Prop1: "False" 2790 Prop2: False 2791 Prop3: 12 2792 Prop4: 12.3 2793 ` 2794 expected := data{ 2795 MyMap: map[string]string{ 2796 "Prop1": "False", 2797 "Prop2": "False", 2798 "Prop3": "12", 2799 "Prop4": "12.3", 2800 }, 2801 } 2802 var node yaml.Node 2803 err := yaml.Unmarshal([]byte(config), &node) 2804 require.NoError(t, err) 2805 fig := newFigTreeFromEnv() 2806 got := data{} 2807 err = fig.LoadConfigSource(&node, "test", &got) 2808 require.NoError(t, err) 2809 require.Equal(t, expected, got) 2810 2811 content, err := yaml.Marshal(got) 2812 require.NoError(t, err) 2813 raw := map[string]any{} 2814 err = yaml.Unmarshal(content, &raw) 2815 require.NoError(t, err) 2816 2817 got = data{} 2818 err = Merge(&got, &raw) 2819 require.NoError(t, err) 2820 require.Equal(t, expected, got) 2821 } 2822 2823 func TestMergeZeroOption(t *testing.T) { 2824 type testData struct { 2825 Data StringOption 2826 } 2827 dest := testData{ 2828 Data: NewStringOption("default"), 2829 } 2830 err := Merge(&dest, &testData{}) 2831 require.NoError(t, err) 2832 2833 expected := testData{ 2834 Data: NewStringOption("default"), 2835 } 2836 2837 require.Equal(t, expected, dest) 2838 } 2839 2840 func TestYAMLReferences(t *testing.T) { 2841 type stuffOptions struct { 2842 A StringOption 2843 B StringOption 2844 C StringOption 2845 D StringOption 2846 } 2847 type stuffStrings struct { 2848 A string 2849 B string 2850 C string 2851 D string 2852 } 2853 type data struct { 2854 Stuff1 stuffOptions `yaml:"stuff1"` 2855 Stuff2 stuffStrings `yaml:"stuff2"` 2856 Stuff3 map[string]string `yaml:"stuff3"` 2857 } 2858 2859 config := ` 2860 defs: 2861 - &mystuff 2862 a: 1 2863 b: 2 2864 - &num 42 2865 stuff1: 2866 <<: *mystuff 2867 c: 3 2868 d: *num 2869 stuff2: 2870 <<: *mystuff 2871 c: 4 2872 d: *num 2873 stuff3: 2874 <<: *mystuff 2875 c: 5 2876 d: *num 2877 ` 2878 expected := data{ 2879 Stuff1: stuffOptions{ 2880 A: StringOption{tSrc("test", 4, 7), true, "1"}, 2881 B: StringOption{tSrc("test", 5, 7), true, "2"}, 2882 C: StringOption{tSrc("test", 9, 6), true, "3"}, 2883 D: StringOption{tSrc("test", 6, 4), true, "42"}, 2884 }, 2885 Stuff2: stuffStrings{ 2886 A: "1", 2887 B: "2", 2888 C: "4", 2889 D: "42", 2890 }, 2891 Stuff3: map[string]string{ 2892 "a": "1", 2893 "b": "2", 2894 "c": "5", 2895 "d": "42", 2896 }, 2897 } 2898 2899 var node yaml.Node 2900 err := yaml.Unmarshal([]byte(config), &node) 2901 require.NoError(t, err) 2902 fig := newFigTreeFromEnv() 2903 2904 got := data{} 2905 err = fig.LoadConfigSource(&node, "test", &got) 2906 require.NoError(t, err) 2907 require.Equal(t, expected, got) 2908 } 2909 2910 func TestYAMLReferencesMaps(t *testing.T) { 2911 type stuffOptions struct { 2912 Map MapStringOption 2913 Merged StringOption 2914 Extra StringOption 2915 } 2916 type stuffMap struct { 2917 Map map[string]string 2918 Merged string 2919 Extra string 2920 } 2921 type data struct { 2922 Stuff1 stuffOptions `yaml:"stuff1"` 2923 Stuff2 stuffOptions `yaml:"stuff2"` 2924 Stuff3 stuffMap `yaml:"stuff3"` 2925 Stuff4 stuffMap `yaml:"stuff4"` 2926 } 2927 2928 config := ` 2929 defs: 2930 - &map1 2931 map: {a: 1, b: 2} # this is ignored, dup key with merge site 2932 merged: "map1" 2933 - &map2 2934 map: {b: 3, c: 4, d: 5} # this is ignored, dup key with merge site 2935 merged: "map2" 2936 stuff1: 2937 <<: *map1 2938 map: {b: 3, c: 4} 2939 extra: stuff1 2940 stuff2: 2941 <<: [*map1, *map2] 2942 map: {b: 5, c: 6, e: 7} 2943 extra: stuff2 2944 stuff3: 2945 <<: *map1 2946 map: {b: 3, c: 4} 2947 extra: stuff3 2948 stuff4: 2949 <<: [*map1, *map2] 2950 map: {b: 5, c: 6, e: 7} 2951 extra: stuff4 2952 ` 2953 expected := data{ 2954 Stuff1: stuffOptions{ 2955 Map: MapStringOption{ 2956 "b": {tSrc("test", 11, 12), true, "3"}, 2957 "c": {tSrc("test", 11, 18), true, "4"}, 2958 }, 2959 Merged: StringOption{tSrc("test", 5, 12), true, "map1"}, 2960 Extra: StringOption{tSrc("test", 12, 10), true, "stuff1"}, 2961 }, 2962 Stuff2: stuffOptions{ 2963 Map: MapStringOption{ 2964 "b": {tSrc("test", 15, 12), true, "5"}, 2965 "c": {tSrc("test", 15, 18), true, "6"}, 2966 "e": {tSrc("test", 15, 24), true, "7"}, 2967 }, 2968 Merged: StringOption{tSrc("test", 5, 12), true, "map1"}, 2969 Extra: StringOption{tSrc("test", 16, 10), true, "stuff2"}, 2970 }, 2971 Stuff3: stuffMap{ 2972 Map: map[string]string{ 2973 "b": "3", 2974 "c": "4", 2975 }, 2976 Merged: "map1", 2977 Extra: "stuff3", 2978 }, 2979 Stuff4: stuffMap{ 2980 Map: map[string]string{ 2981 "b": "5", 2982 "c": "6", 2983 "e": "7", 2984 }, 2985 Merged: "map1", 2986 Extra: "stuff4", 2987 }, 2988 } 2989 2990 var node yaml.Node 2991 err := yaml.Unmarshal([]byte(config), &node) 2992 require.NoError(t, err) 2993 fig := newFigTreeFromEnv() 2994 2995 got := data{} 2996 err = fig.LoadConfigSource(&node, "test", &got) 2997 require.NoError(t, err) 2998 require.Equal(t, expected, got) 2999 } 3000 3001 func TestNullMerge(t *testing.T) { 3002 type data struct { 3003 Stuff MapStringOption 3004 } 3005 configs := []string{` 3006 stuff: 3007 a: 3008 `, ` 3009 stuff: 3010 a: 1 3011 `} 3012 3013 sources := []ConfigSource{} 3014 for i, config := range configs { 3015 var node yaml.Node 3016 err := yaml.Unmarshal([]byte(config), &node) 3017 require.NoError(t, err) 3018 sources = append(sources, ConfigSource{ 3019 Config: &node, 3020 Filename: "config" + strconv.Itoa(i), 3021 }) 3022 } 3023 got := data{} 3024 fig := newFigTreeFromEnv() 3025 err := fig.LoadAllConfigSources(sources, &got) 3026 require.NoError(t, err) 3027 expected := data{ 3028 Stuff: MapStringOption{ 3029 "a": {tSrc("config1", 3, 6), true, "1"}, 3030 }, 3031 } 3032 require.Equal(t, expected, got) 3033 } 3034 3035 func TestArrayAllowDupsOnPrimarySource(t *testing.T) { 3036 type data struct { 3037 Stuff1 []string `yaml:"stuff1"` 3038 Stuff2 ListStringOption `yaml:"stuff2"` 3039 } 3040 3041 configs := []string{` 3042 stuff1: [a, b, a, a] 3043 stuff2: [a, b, a, a] 3044 `, ` 3045 stuff1: [a, b, c] 3046 stuff2: [a, b, c] 3047 `} 3048 3049 sources := []ConfigSource{} 3050 for i, config := range configs { 3051 var node yaml.Node 3052 err := yaml.Unmarshal([]byte(config), &node) 3053 require.NoError(t, err) 3054 sources = append(sources, ConfigSource{ 3055 Config: &node, 3056 Filename: "config" + strconv.Itoa(i), 3057 }) 3058 } 3059 got := data{} 3060 fig := newFigTreeFromEnv() 3061 err := fig.LoadAllConfigSources(sources, &got) 3062 require.NoError(t, err) 3063 expected := data{ 3064 Stuff1: []string{"a", "b", "a", "a", "c"}, 3065 Stuff2: ListStringOption{ 3066 {tSrc("config0", 3, 10), true, "a"}, 3067 {tSrc("config0", 3, 13), true, "b"}, 3068 {tSrc("config0", 3, 16), true, "a"}, 3069 {tSrc("config0", 3, 19), true, "a"}, 3070 {tSrc("config1", 3, 16), true, "c"}, 3071 }, 3072 } 3073 require.Equal(t, expected, got) 3074 } 3075 3076 func TestOverwrite(t *testing.T) { 3077 type data struct { 3078 A1 []string `yaml:"a1"` 3079 A2 ListStringOption `yaml:"a2"` 3080 A3 []string `yaml:"a3"` 3081 A4 ListStringOption `yaml:"a4"` 3082 B1 map[string]string `yaml:"b1"` 3083 B2 MapStringOption `yaml:"b2"` 3084 B3 map[string]string `yaml:"b3"` 3085 B4 MapStringOption `yaml:"b4"` 3086 C1 string `yaml:"c1"` 3087 C2 StringOption `yaml:"c2"` 3088 C3 string `yaml:"c3"` 3089 C4 StringOption `yaml:"c4"` 3090 } 3091 3092 configs := []string{` 3093 a1: [a, b, a, a] 3094 a2: [a, b, a, a] 3095 a3: [a, b, a, a] 3096 a4: [a, b, a, a] 3097 b1: {a: 1, b: 2} 3098 b2: {a: 1, b: 2} 3099 b3: {a: 1, b: 2} 3100 b4: {a: 1, b: 2} 3101 c1: a 3102 c2: a 3103 c3: a 3104 c4: a 3105 `, ` 3106 config: 3107 overwrite: [a1, a2, a3, a4, b1, b2, b3, b4, c1, c2, c3, c4] 3108 a1: [a] 3109 a2: [a] 3110 a3: [] 3111 a4: [] 3112 b1: {a: 1} 3113 b2: {a: 1} 3114 b3: {} 3115 b4: {} 3116 c1: b 3117 c2: b 3118 c3: "" 3119 c4: "" 3120 `, ` 3121 # these are ignored b/c first overwrite will "win" 3122 config: 3123 overwrite: [a1, a2, a3, a4, b1, b2, b3, b4, c1, c2, c3, c4] 3124 a1: [c] 3125 a2: [c] 3126 a3: [c] 3127 a4: [c] 3128 b1: {c: 1} 3129 b2: {c: 1} 3130 b3: {c: 1} 3131 b4: {c: 1} 3132 c1: c 3133 c2: c 3134 c3: c 3135 c4: c 3136 `} 3137 3138 sources := []ConfigSource{} 3139 for i, config := range configs { 3140 var node yaml.Node 3141 err := yaml.Unmarshal([]byte(config), &node) 3142 require.NoError(t, err) 3143 sources = append(sources, ConfigSource{ 3144 Config: &node, 3145 Filename: "config" + strconv.Itoa(i), 3146 }) 3147 } 3148 got := data{} 3149 fig := newFigTreeFromEnv() 3150 err := fig.LoadAllConfigSources(sources, &got) 3151 require.NoError(t, err) 3152 expected := data{ 3153 A1: []string{"a"}, 3154 A2: ListStringOption{ 3155 {tSrc("config1", 5, 6), true, "a"}, 3156 }, 3157 A3: []string{}, 3158 A4: ListStringOption{}, 3159 B1: map[string]string{"a": "1"}, 3160 B2: MapStringOption{ 3161 "a": {tSrc("config1", 9, 9), true, "1"}, 3162 }, 3163 B3: map[string]string{}, 3164 B4: MapStringOption{}, 3165 C1: "b", 3166 C2: StringOption{tSrc("config1", 13, 5), true, "b"}, 3167 C3: "", 3168 C4: StringOption{tSrc("config1", 15, 5), true, ""}, 3169 } 3170 require.Equal(t, expected, got) 3171 } 3172 3173 func TestCustomOption(t *testing.T) { 3174 type MyString string 3175 type data struct { 3176 Stuff Option[MyString] 3177 Other Option[UnmarshalString] 3178 More Option[*MyString] 3179 Extra Option[*UnmarshalString] 3180 } 3181 config := ` 3182 stuff: abc 3183 other: foo 3184 more: baz 3185 extra: bin 3186 ` 3187 baz := MyString("baz") 3188 bin := UnmarshalString("BIN") 3189 expected := data{ 3190 Stuff: Option[MyString]{ 3191 Source: tSrc("test", 2, 8), 3192 Defined: true, 3193 Value: MyString("abc"), 3194 }, 3195 Other: Option[UnmarshalString]{ 3196 Source: tSrc("test", 3, 8), 3197 Defined: true, 3198 Value: UnmarshalString("FOO"), 3199 }, 3200 More: Option[*MyString]{ 3201 Source: tSrc("test", 4, 7), 3202 Defined: true, 3203 Value: &baz, 3204 }, 3205 Extra: Option[*UnmarshalString]{ 3206 Source: tSrc("test", 5, 8), 3207 Defined: true, 3208 Value: &bin, 3209 }, 3210 } 3211 var node yaml.Node 3212 err := yaml.Unmarshal([]byte(config), &node) 3213 require.NoError(t, err) 3214 fig := newFigTreeFromEnv() 3215 got := data{} 3216 err = fig.LoadConfigSource(&node, "test", &got) 3217 require.NoError(t, err) 3218 require.Equal(t, expected, got) 3219 } 3220 3221 type ParsedThing struct { 3222 First string 3223 Second string 3224 } 3225 3226 func (t *ParsedThing) UnmarshalYAML(unmarshal func(any) error) error { 3227 var rawType string 3228 if err := unmarshal(&rawType); err != nil { 3229 return errors.WithStack(err) 3230 } 3231 first, second, ok := strings.Cut(rawType, ":") 3232 if !ok { 3233 return errors.Errorf("invalid parsed thing, expected: 'first:second', got: %s", rawType) 3234 } 3235 t.First = first 3236 t.Second = second 3237 return nil 3238 } 3239 3240 func (t ParsedThing) MarshalYAML() (any, error) { 3241 return t.First + ":" + t.Second, nil 3242 } 3243 3244 func TestCustomOptionStruct(t *testing.T) { 3245 StringifyValue = true 3246 defer func() { 3247 StringifyValue = false 3248 }() 3249 type data struct { 3250 Stuff Option[ParsedThing] 3251 Other ParsedThing 3252 More Option[*ParsedThing] 3253 Extra *ParsedThing 3254 } 3255 config := `stuff: abc:def 3256 other: ghi:jkl 3257 more: mno:pqr 3258 extra: stu:vwx 3259 ` 3260 expected := data{ 3261 Stuff: Option[ParsedThing]{ 3262 Source: tSrc("yaml", 1, 8), 3263 Defined: true, 3264 Value: ParsedThing{ 3265 First: "abc", 3266 Second: "def", 3267 }, 3268 }, 3269 Other: ParsedThing{ 3270 First: "ghi", 3271 Second: "jkl", 3272 }, 3273 More: Option[*ParsedThing]{ 3274 Source: tSrc("yaml", 3, 7), 3275 Defined: true, 3276 Value: &ParsedThing{ 3277 First: "mno", 3278 Second: "pqr", 3279 }, 3280 }, 3281 Extra: &ParsedThing{ 3282 First: "stu", 3283 Second: "vwx", 3284 }, 3285 } 3286 var node yaml.Node 3287 err := yaml.Unmarshal([]byte(config), &node) 3288 require.NoError(t, err) 3289 fig := newFigTreeFromEnv() 3290 got := data{} 3291 err = fig.LoadConfigSource(&node, "yaml", &got) 3292 require.NoError(t, err) 3293 require.Equal(t, expected, got) 3294 3295 content, err := yaml.Marshal(got) 3296 require.NoError(t, err) 3297 got = data{} 3298 err = yaml.Unmarshal(content, &got) 3299 require.NoError(t, err) 3300 require.Equal(t, expected, got) 3301 } 3302 3303 func TestMergeArrayWithNilElems(t *testing.T) { 3304 dest := map[string]interface{}{ 3305 "stuff": []any{ 3306 "abc", 3307 nil, 3308 }, 3309 } 3310 3311 src := map[string]interface{}{ 3312 "stuff": []any{ 3313 "abc", 3314 "def", 3315 }, 3316 } 3317 3318 err := Merge(dest, src) 3319 require.NoError(t, err) 3320 3321 expected := map[string]interface{}{ 3322 "stuff": []any{ 3323 "abc", 3324 nil, 3325 "def", 3326 }, 3327 } 3328 assert.Equal(t, expected, dest) 3329 } 3330 3331 func TestMergeEmbedStructWithNilFields(t *testing.T) { 3332 type Embed struct { 3333 A string 3334 B string 3335 } 3336 type myStruct struct { 3337 Foo string 3338 Bar string 3339 *Embed 3340 } 3341 3342 dest := &myStruct{ 3343 Foo: "foo", 3344 Embed: &Embed{}, 3345 } 3346 3347 src := &myStruct{ 3348 Bar: "bar", 3349 } 3350 3351 err := Merge(dest, src) 3352 require.NoError(t, err) 3353 3354 // Empty Embed is already set in dest, so left empty 3355 expected := &myStruct{ 3356 Foo: "foo", 3357 Bar: "bar", 3358 Embed: &Embed{}, 3359 } 3360 assert.Equal(t, expected, dest) 3361 3362 dest = &myStruct{ 3363 Foo: "foo", 3364 } 3365 3366 src = &myStruct{ 3367 Bar: "bar", 3368 Embed: &Embed{}, 3369 } 3370 3371 err = Merge(dest, src) 3372 require.NoError(t, err) 3373 3374 // Embed is nil, b/c it is empty in src, and nil in dest 3375 // We do not create new pointer structs for empty sources. 3376 expected = &myStruct{ 3377 Foo: "foo", 3378 Bar: "bar", 3379 Embed: (*Embed)(nil), 3380 } 3381 assert.Equal(t, expected, dest) 3382 3383 dest = &myStruct{ 3384 Foo: "foo", 3385 Embed: &Embed{ 3386 A: "a", 3387 }, 3388 } 3389 3390 src = &myStruct{ 3391 Bar: "bar", 3392 } 3393 3394 err = Merge(dest, src) 3395 require.NoError(t, err) 3396 3397 // Embed is defined from dest, no updates from nil Embed in src 3398 expected = &myStruct{ 3399 Foo: "foo", 3400 Bar: "bar", 3401 Embed: &Embed{ 3402 A: "a", 3403 }, 3404 } 3405 assert.Equal(t, expected, dest) 3406 3407 dest = &myStruct{ 3408 Foo: "foo", 3409 Embed: &Embed{ 3410 A: "a", 3411 }, 3412 } 3413 3414 src = &myStruct{ 3415 Bar: "bar", 3416 } 3417 3418 err = Merge(dest, src) 3419 require.NoError(t, err) 3420 3421 // Embed is created from src, no errors on merge from nil Embed in dest. 3422 expected = &myStruct{ 3423 Foo: "foo", 3424 Bar: "bar", 3425 Embed: &Embed{ 3426 A: "a", 3427 }, 3428 } 3429 assert.Equal(t, expected, dest) 3430 } 3431 3432 func TestMergeWithDstStructArg(t *testing.T) { 3433 dest := struct { 3434 MyStructField string `yaml:"struct-field"` 3435 }{ 3436 MyStructField: "field1", 3437 } 3438 3439 src := map[string]interface{}{ 3440 "struct-field": "mapval1", 3441 } 3442 3443 err := Merge(dest, src) 3444 require.Error(t, err) 3445 }