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