OSDN Git Service

Hulk did something
[bytom/vapor.git] / vendor / github.com / hashicorp / hcl / decoder_test.go
1 package hcl
2
3 import (
4         "io/ioutil"
5         "path/filepath"
6         "reflect"
7         "testing"
8         "time"
9
10         "github.com/davecgh/go-spew/spew"
11         "github.com/hashicorp/hcl/hcl/ast"
12 )
13
14 func TestDecode_interface(t *testing.T) {
15         cases := []struct {
16                 File string
17                 Err  bool
18                 Out  interface{}
19         }{
20                 {
21                         "basic.hcl",
22                         false,
23                         map[string]interface{}{
24                                 "foo": "bar",
25                                 "bar": "${file(\"bing/bong.txt\")}",
26                         },
27                 },
28                 {
29                         "basic_squish.hcl",
30                         false,
31                         map[string]interface{}{
32                                 "foo":     "bar",
33                                 "bar":     "${file(\"bing/bong.txt\")}",
34                                 "foo-bar": "baz",
35                         },
36                 },
37                 {
38                         "empty.hcl",
39                         false,
40                         map[string]interface{}{
41                                 "resource": []map[string]interface{}{
42                                         map[string]interface{}{
43                                                 "foo": []map[string]interface{}{
44                                                         map[string]interface{}{},
45                                                 },
46                                         },
47                                 },
48                         },
49                 },
50                 {
51                         "tfvars.hcl",
52                         false,
53                         map[string]interface{}{
54                                 "regularvar": "Should work",
55                                 "map.key1":   "Value",
56                                 "map.key2":   "Other value",
57                         },
58                 },
59                 {
60                         "escape.hcl",
61                         false,
62                         map[string]interface{}{
63                                 "foo":          "bar\"baz\\n",
64                                 "qux":          "back\\slash",
65                                 "bar":          "new\nline",
66                                 "qax":          `slash\:colon`,
67                                 "nested":       `${HH\\:mm\\:ss}`,
68                                 "nestedquotes": `${"\"stringwrappedinquotes\""}`,
69                         },
70                 },
71                 {
72                         "float.hcl",
73                         false,
74                         map[string]interface{}{
75                                 "a": 1.02,
76                                 "b": 2,
77                         },
78                 },
79                 {
80                         "multiline_bad.hcl",
81                         true,
82                         nil,
83                 },
84                 {
85                         "multiline_literal.hcl",
86                         true,
87                         nil,
88                 },
89                 {
90                         "multiline_literal_with_hil.hcl",
91                         false,
92                         map[string]interface{}{"multiline_literal_with_hil": "${hello\n  world}"},
93                 },
94                 {
95                         "multiline_no_marker.hcl",
96                         true,
97                         nil,
98                 },
99                 {
100                         "multiline.hcl",
101                         false,
102                         map[string]interface{}{"foo": "bar\nbaz\n"},
103                 },
104                 {
105                         "multiline_indented.hcl",
106                         false,
107                         map[string]interface{}{"foo": "  bar\n  baz\n"},
108                 },
109                 {
110                         "multiline_no_hanging_indent.hcl",
111                         false,
112                         map[string]interface{}{"foo": "  baz\n    bar\n      foo\n"},
113                 },
114                 {
115                         "multiline_no_eof.hcl",
116                         false,
117                         map[string]interface{}{"foo": "bar\nbaz\n", "key": "value"},
118                 },
119                 {
120                         "multiline.json",
121                         false,
122                         map[string]interface{}{"foo": "bar\nbaz"},
123                 },
124                 {
125                         "null_strings.json",
126                         false,
127                         map[string]interface{}{
128                                 "module": []map[string]interface{}{
129                                         map[string]interface{}{
130                                                 "app": []map[string]interface{}{
131                                                         map[string]interface{}{"foo": ""},
132                                                 },
133                                         },
134                                 },
135                         },
136                 },
137                 {
138                         "scientific.json",
139                         false,
140                         map[string]interface{}{
141                                 "a": 1e-10,
142                                 "b": 1e+10,
143                                 "c": 1e10,
144                                 "d": 1.2e-10,
145                                 "e": 1.2e+10,
146                                 "f": 1.2e10,
147                         },
148                 },
149                 {
150                         "scientific.hcl",
151                         false,
152                         map[string]interface{}{
153                                 "a": 1e-10,
154                                 "b": 1e+10,
155                                 "c": 1e10,
156                                 "d": 1.2e-10,
157                                 "e": 1.2e+10,
158                                 "f": 1.2e10,
159                         },
160                 },
161                 {
162                         "terraform_heroku.hcl",
163                         false,
164                         map[string]interface{}{
165                                 "name": "terraform-test-app",
166                                 "config_vars": []map[string]interface{}{
167                                         map[string]interface{}{
168                                                 "FOO": "bar",
169                                         },
170                                 },
171                         },
172                 },
173                 {
174                         "structure_multi.hcl",
175                         false,
176                         map[string]interface{}{
177                                 "foo": []map[string]interface{}{
178                                         map[string]interface{}{
179                                                 "baz": []map[string]interface{}{
180                                                         map[string]interface{}{"key": 7},
181                                                 },
182                                         },
183                                         map[string]interface{}{
184                                                 "bar": []map[string]interface{}{
185                                                         map[string]interface{}{"key": 12},
186                                                 },
187                                         },
188                                 },
189                         },
190                 },
191                 {
192                         "structure_multi.json",
193                         false,
194                         map[string]interface{}{
195                                 "foo": []map[string]interface{}{
196                                         map[string]interface{}{
197                                                 "baz": []map[string]interface{}{
198                                                         map[string]interface{}{"key": 7},
199                                                 },
200                                         },
201                                         map[string]interface{}{
202                                                 "bar": []map[string]interface{}{
203                                                         map[string]interface{}{"key": 12},
204                                                 },
205                                         },
206                                 },
207                         },
208                 },
209                 {
210                         "list_of_lists.hcl",
211                         false,
212                         map[string]interface{}{
213                                 "foo": []interface{}{
214                                         []interface{}{"foo"},
215                                         []interface{}{"bar"},
216                                 },
217                         },
218                 },
219                 {
220                         "list_of_maps.hcl",
221                         false,
222                         map[string]interface{}{
223                                 "foo": []interface{}{
224                                         map[string]interface{}{"somekey1": "someval1"},
225                                         map[string]interface{}{"somekey2": "someval2", "someextrakey": "someextraval"},
226                                 },
227                         },
228                 },
229                 {
230                         "assign_deep.hcl",
231                         false,
232                         map[string]interface{}{
233                                 "resource": []interface{}{
234                                         map[string]interface{}{
235                                                 "foo": []interface{}{
236                                                         map[string]interface{}{
237                                                                 "bar": []map[string]interface{}{
238                                                                         map[string]interface{}{}}}}}}},
239                 },
240                 {
241                         "structure_list.hcl",
242                         false,
243                         map[string]interface{}{
244                                 "foo": []map[string]interface{}{
245                                         map[string]interface{}{
246                                                 "key": 7,
247                                         },
248                                         map[string]interface{}{
249                                                 "key": 12,
250                                         },
251                                 },
252                         },
253                 },
254                 {
255                         "structure_list.json",
256                         false,
257                         map[string]interface{}{
258                                 "foo": []map[string]interface{}{
259                                         map[string]interface{}{
260                                                 "key": 7,
261                                         },
262                                         map[string]interface{}{
263                                                 "key": 12,
264                                         },
265                                 },
266                         },
267                 },
268                 {
269                         "structure_list_deep.json",
270                         false,
271                         map[string]interface{}{
272                                 "bar": []map[string]interface{}{
273                                         map[string]interface{}{
274                                                 "foo": []map[string]interface{}{
275                                                         map[string]interface{}{
276                                                                 "name": "terraform_example",
277                                                                 "ingress": []map[string]interface{}{
278                                                                         map[string]interface{}{
279                                                                                 "from_port": 22,
280                                                                         },
281                                                                         map[string]interface{}{
282                                                                                 "from_port": 80,
283                                                                         },
284                                                                 },
285                                                         },
286                                                 },
287                                         },
288                                 },
289                         },
290                 },
291
292                 {
293                         "structure_list_empty.json",
294                         false,
295                         map[string]interface{}{
296                                 "foo": []interface{}{},
297                         },
298                 },
299
300                 {
301                         "nested_block_comment.hcl",
302                         false,
303                         map[string]interface{}{
304                                 "bar": "value",
305                         },
306                 },
307
308                 {
309                         "unterminated_block_comment.hcl",
310                         true,
311                         nil,
312                 },
313
314                 {
315                         "unterminated_brace.hcl",
316                         true,
317                         nil,
318                 },
319
320                 {
321                         "nested_provider_bad.hcl",
322                         true,
323                         nil,
324                 },
325
326                 {
327                         "object_list.json",
328                         false,
329                         map[string]interface{}{
330                                 "resource": []map[string]interface{}{
331                                         map[string]interface{}{
332                                                 "aws_instance": []map[string]interface{}{
333                                                         map[string]interface{}{
334                                                                 "db": []map[string]interface{}{
335                                                                         map[string]interface{}{
336                                                                                 "vpc": "foo",
337                                                                                 "provisioner": []map[string]interface{}{
338                                                                                         map[string]interface{}{
339                                                                                                 "file": []map[string]interface{}{
340                                                                                                         map[string]interface{}{
341                                                                                                                 "source":      "foo",
342                                                                                                                 "destination": "bar",
343                                                                                                         },
344                                                                                                 },
345                                                                                         },
346                                                                                 },
347                                                                         },
348                                                                 },
349                                                         },
350                                                 },
351                                         },
352                                 },
353                         },
354                 },
355
356                 // Terraform GH-8295 sanity test that basic decoding into
357                 // interface{} works.
358                 {
359                         "terraform_variable_invalid.json",
360                         false,
361                         map[string]interface{}{
362                                 "variable": []map[string]interface{}{
363                                         map[string]interface{}{
364                                                 "whatever": "abc123",
365                                         },
366                                 },
367                         },
368                 },
369
370                 {
371                         "interpolate.json",
372                         false,
373                         map[string]interface{}{
374                                 "default": `${replace("europe-west", "-", " ")}`,
375                         },
376                 },
377
378                 {
379                         "block_assign.hcl",
380                         true,
381                         nil,
382                 },
383
384                 {
385                         "escape_backslash.hcl",
386                         false,
387                         map[string]interface{}{
388                                 "output": []map[string]interface{}{
389                                         map[string]interface{}{
390                                                 "one":  `${replace(var.sub_domain, ".", "\\.")}`,
391                                                 "two":  `${replace(var.sub_domain, ".", "\\\\.")}`,
392                                                 "many": `${replace(var.sub_domain, ".", "\\\\\\\\.")}`,
393                                         },
394                                 },
395                         },
396                 },
397
398                 {
399                         "git_crypt.hcl",
400                         true,
401                         nil,
402                 },
403
404                 {
405                         "object_with_bool.hcl",
406                         false,
407                         map[string]interface{}{
408                                 "path": []map[string]interface{}{
409                                         map[string]interface{}{
410                                                 "policy": "write",
411                                                 "permissions": []map[string]interface{}{
412                                                         map[string]interface{}{
413                                                                 "bool": []interface{}{false},
414                                                         },
415                                                 },
416                                         },
417                                 },
418                         },
419                 },
420         }
421
422         for _, tc := range cases {
423                 t.Run(tc.File, func(t *testing.T) {
424                         d, err := ioutil.ReadFile(filepath.Join(fixtureDir, tc.File))
425                         if err != nil {
426                                 t.Fatalf("err: %s", err)
427                         }
428
429                         var out interface{}
430                         err = Decode(&out, string(d))
431                         if (err != nil) != tc.Err {
432                                 t.Fatalf("Input: %s\n\nError: %s", tc.File, err)
433                         }
434
435                         if !reflect.DeepEqual(out, tc.Out) {
436                                 t.Fatalf("Input: %s. Actual, Expected.\n\n%#v\n\n%#v", tc.File, out, tc.Out)
437                         }
438
439                         var v interface{}
440                         err = Unmarshal(d, &v)
441                         if (err != nil) != tc.Err {
442                                 t.Fatalf("Input: %s\n\nError: %s", tc.File, err)
443                         }
444
445                         if !reflect.DeepEqual(v, tc.Out) {
446                                 t.Fatalf("Input: %s. Actual, Expected.\n\n%#v\n\n%#v", tc.File, out, tc.Out)
447                         }
448                 })
449         }
450 }
451
452 func TestDecode_interfaceInline(t *testing.T) {
453         cases := []struct {
454                 Value string
455                 Err   bool
456                 Out   interface{}
457         }{
458                 {"t t e{{}}", true, nil},
459                 {"t=0t d {}", true, map[string]interface{}{"t": 0}},
460                 {"v=0E0v d{}", true, map[string]interface{}{"v": float64(0)}},
461         }
462
463         for _, tc := range cases {
464                 t.Logf("Testing: %q", tc.Value)
465
466                 var out interface{}
467                 err := Decode(&out, tc.Value)
468                 if (err != nil) != tc.Err {
469                         t.Fatalf("Input: %q\n\nError: %s", tc.Value, err)
470                 }
471
472                 if !reflect.DeepEqual(out, tc.Out) {
473                         t.Fatalf("Input: %q. Actual, Expected.\n\n%#v\n\n%#v", tc.Value, out, tc.Out)
474                 }
475
476                 var v interface{}
477                 err = Unmarshal([]byte(tc.Value), &v)
478                 if (err != nil) != tc.Err {
479                         t.Fatalf("Input: %q\n\nError: %s", tc.Value, err)
480                 }
481
482                 if !reflect.DeepEqual(v, tc.Out) {
483                         t.Fatalf("Input: %q. Actual, Expected.\n\n%#v\n\n%#v", tc.Value, out, tc.Out)
484                 }
485         }
486 }
487
488 func TestDecode_equal(t *testing.T) {
489         cases := []struct {
490                 One, Two string
491         }{
492                 {
493                         "basic.hcl",
494                         "basic.json",
495                 },
496                 {
497                         "float.hcl",
498                         "float.json",
499                 },
500                 /*
501                         {
502                                 "structure.hcl",
503                                 "structure.json",
504                         },
505                 */
506                 {
507                         "structure.hcl",
508                         "structure_flat.json",
509                 },
510                 {
511                         "terraform_heroku.hcl",
512                         "terraform_heroku.json",
513                 },
514         }
515
516         for _, tc := range cases {
517                 p1 := filepath.Join(fixtureDir, tc.One)
518                 p2 := filepath.Join(fixtureDir, tc.Two)
519
520                 d1, err := ioutil.ReadFile(p1)
521                 if err != nil {
522                         t.Fatalf("err: %s", err)
523                 }
524
525                 d2, err := ioutil.ReadFile(p2)
526                 if err != nil {
527                         t.Fatalf("err: %s", err)
528                 }
529
530                 var i1, i2 interface{}
531                 err = Decode(&i1, string(d1))
532                 if err != nil {
533                         t.Fatalf("err: %s", err)
534                 }
535
536                 err = Decode(&i2, string(d2))
537                 if err != nil {
538                         t.Fatalf("err: %s", err)
539                 }
540
541                 if !reflect.DeepEqual(i1, i2) {
542                         t.Fatalf(
543                                 "%s != %s\n\n%#v\n\n%#v",
544                                 tc.One, tc.Two,
545                                 i1, i2)
546                 }
547         }
548 }
549
550 func TestDecode_flatMap(t *testing.T) {
551         var val map[string]map[string]string
552
553         err := Decode(&val, testReadFile(t, "structure_flatmap.hcl"))
554         if err != nil {
555                 t.Fatalf("err: %s", err)
556         }
557
558         expected := map[string]map[string]string{
559                 "foo": map[string]string{
560                         "foo": "bar",
561                         "key": "7",
562                 },
563         }
564
565         if !reflect.DeepEqual(val, expected) {
566                 t.Fatalf("Actual: %#v\n\nExpected: %#v", val, expected)
567         }
568 }
569
570 func TestDecode_structure(t *testing.T) {
571         type Embedded interface{}
572
573         type V struct {
574                 Embedded `hcl:"-"`
575                 Key      int
576                 Foo      string
577         }
578
579         var actual V
580
581         err := Decode(&actual, testReadFile(t, "flat.hcl"))
582         if err != nil {
583                 t.Fatalf("err: %s", err)
584         }
585
586         expected := V{
587                 Key: 7,
588                 Foo: "bar",
589         }
590
591         if !reflect.DeepEqual(actual, expected) {
592                 t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected)
593         }
594 }
595
596 func TestDecode_structurePtr(t *testing.T) {
597         type V struct {
598                 Key int
599                 Foo string
600         }
601
602         var actual *V
603
604         err := Decode(&actual, testReadFile(t, "flat.hcl"))
605         if err != nil {
606                 t.Fatalf("err: %s", err)
607         }
608
609         expected := &V{
610                 Key: 7,
611                 Foo: "bar",
612         }
613
614         if !reflect.DeepEqual(actual, expected) {
615                 t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected)
616         }
617 }
618
619 func TestDecode_structureArray(t *testing.T) {
620         // This test is extracted from a failure in Consul (consul.io),
621         // hence the interesting structure naming.
622
623         type KeyPolicyType string
624
625         type KeyPolicy struct {
626                 Prefix string `hcl:",key"`
627                 Policy KeyPolicyType
628         }
629
630         type Policy struct {
631                 Keys []KeyPolicy `hcl:"key,expand"`
632         }
633
634         expected := Policy{
635                 Keys: []KeyPolicy{
636                         KeyPolicy{
637                                 Prefix: "",
638                                 Policy: "read",
639                         },
640                         KeyPolicy{
641                                 Prefix: "foo/",
642                                 Policy: "write",
643                         },
644                         KeyPolicy{
645                                 Prefix: "foo/bar/",
646                                 Policy: "read",
647                         },
648                         KeyPolicy{
649                                 Prefix: "foo/bar/baz",
650                                 Policy: "deny",
651                         },
652                 },
653         }
654
655         files := []string{
656                 "decode_policy.hcl",
657                 "decode_policy.json",
658         }
659
660         for _, f := range files {
661                 var actual Policy
662
663                 err := Decode(&actual, testReadFile(t, f))
664                 if err != nil {
665                         t.Fatalf("Input: %s\n\nerr: %s", f, err)
666                 }
667
668                 if !reflect.DeepEqual(actual, expected) {
669                         t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
670                 }
671         }
672 }
673
674 func TestDecode_sliceExpand(t *testing.T) {
675         type testInner struct {
676                 Name string `hcl:",key"`
677                 Key  string
678         }
679
680         type testStruct struct {
681                 Services []testInner `hcl:"service,expand"`
682         }
683
684         expected := testStruct{
685                 Services: []testInner{
686                         testInner{
687                                 Name: "my-service-0",
688                                 Key:  "value",
689                         },
690                         testInner{
691                                 Name: "my-service-1",
692                                 Key:  "value",
693                         },
694                 },
695         }
696
697         files := []string{
698                 "slice_expand.hcl",
699         }
700
701         for _, f := range files {
702                 t.Logf("Testing: %s", f)
703
704                 var actual testStruct
705                 err := Decode(&actual, testReadFile(t, f))
706                 if err != nil {
707                         t.Fatalf("Input: %s\n\nerr: %s", f, err)
708                 }
709
710                 if !reflect.DeepEqual(actual, expected) {
711                         t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
712                 }
713         }
714 }
715
716 func TestDecode_structureMap(t *testing.T) {
717         // This test is extracted from a failure in Terraform (terraform.io),
718         // hence the interesting structure naming.
719
720         type hclVariable struct {
721                 Default     interface{}
722                 Description string
723                 Fields      []string `hcl:",decodedFields"`
724         }
725
726         type rawConfig struct {
727                 Variable map[string]hclVariable
728         }
729
730         expected := rawConfig{
731                 Variable: map[string]hclVariable{
732                         "foo": hclVariable{
733                                 Default:     "bar",
734                                 Description: "bar",
735                                 Fields:      []string{"Default", "Description"},
736                         },
737
738                         "amis": hclVariable{
739                                 Default: []map[string]interface{}{
740                                         map[string]interface{}{
741                                                 "east": "foo",
742                                         },
743                                 },
744                                 Fields: []string{"Default"},
745                         },
746                 },
747         }
748
749         files := []string{
750                 "decode_tf_variable.hcl",
751                 "decode_tf_variable.json",
752         }
753
754         for _, f := range files {
755                 t.Logf("Testing: %s", f)
756
757                 var actual rawConfig
758                 err := Decode(&actual, testReadFile(t, f))
759                 if err != nil {
760                         t.Fatalf("Input: %s\n\nerr: %s", f, err)
761                 }
762
763                 if !reflect.DeepEqual(actual, expected) {
764                         t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
765                 }
766         }
767 }
768
769 func TestDecode_structureMapInvalid(t *testing.T) {
770         // Terraform GH-8295
771
772         type hclVariable struct {
773                 Default     interface{}
774                 Description string
775                 Fields      []string `hcl:",decodedFields"`
776         }
777
778         type rawConfig struct {
779                 Variable map[string]*hclVariable
780         }
781
782         var actual rawConfig
783         err := Decode(&actual, testReadFile(t, "terraform_variable_invalid.json"))
784         if err == nil {
785                 t.Fatal("expected error")
786         }
787 }
788
789 func TestDecode_interfaceNonPointer(t *testing.T) {
790         var value interface{}
791         err := Decode(value, testReadFile(t, "basic_int_string.hcl"))
792         if err == nil {
793                 t.Fatal("should error")
794         }
795 }
796
797 func TestDecode_intString(t *testing.T) {
798         var value struct {
799                 Count int
800         }
801
802         err := Decode(&value, testReadFile(t, "basic_int_string.hcl"))
803         if err != nil {
804                 t.Fatalf("err: %s", err)
805         }
806
807         if value.Count != 3 {
808                 t.Fatalf("bad: %#v", value.Count)
809         }
810 }
811
812 func TestDecode_float32(t *testing.T) {
813         var value struct {
814                 A float32 `hcl:"a"`
815                 B float32 `hcl:"b"`
816         }
817
818         err := Decode(&value, testReadFile(t, "float.hcl"))
819         if err != nil {
820                 t.Fatalf("err: %s", err)
821         }
822
823         if got, want := value.A, float32(1.02); got != want {
824                 t.Fatalf("wrong result %#v; want %#v", got, want)
825         }
826         if got, want := value.B, float32(2); got != want {
827                 t.Fatalf("wrong result %#v; want %#v", got, want)
828         }
829 }
830
831 func TestDecode_float64(t *testing.T) {
832         var value struct {
833                 A float64 `hcl:"a"`
834                 B float64 `hcl:"b"`
835         }
836
837         err := Decode(&value, testReadFile(t, "float.hcl"))
838         if err != nil {
839                 t.Fatalf("err: %s", err)
840         }
841
842         if got, want := value.A, float64(1.02); got != want {
843                 t.Fatalf("wrong result %#v; want %#v", got, want)
844         }
845         if got, want := value.B, float64(2); got != want {
846                 t.Fatalf("wrong result %#v; want %#v", got, want)
847         }
848 }
849
850 func TestDecode_intStringAliased(t *testing.T) {
851         var value struct {
852                 Count time.Duration
853         }
854
855         err := Decode(&value, testReadFile(t, "basic_int_string.hcl"))
856         if err != nil {
857                 t.Fatalf("err: %s", err)
858         }
859
860         if value.Count != time.Duration(3) {
861                 t.Fatalf("bad: %#v", value.Count)
862         }
863 }
864
865 func TestDecode_Node(t *testing.T) {
866         // given
867         var value struct {
868                 Content ast.Node
869                 Nested  struct {
870                         Content ast.Node
871                 }
872         }
873
874         content := `
875 content {
876         hello = "world"
877 }
878 `
879
880         // when
881         err := Decode(&value, content)
882
883         // then
884         if err != nil {
885                 t.Errorf("unable to decode content, %v", err)
886                 return
887         }
888
889         // verify ast.Node can be decoded later
890         var v map[string]interface{}
891         err = DecodeObject(&v, value.Content)
892         if err != nil {
893                 t.Errorf("unable to decode content, %v", err)
894                 return
895         }
896
897         if v["hello"] != "world" {
898                 t.Errorf("expected mapping to be returned")
899         }
900 }
901
902 func TestDecode_NestedNode(t *testing.T) {
903         // given
904         var value struct {
905                 Nested struct {
906                         Content ast.Node
907                 }
908         }
909
910         content := `
911 nested "content" {
912         hello = "world"
913 }
914 `
915
916         // when
917         err := Decode(&value, content)
918
919         // then
920         if err != nil {
921                 t.Errorf("unable to decode content, %v", err)
922                 return
923         }
924
925         // verify ast.Node can be decoded later
926         var v map[string]interface{}
927         err = DecodeObject(&v, value.Nested.Content)
928         if err != nil {
929                 t.Errorf("unable to decode content, %v", err)
930                 return
931         }
932
933         if v["hello"] != "world" {
934                 t.Errorf("expected mapping to be returned")
935         }
936 }
937
938 // https://github.com/hashicorp/hcl/issues/60
939 func TestDecode_topLevelKeys(t *testing.T) {
940         type Template struct {
941                 Source string
942         }
943
944         templates := struct {
945                 Templates []*Template `hcl:"template"`
946         }{}
947
948         err := Decode(&templates, `
949         template {
950             source = "blah"
951         }
952
953         template {
954             source = "blahblah"
955         }`)
956
957         if err != nil {
958                 t.Fatal(err)
959         }
960
961         if templates.Templates[0].Source != "blah" {
962                 t.Errorf("bad source: %s", templates.Templates[0].Source)
963         }
964
965         if templates.Templates[1].Source != "blahblah" {
966                 t.Errorf("bad source: %s", templates.Templates[1].Source)
967         }
968 }
969
970 func TestDecode_flattenedJSON(t *testing.T) {
971         // make sure we can also correctly extract a Name key too
972         type V struct {
973                 Name        string `hcl:",key"`
974                 Description string
975                 Default     map[string]string
976         }
977         type Vars struct {
978                 Variable []*V
979         }
980
981         cases := []struct {
982                 JSON     string
983                 Out      interface{}
984                 Expected interface{}
985         }{
986                 { // Nested object, no sibling keys
987                         JSON: `
988 {
989   "var_name": {
990     "default": {
991       "key1": "a",
992       "key2": "b"
993     }
994   }
995 }
996                         `,
997                         Out: &[]*V{},
998                         Expected: &[]*V{
999                                 &V{
1000                                         Name:    "var_name",
1001                                         Default: map[string]string{"key1": "a", "key2": "b"},
1002                                 },
1003                         },
1004                 },
1005
1006                 { // Nested object with a sibling key (this worked previously)
1007                         JSON: `
1008 {
1009   "var_name": {
1010     "description": "Described",
1011     "default": {
1012       "key1": "a",
1013       "key2": "b"
1014     }
1015   }
1016 }
1017                         `,
1018                         Out: &[]*V{},
1019                         Expected: &[]*V{
1020                                 &V{
1021                                         Name:        "var_name",
1022                                         Description: "Described",
1023                                         Default:     map[string]string{"key1": "a", "key2": "b"},
1024                                 },
1025                         },
1026                 },
1027
1028                 { // Multiple nested objects, one with a sibling key
1029                         JSON: `
1030 {
1031   "variable": {
1032     "var_1": {
1033       "default": {
1034         "key1": "a",
1035         "key2": "b"
1036       }
1037     },
1038     "var_2": {
1039       "description": "Described",
1040       "default": {
1041         "key1": "a",
1042         "key2": "b"
1043       }
1044     }
1045   }
1046 }
1047                         `,
1048                         Out: &Vars{},
1049                         Expected: &Vars{
1050                                 Variable: []*V{
1051                                         &V{
1052                                                 Name:    "var_1",
1053                                                 Default: map[string]string{"key1": "a", "key2": "b"},
1054                                         },
1055                                         &V{
1056                                                 Name:        "var_2",
1057                                                 Description: "Described",
1058                                                 Default:     map[string]string{"key1": "a", "key2": "b"},
1059                                         },
1060                                 },
1061                         },
1062                 },
1063
1064                 { // Nested object to maps
1065                         JSON: `
1066 {
1067   "variable": {
1068     "var_name": {
1069       "description": "Described",
1070       "default": {
1071         "key1": "a",
1072         "key2": "b"
1073       }
1074     }
1075   }
1076 }
1077                         `,
1078                         Out: &[]map[string]interface{}{},
1079                         Expected: &[]map[string]interface{}{
1080                                 {
1081                                         "variable": []map[string]interface{}{
1082                                                 {
1083                                                         "var_name": []map[string]interface{}{
1084                                                                 {
1085                                                                         "description": "Described",
1086                                                                         "default": []map[string]interface{}{
1087                                                                                 {
1088                                                                                         "key1": "a",
1089                                                                                         "key2": "b",
1090                                                                                 },
1091                                                                         },
1092                                                                 },
1093                                                         },
1094                                                 },
1095                                         },
1096                                 },
1097                         },
1098                 },
1099
1100                 { // Nested object to maps without a sibling key should decode the same as above
1101                         JSON: `
1102 {
1103   "variable": {
1104     "var_name": {
1105       "default": {
1106         "key1": "a",
1107         "key2": "b"
1108       }
1109     }
1110   }
1111 }
1112                         `,
1113                         Out: &[]map[string]interface{}{},
1114                         Expected: &[]map[string]interface{}{
1115                                 {
1116                                         "variable": []map[string]interface{}{
1117                                                 {
1118                                                         "var_name": []map[string]interface{}{
1119                                                                 {
1120                                                                         "default": []map[string]interface{}{
1121                                                                                 {
1122                                                                                         "key1": "a",
1123                                                                                         "key2": "b",
1124                                                                                 },
1125                                                                         },
1126                                                                 },
1127                                                         },
1128                                                 },
1129                                         },
1130                                 },
1131                         },
1132                 },
1133
1134                 { // Nested objects, one with a sibling key, and one without
1135                         JSON: `
1136 {
1137   "variable": {
1138     "var_1": {
1139       "default": {
1140         "key1": "a",
1141         "key2": "b"
1142       }
1143     },
1144     "var_2": {
1145       "description": "Described",
1146       "default": {
1147         "key1": "a",
1148         "key2": "b"
1149       }
1150     }
1151   }
1152 }
1153                         `,
1154                         Out: &[]map[string]interface{}{},
1155                         Expected: &[]map[string]interface{}{
1156                                 {
1157                                         "variable": []map[string]interface{}{
1158                                                 {
1159                                                         "var_1": []map[string]interface{}{
1160                                                                 {
1161                                                                         "default": []map[string]interface{}{
1162                                                                                 {
1163                                                                                         "key1": "a",
1164                                                                                         "key2": "b",
1165                                                                                 },
1166                                                                         },
1167                                                                 },
1168                                                         },
1169                                                 },
1170                                         },
1171                                 },
1172                                 {
1173                                         "variable": []map[string]interface{}{
1174                                                 {
1175                                                         "var_2": []map[string]interface{}{
1176                                                                 {
1177                                                                         "description": "Described",
1178                                                                         "default": []map[string]interface{}{
1179                                                                                 {
1180                                                                                         "key1": "a",
1181                                                                                         "key2": "b",
1182                                                                                 },
1183                                                                         },
1184                                                                 },
1185                                                         },
1186                                                 },
1187                                         },
1188                                 },
1189                         },
1190                 },
1191         }
1192
1193         for i, tc := range cases {
1194                 err := Decode(tc.Out, tc.JSON)
1195                 if err != nil {
1196                         t.Fatalf("[%d] err: %s", i, err)
1197                 }
1198
1199                 if !reflect.DeepEqual(tc.Out, tc.Expected) {
1200                         t.Fatalf("[%d]\ngot: %s\nexpected: %s\n", i, spew.Sdump(tc.Out), spew.Sdump(tc.Expected))
1201                 }
1202         }
1203 }