github.com/hashicorp/hcl/v2@v2.20.0/hcldec/public_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hcldec 5 6 import ( 7 "fmt" 8 "reflect" 9 "testing" 10 11 "github.com/hashicorp/hcl/v2" 12 "github.com/hashicorp/hcl/v2/hclsyntax" 13 "github.com/zclconf/go-cty/cty" 14 ) 15 16 func TestDecode(t *testing.T) { 17 tests := []struct { 18 config string 19 spec Spec 20 ctx *hcl.EvalContext 21 want cty.Value 22 diagCount int 23 }{ 24 { 25 ``, 26 &ObjectSpec{}, 27 nil, 28 cty.EmptyObjectVal, 29 0, 30 }, 31 { 32 "a = 1\n", 33 &ObjectSpec{}, 34 nil, 35 cty.EmptyObjectVal, 36 1, // attribute named "a" is not expected here 37 }, 38 { 39 "a = 1\n", 40 &ObjectSpec{ 41 "a": &AttrSpec{ 42 Name: "a", 43 Type: cty.Number, 44 }, 45 }, 46 nil, 47 cty.ObjectVal(map[string]cty.Value{ 48 "a": cty.NumberIntVal(1), 49 }), 50 0, 51 }, 52 { 53 "a = 1\n", 54 &AttrSpec{ 55 Name: "a", 56 Type: cty.Number, 57 }, 58 nil, 59 cty.NumberIntVal(1), 60 0, 61 }, 62 { 63 "a = 1\n", 64 &DefaultSpec{ 65 Primary: &AttrSpec{ 66 Name: "a", 67 Type: cty.Number, 68 }, 69 Default: &LiteralSpec{ 70 Value: cty.NumberIntVal(10), 71 }, 72 }, 73 nil, 74 cty.NumberIntVal(1), 75 0, 76 }, 77 { 78 "", 79 &DefaultSpec{ 80 Primary: &AttrSpec{ 81 Name: "a", 82 Type: cty.Number, 83 }, 84 Default: &LiteralSpec{ 85 Value: cty.NumberIntVal(10), 86 }, 87 }, 88 nil, 89 cty.NumberIntVal(10), 90 0, 91 }, 92 { 93 "a = 1\n", 94 ObjectSpec{ 95 "foo": &DefaultSpec{ 96 Primary: &AttrSpec{ 97 Name: "a", 98 Type: cty.Number, 99 }, 100 Default: &LiteralSpec{ 101 Value: cty.NumberIntVal(10), 102 }, 103 }, 104 }, 105 nil, 106 cty.ObjectVal(map[string]cty.Value{"foo": cty.NumberIntVal(1)}), 107 0, 108 }, 109 { 110 "a = \"1\"\n", 111 &AttrSpec{ 112 Name: "a", 113 Type: cty.Number, 114 }, 115 nil, 116 cty.NumberIntVal(1), 117 0, 118 }, 119 { 120 "a = true\n", 121 &AttrSpec{ 122 Name: "a", 123 Type: cty.Number, 124 }, 125 nil, 126 cty.UnknownVal(cty.Number), 127 1, // incorrect type - number required. 128 }, 129 { 130 ``, 131 &AttrSpec{ 132 Name: "a", 133 Type: cty.Number, 134 Required: true, 135 }, 136 nil, 137 cty.NullVal(cty.Number), 138 1, // attribute "a" is required 139 }, 140 { 141 ``, 142 &AttrSpec{ 143 Name: "a", 144 Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ 145 "attr": cty.String, 146 }, []string{"attr"}), 147 }, 148 nil, 149 cty.NullVal(cty.Object(map[string]cty.Type{ 150 "attr": cty.String, 151 })), 152 0, 153 }, 154 155 { 156 ` 157 b { 158 } 159 `, 160 &BlockSpec{ 161 TypeName: "b", 162 Nested: ObjectSpec{}, 163 }, 164 nil, 165 cty.EmptyObjectVal, 166 0, 167 }, 168 { 169 ` 170 b "baz" { 171 } 172 `, 173 &BlockSpec{ 174 TypeName: "b", 175 Nested: &BlockLabelSpec{ 176 Index: 0, 177 Name: "name", 178 }, 179 }, 180 nil, 181 cty.StringVal("baz"), 182 0, 183 }, 184 { 185 ` 186 b "baz" {} 187 b "foo" {} 188 `, 189 &BlockSpec{ 190 TypeName: "b", 191 Nested: &BlockLabelSpec{ 192 Index: 0, 193 Name: "name", 194 }, 195 }, 196 nil, 197 cty.StringVal("baz"), 198 1, // duplicate "b" block 199 }, 200 { 201 ` 202 b { 203 } 204 `, 205 &BlockSpec{ 206 TypeName: "b", 207 Nested: &BlockLabelSpec{ 208 Index: 0, 209 Name: "name", 210 }, 211 }, 212 nil, 213 cty.NullVal(cty.String), 214 1, // missing name label 215 }, 216 { 217 ``, 218 &BlockSpec{ 219 TypeName: "b", 220 Nested: ObjectSpec{}, 221 }, 222 nil, 223 cty.NullVal(cty.EmptyObject), 224 0, 225 }, 226 { 227 "a {}\n", 228 &BlockSpec{ 229 TypeName: "b", 230 Nested: ObjectSpec{}, 231 }, 232 nil, 233 cty.NullVal(cty.EmptyObject), 234 1, // blocks of type "a" are not supported 235 }, 236 { 237 ``, 238 &BlockSpec{ 239 TypeName: "b", 240 Nested: ObjectSpec{}, 241 Required: true, 242 }, 243 nil, 244 cty.NullVal(cty.EmptyObject), 245 1, // a block of type "b" is required 246 }, 247 { 248 ` 249 b {} 250 b {} 251 `, 252 &BlockSpec{ 253 TypeName: "b", 254 Nested: ObjectSpec{}, 255 Required: true, 256 }, 257 nil, 258 cty.EmptyObjectVal, 259 1, // only one "b" block is allowed 260 }, 261 { 262 ` 263 b { 264 } 265 `, 266 &BlockAttrsSpec{ 267 TypeName: "b", 268 ElementType: cty.String, 269 }, 270 nil, 271 cty.MapValEmpty(cty.String), 272 0, 273 }, 274 { 275 ` 276 b { 277 hello = "world" 278 } 279 `, 280 &BlockAttrsSpec{ 281 TypeName: "b", 282 ElementType: cty.String, 283 }, 284 nil, 285 cty.MapVal(map[string]cty.Value{ 286 "hello": cty.StringVal("world"), 287 }), 288 0, 289 }, 290 { 291 ` 292 b { 293 hello = true 294 } 295 `, 296 &BlockAttrsSpec{ 297 TypeName: "b", 298 ElementType: cty.String, 299 }, 300 nil, 301 cty.MapVal(map[string]cty.Value{ 302 "hello": cty.StringVal("true"), 303 }), 304 0, 305 }, 306 { 307 ` 308 b { 309 hello = true 310 goodbye = 5 311 } 312 `, 313 &BlockAttrsSpec{ 314 TypeName: "b", 315 ElementType: cty.String, 316 }, 317 nil, 318 cty.MapVal(map[string]cty.Value{ 319 "hello": cty.StringVal("true"), 320 "goodbye": cty.StringVal("5"), 321 }), 322 0, 323 }, 324 { 325 ``, 326 &BlockAttrsSpec{ 327 TypeName: "b", 328 ElementType: cty.String, 329 }, 330 nil, 331 cty.NullVal(cty.Map(cty.String)), 332 0, 333 }, 334 { 335 ``, 336 &BlockAttrsSpec{ 337 TypeName: "b", 338 ElementType: cty.String, 339 Required: true, 340 }, 341 nil, 342 cty.NullVal(cty.Map(cty.String)), 343 1, // missing b block 344 }, 345 { 346 ``, 347 &BlockAttrsSpec{ 348 TypeName: "b", 349 ElementType: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ 350 "attr": cty.String, 351 }, []string{"attr"}), 352 }, 353 nil, 354 cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ 355 "attr": cty.String, 356 }))), 357 0, 358 }, 359 { 360 ` 361 b { 362 } 363 b { 364 } 365 `, 366 &BlockAttrsSpec{ 367 TypeName: "b", 368 ElementType: cty.String, 369 }, 370 nil, 371 cty.MapValEmpty(cty.String), 372 1, // duplicate b block 373 }, 374 { 375 ` 376 b { 377 } 378 b { 379 } 380 `, 381 &BlockAttrsSpec{ 382 TypeName: "b", 383 ElementType: cty.String, 384 Required: true, 385 }, 386 nil, 387 cty.MapValEmpty(cty.String), 388 1, // duplicate b block 389 }, 390 { 391 ` 392 b {} 393 b {} 394 `, 395 &BlockListSpec{ 396 TypeName: "b", 397 Nested: ObjectSpec{}, 398 }, 399 nil, 400 cty.ListVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}), 401 0, 402 }, 403 { 404 ``, 405 &BlockListSpec{ 406 TypeName: "b", 407 Nested: ObjectSpec{}, 408 }, 409 nil, 410 cty.ListValEmpty(cty.EmptyObject), 411 0, 412 }, 413 { 414 ` 415 b "foo" {} 416 b "bar" {} 417 `, 418 &BlockListSpec{ 419 TypeName: "b", 420 Nested: &BlockLabelSpec{ 421 Name: "name", 422 Index: 0, 423 }, 424 }, 425 nil, 426 cty.ListVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), 427 0, 428 }, 429 { 430 ` 431 b {} 432 b {} 433 b {} 434 `, 435 &BlockListSpec{ 436 TypeName: "b", 437 Nested: ObjectSpec{}, 438 MaxItems: 2, 439 }, 440 nil, 441 cty.ListVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal, cty.EmptyObjectVal}), 442 1, // too many b blocks 443 }, 444 { 445 ` 446 b {} 447 b {} 448 `, 449 &BlockListSpec{ 450 TypeName: "b", 451 Nested: ObjectSpec{}, 452 MinItems: 10, 453 }, 454 nil, 455 cty.ListVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}), 456 1, // insufficient b blocks 457 }, 458 { 459 ` 460 b { 461 a = true 462 } 463 b { 464 a = 1 465 } 466 `, 467 &BlockListSpec{ 468 TypeName: "b", 469 Nested: &AttrSpec{ 470 Name: "a", 471 Type: cty.DynamicPseudoType, 472 }, 473 }, 474 nil, 475 cty.DynamicVal, 476 1, // Unconsistent argument types in b blocks 477 }, 478 { 479 ` 480 b { 481 a = true 482 } 483 b { 484 a = "not a bool" 485 } 486 `, 487 &BlockListSpec{ 488 TypeName: "b", 489 Nested: &AttrSpec{ 490 Name: "a", 491 Type: cty.DynamicPseudoType, 492 }, 493 }, 494 nil, 495 cty.ListVal([]cty.Value{ 496 cty.StringVal("true"), // type unification generalizes all the values to strings 497 cty.StringVal("not a bool"), 498 }), 499 0, 500 }, 501 { 502 ` 503 b {} 504 b {} 505 `, 506 &BlockSetSpec{ 507 TypeName: "b", 508 Nested: ObjectSpec{}, 509 MaxItems: 2, 510 }, 511 nil, 512 cty.SetVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}), 513 0, 514 }, 515 { 516 ` 517 b "foo" "bar" {} 518 b "bar" "baz" {} 519 `, 520 &BlockSetSpec{ 521 TypeName: "b", 522 Nested: TupleSpec{ 523 &BlockLabelSpec{ 524 Name: "name", 525 Index: 1, 526 }, 527 &BlockLabelSpec{ 528 Name: "type", 529 Index: 0, 530 }, 531 }, 532 }, 533 nil, 534 cty.SetVal([]cty.Value{ 535 cty.TupleVal([]cty.Value{cty.StringVal("bar"), cty.StringVal("foo")}), 536 cty.TupleVal([]cty.Value{cty.StringVal("baz"), cty.StringVal("bar")}), 537 }), 538 0, 539 }, 540 { 541 ` 542 b { 543 a = true 544 } 545 b { 546 a = 1 547 } 548 `, 549 &BlockSetSpec{ 550 TypeName: "b", 551 Nested: &AttrSpec{ 552 Name: "a", 553 Type: cty.DynamicPseudoType, 554 }, 555 }, 556 nil, 557 cty.DynamicVal, 558 1, // Unconsistent argument types in b blocks 559 }, 560 { 561 ` 562 b { 563 a = true 564 } 565 b { 566 a = "not a bool" 567 } 568 `, 569 &BlockSetSpec{ 570 TypeName: "b", 571 Nested: &AttrSpec{ 572 Name: "a", 573 Type: cty.DynamicPseudoType, 574 }, 575 }, 576 nil, 577 cty.SetVal([]cty.Value{ 578 cty.StringVal("true"), // type unification generalizes all the values to strings 579 cty.StringVal("not a bool"), 580 }), 581 0, 582 }, 583 { 584 ` 585 b "foo" {} 586 b "bar" {} 587 `, 588 &BlockMapSpec{ 589 TypeName: "b", 590 LabelNames: []string{"key"}, 591 Nested: ObjectSpec{}, 592 }, 593 nil, 594 cty.MapVal(map[string]cty.Value{"foo": cty.EmptyObjectVal, "bar": cty.EmptyObjectVal}), 595 0, 596 }, 597 { 598 ` 599 b "foo" "bar" {} 600 b "bar" "baz" {} 601 `, 602 &BlockMapSpec{ 603 TypeName: "b", 604 LabelNames: []string{"key1", "key2"}, 605 Nested: ObjectSpec{}, 606 }, 607 nil, 608 cty.MapVal(map[string]cty.Value{ 609 "foo": cty.MapVal(map[string]cty.Value{ 610 "bar": cty.EmptyObjectVal, 611 }), 612 "bar": cty.MapVal(map[string]cty.Value{ 613 "baz": cty.EmptyObjectVal, 614 }), 615 }), 616 0, 617 }, 618 { 619 ` 620 b "foo" "bar" {} 621 b "bar" "bar" {} 622 `, 623 &BlockMapSpec{ 624 TypeName: "b", 625 LabelNames: []string{"key1", "key2"}, 626 Nested: ObjectSpec{}, 627 }, 628 nil, 629 cty.MapVal(map[string]cty.Value{ 630 "foo": cty.MapVal(map[string]cty.Value{ 631 "bar": cty.EmptyObjectVal, 632 }), 633 "bar": cty.MapVal(map[string]cty.Value{ 634 "bar": cty.EmptyObjectVal, 635 }), 636 }), 637 0, 638 }, 639 { 640 ` 641 b "foo" "bar" {} 642 b "foo" "baz" {} 643 `, 644 &BlockMapSpec{ 645 TypeName: "b", 646 LabelNames: []string{"key1", "key2"}, 647 Nested: ObjectSpec{}, 648 }, 649 nil, 650 cty.MapVal(map[string]cty.Value{ 651 "foo": cty.MapVal(map[string]cty.Value{ 652 "bar": cty.EmptyObjectVal, 653 "baz": cty.EmptyObjectVal, 654 }), 655 }), 656 0, 657 }, 658 { 659 ` 660 b "foo" "bar" {} 661 `, 662 &BlockMapSpec{ 663 TypeName: "b", 664 LabelNames: []string{"key"}, 665 Nested: ObjectSpec{}, 666 }, 667 nil, 668 cty.MapValEmpty(cty.EmptyObject), 669 1, // too many labels 670 }, 671 { 672 ` 673 b "bar" {} 674 `, 675 &BlockMapSpec{ 676 TypeName: "b", 677 LabelNames: []string{"key1", "key2"}, 678 Nested: ObjectSpec{}, 679 }, 680 nil, 681 cty.MapValEmpty(cty.EmptyObject), 682 1, // not enough labels 683 }, 684 { 685 ` 686 b "foo" {} 687 b "foo" {} 688 `, 689 &BlockMapSpec{ 690 TypeName: "b", 691 LabelNames: []string{"key"}, 692 Nested: ObjectSpec{}, 693 }, 694 nil, 695 cty.MapVal(map[string]cty.Value{"foo": cty.EmptyObjectVal}), 696 1, // duplicate b block 697 }, 698 { 699 ` 700 b "foo" "bar" {} 701 b "foo" "bar" {} 702 `, 703 &BlockMapSpec{ 704 TypeName: "b", 705 LabelNames: []string{"key1", "key2"}, 706 Nested: ObjectSpec{}, 707 }, 708 nil, 709 cty.MapVal(map[string]cty.Value{"foo": cty.MapVal(map[string]cty.Value{"bar": cty.EmptyObjectVal})}), 710 1, // duplicate b block 711 }, 712 { 713 ` 714 b "foo" "bar" {} 715 b "bar" "baz" {} 716 `, 717 &BlockMapSpec{ 718 TypeName: "b", 719 LabelNames: []string{"type"}, 720 Nested: &BlockLabelSpec{ 721 Name: "name", 722 Index: 0, 723 }, 724 }, 725 nil, 726 cty.MapVal(map[string]cty.Value{ 727 "foo": cty.StringVal("bar"), 728 "bar": cty.StringVal("baz"), 729 }), 730 0, 731 }, 732 { 733 ` 734 b "foo" {} 735 `, 736 &BlockMapSpec{ 737 TypeName: "b", 738 LabelNames: []string{"type"}, 739 Nested: &BlockLabelSpec{ 740 Name: "name", 741 Index: 0, 742 }, 743 }, 744 nil, 745 cty.MapValEmpty(cty.String), 746 1, // missing name 747 }, 748 { 749 ` 750 b {} 751 b {} 752 `, 753 &BlockTupleSpec{ 754 TypeName: "b", 755 Nested: ObjectSpec{}, 756 }, 757 nil, 758 cty.TupleVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}), 759 0, 760 }, 761 { 762 ``, 763 &BlockTupleSpec{ 764 TypeName: "b", 765 Nested: ObjectSpec{}, 766 }, 767 nil, 768 cty.EmptyTupleVal, 769 0, 770 }, 771 { 772 ` 773 b "foo" {} 774 b "bar" {} 775 `, 776 &BlockTupleSpec{ 777 TypeName: "b", 778 Nested: &BlockLabelSpec{ 779 Name: "name", 780 Index: 0, 781 }, 782 }, 783 nil, 784 cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), 785 0, 786 }, 787 { 788 ` 789 b {} 790 b {} 791 b {} 792 `, 793 &BlockTupleSpec{ 794 TypeName: "b", 795 Nested: ObjectSpec{}, 796 MaxItems: 2, 797 }, 798 nil, 799 cty.TupleVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal, cty.EmptyObjectVal}), 800 1, // too many b blocks 801 }, 802 { 803 ` 804 b {} 805 b {} 806 `, 807 &BlockTupleSpec{ 808 TypeName: "b", 809 Nested: ObjectSpec{}, 810 MinItems: 10, 811 }, 812 nil, 813 cty.TupleVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}), 814 1, // insufficient b blocks 815 }, 816 { 817 ` 818 b { 819 a = true 820 } 821 b { 822 a = 1 823 } 824 `, 825 &BlockTupleSpec{ 826 TypeName: "b", 827 Nested: &AttrSpec{ 828 Name: "a", 829 Type: cty.DynamicPseudoType, 830 }, 831 }, 832 nil, 833 cty.TupleVal([]cty.Value{ 834 cty.True, 835 cty.NumberIntVal(1), 836 }), 837 0, 838 }, 839 { 840 ` 841 b { 842 a = true 843 } 844 b { 845 a = "not a bool" 846 } 847 `, 848 &BlockTupleSpec{ 849 TypeName: "b", 850 Nested: &AttrSpec{ 851 Name: "a", 852 Type: cty.DynamicPseudoType, 853 }, 854 }, 855 nil, 856 cty.TupleVal([]cty.Value{ 857 cty.True, 858 cty.StringVal("not a bool"), 859 }), 860 0, 861 }, 862 { 863 ` 864 b "foo" {} 865 b "bar" {} 866 `, 867 &BlockObjectSpec{ 868 TypeName: "b", 869 LabelNames: []string{"key"}, 870 Nested: ObjectSpec{}, 871 }, 872 nil, 873 cty.ObjectVal(map[string]cty.Value{"foo": cty.EmptyObjectVal, "bar": cty.EmptyObjectVal}), 874 0, 875 }, 876 { 877 ` 878 b "foo" "bar" {} 879 b "bar" "baz" {} 880 `, 881 &BlockObjectSpec{ 882 TypeName: "b", 883 LabelNames: []string{"key1", "key2"}, 884 Nested: ObjectSpec{}, 885 }, 886 nil, 887 cty.ObjectVal(map[string]cty.Value{ 888 "foo": cty.ObjectVal(map[string]cty.Value{ 889 "bar": cty.EmptyObjectVal, 890 }), 891 "bar": cty.ObjectVal(map[string]cty.Value{ 892 "baz": cty.EmptyObjectVal, 893 }), 894 }), 895 0, 896 }, 897 { 898 ` 899 b "foo" "bar" {} 900 b "bar" "bar" {} 901 `, 902 &BlockObjectSpec{ 903 TypeName: "b", 904 LabelNames: []string{"key1", "key2"}, 905 Nested: ObjectSpec{}, 906 }, 907 nil, 908 cty.ObjectVal(map[string]cty.Value{ 909 "foo": cty.ObjectVal(map[string]cty.Value{ 910 "bar": cty.EmptyObjectVal, 911 }), 912 "bar": cty.ObjectVal(map[string]cty.Value{ 913 "bar": cty.EmptyObjectVal, 914 }), 915 }), 916 0, 917 }, 918 { 919 ` 920 b "foo" "bar" {} 921 b "foo" "baz" {} 922 `, 923 &BlockObjectSpec{ 924 TypeName: "b", 925 LabelNames: []string{"key1", "key2"}, 926 Nested: ObjectSpec{}, 927 }, 928 nil, 929 cty.ObjectVal(map[string]cty.Value{ 930 "foo": cty.ObjectVal(map[string]cty.Value{ 931 "bar": cty.EmptyObjectVal, 932 "baz": cty.EmptyObjectVal, 933 }), 934 }), 935 0, 936 }, 937 { 938 ` 939 b "foo" "bar" {} 940 `, 941 &BlockObjectSpec{ 942 TypeName: "b", 943 LabelNames: []string{"key"}, 944 Nested: ObjectSpec{}, 945 }, 946 nil, 947 cty.EmptyObjectVal, 948 1, // too many labels 949 }, 950 { 951 ` 952 b "bar" {} 953 `, 954 &BlockObjectSpec{ 955 TypeName: "b", 956 LabelNames: []string{"key1", "key2"}, 957 Nested: ObjectSpec{}, 958 }, 959 nil, 960 cty.EmptyObjectVal, 961 1, // not enough labels 962 }, 963 { 964 ` 965 b "foo" {} 966 b "foo" {} 967 `, 968 &BlockObjectSpec{ 969 TypeName: "b", 970 LabelNames: []string{"key"}, 971 Nested: ObjectSpec{}, 972 }, 973 nil, 974 cty.ObjectVal(map[string]cty.Value{"foo": cty.EmptyObjectVal}), 975 1, // duplicate b block 976 }, 977 { 978 ` 979 b "foo" "bar" {} 980 b "foo" "bar" {} 981 `, 982 &BlockObjectSpec{ 983 TypeName: "b", 984 LabelNames: []string{"key1", "key2"}, 985 Nested: ObjectSpec{}, 986 }, 987 nil, 988 cty.ObjectVal(map[string]cty.Value{"foo": cty.ObjectVal(map[string]cty.Value{"bar": cty.EmptyObjectVal})}), 989 1, // duplicate b block 990 }, 991 { 992 ` 993 b "foo" "bar" {} 994 b "bar" "baz" {} 995 `, 996 &BlockObjectSpec{ 997 TypeName: "b", 998 LabelNames: []string{"type"}, 999 Nested: &BlockLabelSpec{ 1000 Name: "name", 1001 Index: 0, 1002 }, 1003 }, 1004 nil, 1005 cty.ObjectVal(map[string]cty.Value{ 1006 "foo": cty.StringVal("bar"), 1007 "bar": cty.StringVal("baz"), 1008 }), 1009 0, 1010 }, 1011 { 1012 ` 1013 b "foo" {} 1014 `, 1015 &BlockObjectSpec{ 1016 TypeName: "b", 1017 LabelNames: []string{"type"}, 1018 Nested: &BlockLabelSpec{ 1019 Name: "name", 1020 Index: 0, 1021 }, 1022 }, 1023 nil, 1024 cty.EmptyObjectVal, 1025 1, // missing name 1026 }, 1027 { 1028 ` 1029 b "foo" { 1030 arg = true 1031 } 1032 b "bar" { 1033 arg = 1 1034 } 1035 `, 1036 &BlockObjectSpec{ 1037 TypeName: "b", 1038 LabelNames: []string{"type"}, 1039 Nested: &AttrSpec{ 1040 Name: "arg", 1041 Type: cty.DynamicPseudoType, 1042 }, 1043 }, 1044 nil, 1045 cty.ObjectVal(map[string]cty.Value{ 1046 "foo": cty.True, 1047 "bar": cty.NumberIntVal(1), 1048 }), 1049 0, 1050 }, 1051 } 1052 1053 for i, test := range tests { 1054 t.Run(fmt.Sprintf("%02d-%s", i, test.config), func(t *testing.T) { 1055 file, parseDiags := hclsyntax.ParseConfig([]byte(test.config), "", hcl.Pos{Line: 1, Column: 1, Byte: 0}) 1056 body := file.Body 1057 got, valDiags := Decode(body, test.spec, test.ctx) 1058 1059 var diags hcl.Diagnostics 1060 diags = append(diags, parseDiags...) 1061 diags = append(diags, valDiags...) 1062 1063 if len(diags) != test.diagCount { 1064 t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount) 1065 for _, diag := range diags { 1066 t.Logf(" - %s", diag.Error()) 1067 } 1068 } 1069 1070 if !got.RawEquals(test.want) { 1071 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.want) 1072 } 1073 }) 1074 } 1075 } 1076 1077 func TestSourceRange(t *testing.T) { 1078 tests := []struct { 1079 config string 1080 spec Spec 1081 want hcl.Range 1082 }{ 1083 { 1084 "a = 1\n", 1085 &AttrSpec{ 1086 Name: "a", 1087 }, 1088 hcl.Range{ 1089 Start: hcl.Pos{Line: 1, Column: 5, Byte: 4}, 1090 End: hcl.Pos{Line: 1, Column: 6, Byte: 5}, 1091 }, 1092 }, 1093 { 1094 ` 1095 b { 1096 a = 1 1097 } 1098 `, 1099 &BlockSpec{ 1100 TypeName: "b", 1101 Nested: &AttrSpec{ 1102 Name: "a", 1103 }, 1104 }, 1105 hcl.Range{ 1106 Start: hcl.Pos{Line: 3, Column: 7, Byte: 11}, 1107 End: hcl.Pos{Line: 3, Column: 8, Byte: 12}, 1108 }, 1109 }, 1110 { 1111 ` 1112 b { 1113 c { 1114 a = 1 1115 } 1116 } 1117 `, 1118 &BlockSpec{ 1119 TypeName: "b", 1120 Nested: &BlockSpec{ 1121 TypeName: "c", 1122 Nested: &AttrSpec{ 1123 Name: "a", 1124 }, 1125 }, 1126 }, 1127 hcl.Range{ 1128 Start: hcl.Pos{Line: 4, Column: 9, Byte: 19}, 1129 End: hcl.Pos{Line: 4, Column: 10, Byte: 20}, 1130 }, 1131 }, 1132 } 1133 1134 for i, test := range tests { 1135 t.Run(fmt.Sprintf("%02d-%s", i, test.config), func(t *testing.T) { 1136 file, diags := hclsyntax.ParseConfig([]byte(test.config), "", hcl.Pos{Line: 1, Column: 1, Byte: 0}) 1137 if len(diags) != 0 { 1138 t.Errorf("wrong number of diagnostics %d; want %d", len(diags), 0) 1139 for _, diag := range diags { 1140 t.Logf(" - %s", diag.Error()) 1141 } 1142 } 1143 body := file.Body 1144 1145 got := SourceRange(body, test.spec) 1146 1147 if !reflect.DeepEqual(got, test.want) { 1148 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.want) 1149 } 1150 }) 1151 } 1152 1153 }