1package toml 2 3import ( 4 "fmt" 5 "log" 6 "math" 7 "reflect" 8 "strings" 9 "testing" 10 "time" 11) 12 13func TestDecodeSimple(t *testing.T) { 14 var testSimple = ` 15age = 250 16andrew = "gallant" 17kait = "brady" 18now = 1987-07-05T05:45:00Z 19nowEast = 2017-06-22T16:15:21+08:00 20nowWest = 2017-06-22T02:14:36-06:00 21yesOrNo = true 22pi = 3.14 23colors = [ 24 ["red", "green", "blue"], 25 ["cyan", "magenta", "yellow", "black"], 26] 27 28[My.Cats] 29plato = "cat 1" 30cauchy = "cat 2" 31` 32 33 type cats struct { 34 Plato string 35 Cauchy string 36 } 37 type simple struct { 38 Age int 39 Colors [][]string 40 Pi float64 41 YesOrNo bool 42 Now time.Time 43 NowEast time.Time 44 NowWest time.Time 45 Andrew string 46 Kait string 47 My map[string]cats 48 } 49 50 var val simple 51 _, err := Decode(testSimple, &val) 52 if err != nil { 53 t.Fatal(err) 54 } 55 56 now, err := time.Parse("2006-01-02T15:04:05", "1987-07-05T05:45:00") 57 if err != nil { 58 panic(err) 59 } 60 nowEast, err := time.Parse("2006-01-02T15:04:05-07:00", "2017-06-22T16:15:21+08:00") 61 if err != nil { 62 panic(err) 63 } 64 nowWest, err := time.Parse("2006-01-02T15:04:05-07:00", "2017-06-22T02:14:36-06:00") 65 if err != nil { 66 panic(err) 67 } 68 var answer = simple{ 69 Age: 250, 70 Andrew: "gallant", 71 Kait: "brady", 72 Now: now, 73 NowEast: nowEast, 74 NowWest: nowWest, 75 YesOrNo: true, 76 Pi: 3.14, 77 Colors: [][]string{ 78 {"red", "green", "blue"}, 79 {"cyan", "magenta", "yellow", "black"}, 80 }, 81 My: map[string]cats{ 82 "Cats": {Plato: "cat 1", Cauchy: "cat 2"}, 83 }, 84 } 85 if !reflect.DeepEqual(val, answer) { 86 t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 87 answer, val) 88 } 89} 90 91func TestDecodeEmbedded(t *testing.T) { 92 type Dog struct{ Name string } 93 type Age int 94 type cat struct{ Name string } 95 96 for _, test := range []struct { 97 label string 98 input string 99 decodeInto interface{} 100 wantDecoded interface{} 101 }{ 102 { 103 label: "embedded struct", 104 input: `Name = "milton"`, 105 decodeInto: &struct{ Dog }{}, 106 wantDecoded: &struct{ Dog }{Dog{"milton"}}, 107 }, 108 { 109 label: "embedded non-nil pointer to struct", 110 input: `Name = "milton"`, 111 decodeInto: &struct{ *Dog }{}, 112 wantDecoded: &struct{ *Dog }{&Dog{"milton"}}, 113 }, 114 { 115 label: "embedded nil pointer to struct", 116 input: ``, 117 decodeInto: &struct{ *Dog }{}, 118 wantDecoded: &struct{ *Dog }{nil}, 119 }, 120 { 121 label: "unexported embedded struct", 122 input: `Name = "socks"`, 123 decodeInto: &struct{ cat }{}, 124 wantDecoded: &struct{ cat }{cat{"socks"}}, 125 }, 126 { 127 label: "embedded int", 128 input: `Age = -5`, 129 decodeInto: &struct{ Age }{}, 130 wantDecoded: &struct{ Age }{-5}, 131 }, 132 } { 133 _, err := Decode(test.input, test.decodeInto) 134 if err != nil { 135 t.Fatal(err) 136 } 137 if !reflect.DeepEqual(test.wantDecoded, test.decodeInto) { 138 t.Errorf("%s: want decoded == %+v, got %+v", 139 test.label, test.wantDecoded, test.decodeInto) 140 } 141 } 142} 143 144func TestDecodeIgnoredFields(t *testing.T) { 145 type simple struct { 146 Number int `toml:"-"` 147 } 148 const input = ` 149Number = 123 150- = 234 151` 152 var s simple 153 if _, err := Decode(input, &s); err != nil { 154 t.Fatal(err) 155 } 156 if s.Number != 0 { 157 t.Errorf("got: %d; want 0", s.Number) 158 } 159} 160 161func TestTableArrays(t *testing.T) { 162 var tomlTableArrays = ` 163[[albums]] 164name = "Born to Run" 165 166 [[albums.songs]] 167 name = "Jungleland" 168 169 [[albums.songs]] 170 name = "Meeting Across the River" 171 172[[albums]] 173name = "Born in the USA" 174 175 [[albums.songs]] 176 name = "Glory Days" 177 178 [[albums.songs]] 179 name = "Dancing in the Dark" 180` 181 182 type Song struct { 183 Name string 184 } 185 186 type Album struct { 187 Name string 188 Songs []Song 189 } 190 191 type Music struct { 192 Albums []Album 193 } 194 195 expected := Music{[]Album{ 196 {"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}}, 197 {"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}}, 198 }} 199 var got Music 200 if _, err := Decode(tomlTableArrays, &got); err != nil { 201 t.Fatal(err) 202 } 203 if !reflect.DeepEqual(expected, got) { 204 t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) 205 } 206} 207 208func TestTableNesting(t *testing.T) { 209 for _, tt := range []struct { 210 t string 211 want []string 212 }{ 213 {"[a.b.c]", []string{"a", "b", "c"}}, 214 {`[a."b.c"]`, []string{"a", "b.c"}}, 215 {`[a.'b.c']`, []string{"a", "b.c"}}, 216 {`[a.' b ']`, []string{"a", " b "}}, 217 {"[ d.e.f ]", []string{"d", "e", "f"}}, 218 {"[ g . h . i ]", []string{"g", "h", "i"}}, 219 {`[ j . "ʞ" . 'l' ]`, []string{"j", "ʞ", "l"}}, 220 } { 221 var m map[string]interface{} 222 if _, err := Decode(tt.t, &m); err != nil { 223 t.Errorf("Decode(%q): got error: %s", tt.t, err) 224 continue 225 } 226 if keys := extractNestedKeys(m); !reflect.DeepEqual(keys, tt.want) { 227 t.Errorf("Decode(%q): got nested keys %#v; want %#v", 228 tt.t, keys, tt.want) 229 } 230 } 231} 232 233func extractNestedKeys(v map[string]interface{}) []string { 234 var result []string 235 for { 236 if len(v) != 1 { 237 return result 238 } 239 for k, m := range v { 240 result = append(result, k) 241 var ok bool 242 v, ok = m.(map[string]interface{}) 243 if !ok { 244 return result 245 } 246 } 247 248 } 249} 250 251// Case insensitive matching tests. 252// A bit more comprehensive than needed given the current implementation, 253// but implementations change. 254// Probably still missing demonstrations of some ugly corner cases regarding 255// case insensitive matching and multiple fields. 256func TestCase(t *testing.T) { 257 var caseToml = ` 258tOpString = "string" 259tOpInt = 1 260tOpFloat = 1.1 261tOpBool = true 262tOpdate = 2006-01-02T15:04:05Z 263tOparray = [ "array" ] 264Match = "i should be in Match only" 265MatcH = "i should be in MatcH only" 266once = "just once" 267[nEst.eD] 268nEstedString = "another string" 269` 270 271 type InsensitiveEd struct { 272 NestedString string 273 } 274 275 type InsensitiveNest struct { 276 Ed InsensitiveEd 277 } 278 279 type Insensitive struct { 280 TopString string 281 TopInt int 282 TopFloat float64 283 TopBool bool 284 TopDate time.Time 285 TopArray []string 286 Match string 287 MatcH string 288 Once string 289 OncE string 290 Nest InsensitiveNest 291 } 292 293 tme, err := time.Parse(time.RFC3339, time.RFC3339[:len(time.RFC3339)-5]) 294 if err != nil { 295 panic(err) 296 } 297 expected := Insensitive{ 298 TopString: "string", 299 TopInt: 1, 300 TopFloat: 1.1, 301 TopBool: true, 302 TopDate: tme, 303 TopArray: []string{"array"}, 304 MatcH: "i should be in MatcH only", 305 Match: "i should be in Match only", 306 Once: "just once", 307 OncE: "", 308 Nest: InsensitiveNest{ 309 Ed: InsensitiveEd{NestedString: "another string"}, 310 }, 311 } 312 var got Insensitive 313 if _, err := Decode(caseToml, &got); err != nil { 314 t.Fatal(err) 315 } 316 if !reflect.DeepEqual(expected, got) { 317 t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) 318 } 319} 320 321func TestPointers(t *testing.T) { 322 type Object struct { 323 Type string 324 Description string 325 } 326 327 type Dict struct { 328 NamedObject map[string]*Object 329 BaseObject *Object 330 Strptr *string 331 Strptrs []*string 332 } 333 s1, s2, s3 := "blah", "abc", "def" 334 expected := &Dict{ 335 Strptr: &s1, 336 Strptrs: []*string{&s2, &s3}, 337 NamedObject: map[string]*Object{ 338 "foo": {"FOO", "fooooo!!!"}, 339 "bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"}, 340 }, 341 BaseObject: &Object{"BASE", "da base"}, 342 } 343 344 ex1 := ` 345Strptr = "blah" 346Strptrs = ["abc", "def"] 347 348[NamedObject.foo] 349Type = "FOO" 350Description = "fooooo!!!" 351 352[NamedObject.bar] 353Type = "BAR" 354Description = "ba-ba-ba-ba-barrrr!!!" 355 356[BaseObject] 357Type = "BASE" 358Description = "da base" 359` 360 dict := new(Dict) 361 _, err := Decode(ex1, dict) 362 if err != nil { 363 t.Errorf("Decode error: %v", err) 364 } 365 if !reflect.DeepEqual(expected, dict) { 366 t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict) 367 } 368} 369 370func TestDecodeDatetime(t *testing.T) { 371 const noTimestamp = "2006-01-02T15:04:05" 372 for _, tt := range []struct { 373 s string 374 t string 375 format string 376 }{ 377 {"1979-05-27T07:32:00Z", "1979-05-27T07:32:00Z", time.RFC3339}, 378 {"1979-05-27T00:32:00-07:00", "1979-05-27T00:32:00-07:00", time.RFC3339}, 379 { 380 "1979-05-27T00:32:00.999999-07:00", 381 "1979-05-27T00:32:00.999999-07:00", 382 time.RFC3339, 383 }, 384 {"1979-05-27T07:32:00", "1979-05-27T07:32:00", noTimestamp}, 385 { 386 "1979-05-27T00:32:00.999999", 387 "1979-05-27T00:32:00.999999", 388 noTimestamp, 389 }, 390 {"1979-05-27", "1979-05-27T00:00:00", noTimestamp}, 391 } { 392 var x struct{ D time.Time } 393 input := "d = " + tt.s 394 if _, err := Decode(input, &x); err != nil { 395 t.Errorf("Decode(%q): got error: %s", input, err) 396 continue 397 } 398 want, err := time.ParseInLocation(tt.format, tt.t, time.Local) 399 if err != nil { 400 panic(err) 401 } 402 if !x.D.Equal(want) { 403 t.Errorf("Decode(%q): got %s; want %s", input, x.D, want) 404 } 405 } 406} 407 408func TestDecodeBadDatetime(t *testing.T) { 409 var x struct{ T time.Time } 410 for _, s := range []string{ 411 "123", 412 "2006-01-50T00:00:00Z", 413 "2006-01-30T00:00", 414 "2006-01-30T", 415 } { 416 input := "T = " + s 417 if _, err := Decode(input, &x); err == nil { 418 t.Errorf("Expected invalid DateTime error for %q", s) 419 } 420 } 421} 422 423func TestDecodeMultilineStrings(t *testing.T) { 424 var x struct { 425 S string 426 } 427 const s0 = `s = """ 428a b \n c 429d e f 430"""` 431 if _, err := Decode(s0, &x); err != nil { 432 t.Fatal(err) 433 } 434 if want := "a b \n c\nd e f\n"; x.S != want { 435 t.Errorf("got: %q; want: %q", x.S, want) 436 } 437 const s1 = `s = """a b c\ 438"""` 439 if _, err := Decode(s1, &x); err != nil { 440 t.Fatal(err) 441 } 442 if want := "a b c"; x.S != want { 443 t.Errorf("got: %q; want: %q", x.S, want) 444 } 445} 446 447type sphere struct { 448 Center [3]float64 449 Radius float64 450} 451 452func TestDecodeSimpleArray(t *testing.T) { 453 var s1 sphere 454 if _, err := Decode(`center = [0.0, 1.5, 0.0]`, &s1); err != nil { 455 t.Fatal(err) 456 } 457} 458 459func TestDecodeArrayWrongSize(t *testing.T) { 460 var s1 sphere 461 if _, err := Decode(`center = [0.1, 2.3]`, &s1); err == nil { 462 t.Fatal("Expected array type mismatch error") 463 } 464} 465 466func TestDecodeLargeIntoSmallInt(t *testing.T) { 467 type table struct { 468 Value int8 469 } 470 var tab table 471 if _, err := Decode(`value = 500`, &tab); err == nil { 472 t.Fatal("Expected integer out-of-bounds error.") 473 } 474} 475 476func TestDecodeSizedInts(t *testing.T) { 477 type table struct { 478 U8 uint8 479 U16 uint16 480 U32 uint32 481 U64 uint64 482 U uint 483 I8 int8 484 I16 int16 485 I32 int32 486 I64 int64 487 I int 488 } 489 answer := table{1, 1, 1, 1, 1, -1, -1, -1, -1, -1} 490 toml := ` 491 u8 = 1 492 u16 = 1 493 u32 = 1 494 u64 = 1 495 u = 1 496 i8 = -1 497 i16 = -1 498 i32 = -1 499 i64 = -1 500 i = -1 501 ` 502 var tab table 503 if _, err := Decode(toml, &tab); err != nil { 504 t.Fatal(err.Error()) 505 } 506 if answer != tab { 507 t.Fatalf("Expected %#v but got %#v", answer, tab) 508 } 509} 510 511func TestDecodeInts(t *testing.T) { 512 for _, tt := range []struct { 513 s string 514 want int64 515 }{ 516 {"0", 0}, 517 {"+99", 99}, 518 {"-10", -10}, 519 {"1_234_567", 1234567}, 520 {"1_2_3_4", 1234}, 521 {"-9_223_372_036_854_775_808", math.MinInt64}, 522 {"9_223_372_036_854_775_807", math.MaxInt64}, 523 } { 524 var x struct{ N int64 } 525 input := "n = " + tt.s 526 if _, err := Decode(input, &x); err != nil { 527 t.Errorf("Decode(%q): got error: %s", input, err) 528 continue 529 } 530 if x.N != tt.want { 531 t.Errorf("Decode(%q): got %d; want %d", input, x.N, tt.want) 532 } 533 } 534} 535 536func TestDecodeFloats(t *testing.T) { 537 for _, tt := range []struct { 538 s string 539 want float64 540 }{ 541 {"+1.0", 1}, 542 {"3.1415", 3.1415}, 543 {"-0.01", -0.01}, 544 {"5e+22", 5e22}, 545 {"1e6", 1e6}, 546 {"-2E-2", -2e-2}, 547 {"6.626e-34", 6.626e-34}, 548 {"9_224_617.445_991_228_313", 9224617.445991228313}, 549 {"9_876.54_32e1_0", 9876.5432e10}, 550 } { 551 var x struct{ N float64 } 552 input := "n = " + tt.s 553 if _, err := Decode(input, &x); err != nil { 554 t.Errorf("Decode(%q): got error: %s", input, err) 555 continue 556 } 557 if x.N != tt.want { 558 t.Errorf("Decode(%q): got %f; want %f", input, x.N, tt.want) 559 } 560 } 561} 562 563func TestDecodeMalformedNumbers(t *testing.T) { 564 for _, tt := range []struct { 565 s string 566 want string 567 }{ 568 {"++99", "expected a digit"}, 569 {"0..1", "must be followed by one or more digits"}, 570 {"0.1.2", "Invalid float value"}, 571 {"1e2.3", "Invalid float value"}, 572 {"1e2e3", "Invalid float value"}, 573 {"_123", "expected value"}, 574 {"123_", "surrounded by digits"}, 575 {"1._23", "surrounded by digits"}, 576 {"1e__23", "surrounded by digits"}, 577 {"123.", "must be followed by one or more digits"}, 578 {"1.e2", "must be followed by one or more digits"}, 579 } { 580 var x struct{ N interface{} } 581 input := "n = " + tt.s 582 _, err := Decode(input, &x) 583 if err == nil { 584 t.Errorf("Decode(%q): got nil, want error containing %q", 585 input, tt.want) 586 continue 587 } 588 if !strings.Contains(err.Error(), tt.want) { 589 t.Errorf("Decode(%q): got %q, want error containing %q", 590 input, err, tt.want) 591 } 592 } 593} 594 595func TestDecodeBadValues(t *testing.T) { 596 for _, tt := range []struct { 597 v interface{} 598 want string 599 }{ 600 {3, "non-pointer int"}, 601 {(*int)(nil), "nil"}, 602 } { 603 _, err := Decode(`x = 3`, tt.v) 604 if err == nil { 605 t.Errorf("Decode(%v): got nil; want error containing %q", 606 tt.v, tt.want) 607 continue 608 } 609 if !strings.Contains(err.Error(), tt.want) { 610 t.Errorf("Decode(%v): got %q; want error containing %q", 611 tt.v, err, tt.want) 612 } 613 } 614} 615 616func TestUnmarshaler(t *testing.T) { 617 618 var tomlBlob = ` 619[dishes.hamboogie] 620name = "Hamboogie with fries" 621price = 10.99 622 623[[dishes.hamboogie.ingredients]] 624name = "Bread Bun" 625 626[[dishes.hamboogie.ingredients]] 627name = "Lettuce" 628 629[[dishes.hamboogie.ingredients]] 630name = "Real Beef Patty" 631 632[[dishes.hamboogie.ingredients]] 633name = "Tomato" 634 635[dishes.eggsalad] 636name = "Egg Salad with rice" 637price = 3.99 638 639[[dishes.eggsalad.ingredients]] 640name = "Egg" 641 642[[dishes.eggsalad.ingredients]] 643name = "Mayo" 644 645[[dishes.eggsalad.ingredients]] 646name = "Rice" 647` 648 m := &menu{} 649 if _, err := Decode(tomlBlob, m); err != nil { 650 t.Fatal(err) 651 } 652 653 if len(m.Dishes) != 2 { 654 t.Log("two dishes should be loaded with UnmarshalTOML()") 655 t.Errorf("expected %d but got %d", 2, len(m.Dishes)) 656 } 657 658 eggSalad := m.Dishes["eggsalad"] 659 if _, ok := interface{}(eggSalad).(dish); !ok { 660 t.Errorf("expected a dish") 661 } 662 663 if eggSalad.Name != "Egg Salad with rice" { 664 t.Errorf("expected the dish to be named 'Egg Salad with rice'") 665 } 666 667 if len(eggSalad.Ingredients) != 3 { 668 t.Log("dish should be loaded with UnmarshalTOML()") 669 t.Errorf("expected %d but got %d", 3, len(eggSalad.Ingredients)) 670 } 671 672 found := false 673 for _, i := range eggSalad.Ingredients { 674 if i.Name == "Rice" { 675 found = true 676 break 677 } 678 } 679 if !found { 680 t.Error("Rice was not loaded in UnmarshalTOML()") 681 } 682 683 // test on a value - must be passed as * 684 o := menu{} 685 if _, err := Decode(tomlBlob, &o); err != nil { 686 t.Fatal(err) 687 } 688 689} 690 691func TestDecodeInlineTable(t *testing.T) { 692 input := ` 693[CookieJar] 694Types = {Chocolate = "yummy", Oatmeal = "best ever"} 695 696[Seasons] 697Locations = {NY = {Temp = "not cold", Rating = 4}, MI = {Temp = "freezing", Rating = 9}} 698` 699 type cookieJar struct { 700 Types map[string]string 701 } 702 type properties struct { 703 Temp string 704 Rating int 705 } 706 type seasons struct { 707 Locations map[string]properties 708 } 709 type wrapper struct { 710 CookieJar cookieJar 711 Seasons seasons 712 } 713 var got wrapper 714 715 meta, err := Decode(input, &got) 716 if err != nil { 717 t.Fatal(err) 718 } 719 want := wrapper{ 720 CookieJar: cookieJar{ 721 Types: map[string]string{ 722 "Chocolate": "yummy", 723 "Oatmeal": "best ever", 724 }, 725 }, 726 Seasons: seasons{ 727 Locations: map[string]properties{ 728 "NY": { 729 Temp: "not cold", 730 Rating: 4, 731 }, 732 "MI": { 733 Temp: "freezing", 734 Rating: 9, 735 }, 736 }, 737 }, 738 } 739 if !reflect.DeepEqual(got, want) { 740 t.Fatalf("after decode, got:\n\n%#v\n\nwant:\n\n%#v", got, want) 741 } 742 if len(meta.keys) != 12 { 743 t.Errorf("after decode, got %d meta keys; want 12", len(meta.keys)) 744 } 745 if len(meta.types) != 12 { 746 t.Errorf("after decode, got %d meta types; want 12", len(meta.types)) 747 } 748} 749 750func TestDecodeInlineTableArray(t *testing.T) { 751 type point struct { 752 X, Y, Z int 753 } 754 var got struct { 755 Points []point 756 } 757 // Example inline table array from the spec. 758 const in = ` 759points = [ { x = 1, y = 2, z = 3 }, 760 { x = 7, y = 8, z = 9 }, 761 { x = 2, y = 4, z = 8 } ] 762 763` 764 if _, err := Decode(in, &got); err != nil { 765 t.Fatal(err) 766 } 767 want := []point{ 768 {X: 1, Y: 2, Z: 3}, 769 {X: 7, Y: 8, Z: 9}, 770 {X: 2, Y: 4, Z: 8}, 771 } 772 if !reflect.DeepEqual(got.Points, want) { 773 t.Errorf("got %#v; want %#v", got.Points, want) 774 } 775} 776 777func TestDecodeMalformedInlineTable(t *testing.T) { 778 for _, tt := range []struct { 779 s string 780 want string 781 }{ 782 {"{,}", "unexpected comma"}, 783 {"{x = 3 y = 4}", "expected a comma or an inline table terminator"}, 784 {"{x=3,,y=4}", "unexpected comma"}, 785 {"{x=3,\ny=4}", "newlines not allowed"}, 786 {"{x=3\n,y=4}", "newlines not allowed"}, 787 } { 788 var x struct{ A map[string]int } 789 input := "a = " + tt.s 790 _, err := Decode(input, &x) 791 if err == nil { 792 t.Errorf("Decode(%q): got nil, want error containing %q", 793 input, tt.want) 794 continue 795 } 796 if !strings.Contains(err.Error(), tt.want) { 797 t.Errorf("Decode(%q): got %q, want error containing %q", 798 input, err, tt.want) 799 } 800 } 801} 802 803type menu struct { 804 Dishes map[string]dish 805} 806 807func (m *menu) UnmarshalTOML(p interface{}) error { 808 m.Dishes = make(map[string]dish) 809 data, _ := p.(map[string]interface{}) 810 dishes := data["dishes"].(map[string]interface{}) 811 for n, v := range dishes { 812 if d, ok := v.(map[string]interface{}); ok { 813 nd := dish{} 814 nd.UnmarshalTOML(d) 815 m.Dishes[n] = nd 816 } else { 817 return fmt.Errorf("not a dish") 818 } 819 } 820 return nil 821} 822 823type dish struct { 824 Name string 825 Price float32 826 Ingredients []ingredient 827} 828 829func (d *dish) UnmarshalTOML(p interface{}) error { 830 data, _ := p.(map[string]interface{}) 831 d.Name, _ = data["name"].(string) 832 d.Price, _ = data["price"].(float32) 833 ingredients, _ := data["ingredients"].([]map[string]interface{}) 834 for _, e := range ingredients { 835 n, _ := interface{}(e).(map[string]interface{}) 836 name, _ := n["name"].(string) 837 i := ingredient{name} 838 d.Ingredients = append(d.Ingredients, i) 839 } 840 return nil 841} 842 843type ingredient struct { 844 Name string 845} 846 847func TestDecodeSlices(t *testing.T) { 848 type T struct { 849 S []string 850 } 851 for i, tt := range []struct { 852 v T 853 input string 854 want T 855 }{ 856 {T{}, "", T{}}, 857 {T{[]string{}}, "", T{[]string{}}}, 858 {T{[]string{"a", "b"}}, "", T{[]string{"a", "b"}}}, 859 {T{}, "S = []", T{[]string{}}}, 860 {T{[]string{}}, "S = []", T{[]string{}}}, 861 {T{[]string{"a", "b"}}, "S = []", T{[]string{}}}, 862 {T{}, `S = ["x"]`, T{[]string{"x"}}}, 863 {T{[]string{}}, `S = ["x"]`, T{[]string{"x"}}}, 864 {T{[]string{"a", "b"}}, `S = ["x"]`, T{[]string{"x"}}}, 865 } { 866 if _, err := Decode(tt.input, &tt.v); err != nil { 867 t.Errorf("[%d] %s", i, err) 868 continue 869 } 870 if !reflect.DeepEqual(tt.v, tt.want) { 871 t.Errorf("[%d] got %#v; want %#v", i, tt.v, tt.want) 872 } 873 } 874} 875 876func TestDecodePrimitive(t *testing.T) { 877 type S struct { 878 P Primitive 879 } 880 type T struct { 881 S []int 882 } 883 slicep := func(s []int) *[]int { return &s } 884 arrayp := func(a [2]int) *[2]int { return &a } 885 mapp := func(m map[string]int) *map[string]int { return &m } 886 for i, tt := range []struct { 887 v interface{} 888 input string 889 want interface{} 890 }{ 891 // slices 892 {slicep(nil), "", slicep(nil)}, 893 {slicep([]int{}), "", slicep([]int{})}, 894 {slicep([]int{1, 2, 3}), "", slicep([]int{1, 2, 3})}, 895 {slicep(nil), "P = [1,2]", slicep([]int{1, 2})}, 896 {slicep([]int{}), "P = [1,2]", slicep([]int{1, 2})}, 897 {slicep([]int{1, 2, 3}), "P = [1,2]", slicep([]int{1, 2})}, 898 899 // arrays 900 {arrayp([2]int{2, 3}), "", arrayp([2]int{2, 3})}, 901 {arrayp([2]int{2, 3}), "P = [3,4]", arrayp([2]int{3, 4})}, 902 903 // maps 904 {mapp(nil), "", mapp(nil)}, 905 {mapp(map[string]int{}), "", mapp(map[string]int{})}, 906 {mapp(map[string]int{"a": 1}), "", mapp(map[string]int{"a": 1})}, 907 {mapp(nil), "[P]\na = 2", mapp(map[string]int{"a": 2})}, 908 {mapp(map[string]int{}), "[P]\na = 2", mapp(map[string]int{"a": 2})}, 909 {mapp(map[string]int{"a": 1, "b": 3}), "[P]\na = 2", mapp(map[string]int{"a": 2, "b": 3})}, 910 911 // structs 912 {&T{nil}, "[P]", &T{nil}}, 913 {&T{[]int{}}, "[P]", &T{[]int{}}}, 914 {&T{[]int{1, 2, 3}}, "[P]", &T{[]int{1, 2, 3}}}, 915 {&T{nil}, "[P]\nS = [1,2]", &T{[]int{1, 2}}}, 916 {&T{[]int{}}, "[P]\nS = [1,2]", &T{[]int{1, 2}}}, 917 {&T{[]int{1, 2, 3}}, "[P]\nS = [1,2]", &T{[]int{1, 2}}}, 918 } { 919 var s S 920 md, err := Decode(tt.input, &s) 921 if err != nil { 922 t.Errorf("[%d] Decode error: %s", i, err) 923 continue 924 } 925 if err := md.PrimitiveDecode(s.P, tt.v); err != nil { 926 t.Errorf("[%d] PrimitiveDecode error: %s", i, err) 927 continue 928 } 929 if !reflect.DeepEqual(tt.v, tt.want) { 930 t.Errorf("[%d] got %#v; want %#v", i, tt.v, tt.want) 931 } 932 } 933} 934 935func TestDecodeErrors(t *testing.T) { 936 for _, s := range []string{ 937 `x="`, 938 `x='`, 939 `x='''`, 940 941 // Cases found by fuzzing in 942 // https://github.com/BurntSushi/toml/issues/155. 943 `""�`, // used to panic with index out of range 944 `e="""`, // used to hang 945 } { 946 var x struct{} 947 _, err := Decode(s, &x) 948 if err == nil { 949 t.Errorf("Decode(%q): got nil error", s) 950 } 951 } 952} 953 954// Test for https://github.com/BurntSushi/toml/pull/166. 955func TestDecodeBoolArray(t *testing.T) { 956 for _, tt := range []struct { 957 s string 958 got interface{} 959 want interface{} 960 }{ 961 { 962 "a = [true, false]", 963 &struct{ A []bool }{}, 964 &struct{ A []bool }{[]bool{true, false}}, 965 }, 966 { 967 "a = {a = true, b = false}", 968 &struct{ A map[string]bool }{}, 969 &struct{ A map[string]bool }{map[string]bool{"a": true, "b": false}}, 970 }, 971 } { 972 if _, err := Decode(tt.s, tt.got); err != nil { 973 t.Errorf("Decode(%q): %s", tt.s, err) 974 continue 975 } 976 if !reflect.DeepEqual(tt.got, tt.want) { 977 t.Errorf("Decode(%q): got %#v; want %#v", tt.s, tt.got, tt.want) 978 } 979 } 980} 981 982func ExampleMetaData_PrimitiveDecode() { 983 var md MetaData 984 var err error 985 986 var tomlBlob = ` 987ranking = ["Springsteen", "J Geils"] 988 989[bands.Springsteen] 990started = 1973 991albums = ["Greetings", "WIESS", "Born to Run", "Darkness"] 992 993[bands."J Geils"] 994started = 1970 995albums = ["The J. Geils Band", "Full House", "Blow Your Face Out"] 996` 997 998 type band struct { 999 Started int 1000 Albums []string 1001 } 1002 type classics struct { 1003 Ranking []string 1004 Bands map[string]Primitive 1005 } 1006 1007 // Do the initial decode. Reflection is delayed on Primitive values. 1008 var music classics 1009 if md, err = Decode(tomlBlob, &music); err != nil { 1010 log.Fatal(err) 1011 } 1012 1013 // MetaData still includes information on Primitive values. 1014 fmt.Printf("Is `bands.Springsteen` defined? %v\n", 1015 md.IsDefined("bands", "Springsteen")) 1016 1017 // Decode primitive data into Go values. 1018 for _, artist := range music.Ranking { 1019 // A band is a primitive value, so we need to decode it to get a 1020 // real `band` value. 1021 primValue := music.Bands[artist] 1022 1023 var aBand band 1024 if err = md.PrimitiveDecode(primValue, &aBand); err != nil { 1025 log.Fatal(err) 1026 } 1027 fmt.Printf("%s started in %d.\n", artist, aBand.Started) 1028 } 1029 // Check to see if there were any fields left undecoded. 1030 // Note that this won't be empty before decoding the Primitive value! 1031 fmt.Printf("Undecoded: %q\n", md.Undecoded()) 1032 1033 // Output: 1034 // Is `bands.Springsteen` defined? true 1035 // Springsteen started in 1973. 1036 // J Geils started in 1970. 1037 // Undecoded: [] 1038} 1039 1040func ExampleDecode() { 1041 var tomlBlob = ` 1042# Some comments. 1043[alpha] 1044ip = "10.0.0.1" 1045 1046 [alpha.config] 1047 Ports = [ 8001, 8002 ] 1048 Location = "Toronto" 1049 Created = 1987-07-05T05:45:00Z 1050 1051[beta] 1052ip = "10.0.0.2" 1053 1054 [beta.config] 1055 Ports = [ 9001, 9002 ] 1056 Location = "New Jersey" 1057 Created = 1887-01-05T05:55:00Z 1058` 1059 1060 type serverConfig struct { 1061 Ports []int 1062 Location string 1063 Created time.Time 1064 } 1065 1066 type server struct { 1067 IP string `toml:"ip,omitempty"` 1068 Config serverConfig `toml:"config"` 1069 } 1070 1071 type servers map[string]server 1072 1073 var config servers 1074 if _, err := Decode(tomlBlob, &config); err != nil { 1075 log.Fatal(err) 1076 } 1077 1078 for _, name := range []string{"alpha", "beta"} { 1079 s := config[name] 1080 fmt.Printf("Server: %s (ip: %s) in %s created on %s\n", 1081 name, s.IP, s.Config.Location, 1082 s.Config.Created.Format("2006-01-02")) 1083 fmt.Printf("Ports: %v\n", s.Config.Ports) 1084 } 1085 1086 // Output: 1087 // Server: alpha (ip: 10.0.0.1) in Toronto created on 1987-07-05 1088 // Ports: [8001 8002] 1089 // Server: beta (ip: 10.0.0.2) in New Jersey created on 1887-01-05 1090 // Ports: [9001 9002] 1091} 1092 1093type duration struct { 1094 time.Duration 1095} 1096 1097func (d *duration) UnmarshalText(text []byte) error { 1098 var err error 1099 d.Duration, err = time.ParseDuration(string(text)) 1100 return err 1101} 1102 1103// Example Unmarshaler shows how to decode TOML strings into your own 1104// custom data type. 1105func Example_unmarshaler() { 1106 blob := ` 1107[[song]] 1108name = "Thunder Road" 1109duration = "4m49s" 1110 1111[[song]] 1112name = "Stairway to Heaven" 1113duration = "8m03s" 1114` 1115 type song struct { 1116 Name string 1117 Duration duration 1118 } 1119 type songs struct { 1120 Song []song 1121 } 1122 var favorites songs 1123 if _, err := Decode(blob, &favorites); err != nil { 1124 log.Fatal(err) 1125 } 1126 1127 // Code to implement the TextUnmarshaler interface for `duration`: 1128 // 1129 // type duration struct { 1130 // time.Duration 1131 // } 1132 // 1133 // func (d *duration) UnmarshalText(text []byte) error { 1134 // var err error 1135 // d.Duration, err = time.ParseDuration(string(text)) 1136 // return err 1137 // } 1138 1139 for _, s := range favorites.Song { 1140 fmt.Printf("%s (%s)\n", s.Name, s.Duration) 1141 } 1142 // Output: 1143 // Thunder Road (4m49s) 1144 // Stairway to Heaven (8m3s) 1145} 1146 1147// Example StrictDecoding shows how to detect whether there are keys in the 1148// TOML document that weren't decoded into the value given. This is useful 1149// for returning an error to the user if they've included extraneous fields 1150// in their configuration. 1151func Example_strictDecoding() { 1152 var blob = ` 1153key1 = "value1" 1154key2 = "value2" 1155key3 = "value3" 1156` 1157 type config struct { 1158 Key1 string 1159 Key3 string 1160 } 1161 1162 var conf config 1163 md, err := Decode(blob, &conf) 1164 if err != nil { 1165 log.Fatal(err) 1166 } 1167 fmt.Printf("Undecoded keys: %q\n", md.Undecoded()) 1168 // Output: 1169 // Undecoded keys: ["key2"] 1170} 1171 1172// Example UnmarshalTOML shows how to implement a struct type that knows how to 1173// unmarshal itself. The struct must take full responsibility for mapping the 1174// values passed into the struct. The method may be used with interfaces in a 1175// struct in cases where the actual type is not known until the data is 1176// examined. 1177func Example_unmarshalTOML() { 1178 1179 var blob = ` 1180[[parts]] 1181type = "valve" 1182id = "valve-1" 1183size = 1.2 1184rating = 4 1185 1186[[parts]] 1187type = "valve" 1188id = "valve-2" 1189size = 2.1 1190rating = 5 1191 1192[[parts]] 1193type = "pipe" 1194id = "pipe-1" 1195length = 2.1 1196diameter = 12 1197 1198[[parts]] 1199type = "cable" 1200id = "cable-1" 1201length = 12 1202rating = 3.1 1203` 1204 o := &order{} 1205 err := Unmarshal([]byte(blob), o) 1206 if err != nil { 1207 log.Fatal(err) 1208 } 1209 1210 fmt.Println(len(o.parts)) 1211 1212 for _, part := range o.parts { 1213 fmt.Println(part.Name()) 1214 } 1215 1216 // Code to implement UmarshalJSON. 1217 1218 // type order struct { 1219 // // NOTE `order.parts` is a private slice of type `part` which is an 1220 // // interface and may only be loaded from toml using the 1221 // // UnmarshalTOML() method of the Umarshaler interface. 1222 // parts parts 1223 // } 1224 1225 // func (o *order) UnmarshalTOML(data interface{}) error { 1226 1227 // // NOTE the example below contains detailed type casting to show how 1228 // // the 'data' is retrieved. In operational use, a type cast wrapper 1229 // // may be preferred e.g. 1230 // // 1231 // // func AsMap(v interface{}) (map[string]interface{}, error) { 1232 // // return v.(map[string]interface{}) 1233 // // } 1234 // // 1235 // // resulting in: 1236 // // d, _ := AsMap(data) 1237 // // 1238 1239 // d, _ := data.(map[string]interface{}) 1240 // parts, _ := d["parts"].([]map[string]interface{}) 1241 1242 // for _, p := range parts { 1243 1244 // typ, _ := p["type"].(string) 1245 // id, _ := p["id"].(string) 1246 1247 // // detect the type of part and handle each case 1248 // switch p["type"] { 1249 // case "valve": 1250 1251 // size := float32(p["size"].(float64)) 1252 // rating := int(p["rating"].(int64)) 1253 1254 // valve := &valve{ 1255 // Type: typ, 1256 // ID: id, 1257 // Size: size, 1258 // Rating: rating, 1259 // } 1260 1261 // o.parts = append(o.parts, valve) 1262 1263 // case "pipe": 1264 1265 // length := float32(p["length"].(float64)) 1266 // diameter := int(p["diameter"].(int64)) 1267 1268 // pipe := &pipe{ 1269 // Type: typ, 1270 // ID: id, 1271 // Length: length, 1272 // Diameter: diameter, 1273 // } 1274 1275 // o.parts = append(o.parts, pipe) 1276 1277 // case "cable": 1278 1279 // length := int(p["length"].(int64)) 1280 // rating := float32(p["rating"].(float64)) 1281 1282 // cable := &cable{ 1283 // Type: typ, 1284 // ID: id, 1285 // Length: length, 1286 // Rating: rating, 1287 // } 1288 1289 // o.parts = append(o.parts, cable) 1290 1291 // } 1292 // } 1293 1294 // return nil 1295 // } 1296 1297 // type parts []part 1298 1299 // type part interface { 1300 // Name() string 1301 // } 1302 1303 // type valve struct { 1304 // Type string 1305 // ID string 1306 // Size float32 1307 // Rating int 1308 // } 1309 1310 // func (v *valve) Name() string { 1311 // return fmt.Sprintf("VALVE: %s", v.ID) 1312 // } 1313 1314 // type pipe struct { 1315 // Type string 1316 // ID string 1317 // Length float32 1318 // Diameter int 1319 // } 1320 1321 // func (p *pipe) Name() string { 1322 // return fmt.Sprintf("PIPE: %s", p.ID) 1323 // } 1324 1325 // type cable struct { 1326 // Type string 1327 // ID string 1328 // Length int 1329 // Rating float32 1330 // } 1331 1332 // func (c *cable) Name() string { 1333 // return fmt.Sprintf("CABLE: %s", c.ID) 1334 // } 1335 1336 // Output: 1337 // 4 1338 // VALVE: valve-1 1339 // VALVE: valve-2 1340 // PIPE: pipe-1 1341 // CABLE: cable-1 1342 1343} 1344 1345type order struct { 1346 // NOTE `order.parts` is a private slice of type `part` which is an 1347 // interface and may only be loaded from toml using the UnmarshalTOML() 1348 // method of the Umarshaler interface. 1349 parts parts 1350} 1351 1352func (o *order) UnmarshalTOML(data interface{}) error { 1353 1354 // NOTE the example below contains detailed type casting to show how 1355 // the 'data' is retrieved. In operational use, a type cast wrapper 1356 // may be preferred e.g. 1357 // 1358 // func AsMap(v interface{}) (map[string]interface{}, error) { 1359 // return v.(map[string]interface{}) 1360 // } 1361 // 1362 // resulting in: 1363 // d, _ := AsMap(data) 1364 // 1365 1366 d, _ := data.(map[string]interface{}) 1367 parts, _ := d["parts"].([]map[string]interface{}) 1368 1369 for _, p := range parts { 1370 1371 typ, _ := p["type"].(string) 1372 id, _ := p["id"].(string) 1373 1374 // detect the type of part and handle each case 1375 switch p["type"] { 1376 case "valve": 1377 1378 size := float32(p["size"].(float64)) 1379 rating := int(p["rating"].(int64)) 1380 1381 valve := &valve{ 1382 Type: typ, 1383 ID: id, 1384 Size: size, 1385 Rating: rating, 1386 } 1387 1388 o.parts = append(o.parts, valve) 1389 1390 case "pipe": 1391 1392 length := float32(p["length"].(float64)) 1393 diameter := int(p["diameter"].(int64)) 1394 1395 pipe := &pipe{ 1396 Type: typ, 1397 ID: id, 1398 Length: length, 1399 Diameter: diameter, 1400 } 1401 1402 o.parts = append(o.parts, pipe) 1403 1404 case "cable": 1405 1406 length := int(p["length"].(int64)) 1407 rating := float32(p["rating"].(float64)) 1408 1409 cable := &cable{ 1410 Type: typ, 1411 ID: id, 1412 Length: length, 1413 Rating: rating, 1414 } 1415 1416 o.parts = append(o.parts, cable) 1417 1418 } 1419 } 1420 1421 return nil 1422} 1423 1424type parts []part 1425 1426type part interface { 1427 Name() string 1428} 1429 1430type valve struct { 1431 Type string 1432 ID string 1433 Size float32 1434 Rating int 1435} 1436 1437func (v *valve) Name() string { 1438 return fmt.Sprintf("VALVE: %s", v.ID) 1439} 1440 1441type pipe struct { 1442 Type string 1443 ID string 1444 Length float32 1445 Diameter int 1446} 1447 1448func (p *pipe) Name() string { 1449 return fmt.Sprintf("PIPE: %s", p.ID) 1450} 1451 1452type cable struct { 1453 Type string 1454 ID string 1455 Length int 1456 Rating float32 1457} 1458 1459func (c *cable) Name() string { 1460 return fmt.Sprintf("CABLE: %s", c.ID) 1461} 1462