OSDN Git Service

Hulk did something
[bytom/vapor.git] / vendor / github.com / hashicorp / hcl / decoder_test.go
diff --git a/vendor/github.com/hashicorp/hcl/decoder_test.go b/vendor/github.com/hashicorp/hcl/decoder_test.go
new file mode 100644 (file)
index 0000000..8682f47
--- /dev/null
@@ -0,0 +1,1203 @@
+package hcl
+
+import (
+       "io/ioutil"
+       "path/filepath"
+       "reflect"
+       "testing"
+       "time"
+
+       "github.com/davecgh/go-spew/spew"
+       "github.com/hashicorp/hcl/hcl/ast"
+)
+
+func TestDecode_interface(t *testing.T) {
+       cases := []struct {
+               File string
+               Err  bool
+               Out  interface{}
+       }{
+               {
+                       "basic.hcl",
+                       false,
+                       map[string]interface{}{
+                               "foo": "bar",
+                               "bar": "${file(\"bing/bong.txt\")}",
+                       },
+               },
+               {
+                       "basic_squish.hcl",
+                       false,
+                       map[string]interface{}{
+                               "foo":     "bar",
+                               "bar":     "${file(\"bing/bong.txt\")}",
+                               "foo-bar": "baz",
+                       },
+               },
+               {
+                       "empty.hcl",
+                       false,
+                       map[string]interface{}{
+                               "resource": []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "foo": []map[string]interface{}{
+                                                       map[string]interface{}{},
+                                               },
+                                       },
+                               },
+                       },
+               },
+               {
+                       "tfvars.hcl",
+                       false,
+                       map[string]interface{}{
+                               "regularvar": "Should work",
+                               "map.key1":   "Value",
+                               "map.key2":   "Other value",
+                       },
+               },
+               {
+                       "escape.hcl",
+                       false,
+                       map[string]interface{}{
+                               "foo":          "bar\"baz\\n",
+                               "qux":          "back\\slash",
+                               "bar":          "new\nline",
+                               "qax":          `slash\:colon`,
+                               "nested":       `${HH\\:mm\\:ss}`,
+                               "nestedquotes": `${"\"stringwrappedinquotes\""}`,
+                       },
+               },
+               {
+                       "float.hcl",
+                       false,
+                       map[string]interface{}{
+                               "a": 1.02,
+                               "b": 2,
+                       },
+               },
+               {
+                       "multiline_bad.hcl",
+                       true,
+                       nil,
+               },
+               {
+                       "multiline_literal.hcl",
+                       true,
+                       nil,
+               },
+               {
+                       "multiline_literal_with_hil.hcl",
+                       false,
+                       map[string]interface{}{"multiline_literal_with_hil": "${hello\n  world}"},
+               },
+               {
+                       "multiline_no_marker.hcl",
+                       true,
+                       nil,
+               },
+               {
+                       "multiline.hcl",
+                       false,
+                       map[string]interface{}{"foo": "bar\nbaz\n"},
+               },
+               {
+                       "multiline_indented.hcl",
+                       false,
+                       map[string]interface{}{"foo": "  bar\n  baz\n"},
+               },
+               {
+                       "multiline_no_hanging_indent.hcl",
+                       false,
+                       map[string]interface{}{"foo": "  baz\n    bar\n      foo\n"},
+               },
+               {
+                       "multiline_no_eof.hcl",
+                       false,
+                       map[string]interface{}{"foo": "bar\nbaz\n", "key": "value"},
+               },
+               {
+                       "multiline.json",
+                       false,
+                       map[string]interface{}{"foo": "bar\nbaz"},
+               },
+               {
+                       "null_strings.json",
+                       false,
+                       map[string]interface{}{
+                               "module": []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "app": []map[string]interface{}{
+                                                       map[string]interface{}{"foo": ""},
+                                               },
+                                       },
+                               },
+                       },
+               },
+               {
+                       "scientific.json",
+                       false,
+                       map[string]interface{}{
+                               "a": 1e-10,
+                               "b": 1e+10,
+                               "c": 1e10,
+                               "d": 1.2e-10,
+                               "e": 1.2e+10,
+                               "f": 1.2e10,
+                       },
+               },
+               {
+                       "scientific.hcl",
+                       false,
+                       map[string]interface{}{
+                               "a": 1e-10,
+                               "b": 1e+10,
+                               "c": 1e10,
+                               "d": 1.2e-10,
+                               "e": 1.2e+10,
+                               "f": 1.2e10,
+                       },
+               },
+               {
+                       "terraform_heroku.hcl",
+                       false,
+                       map[string]interface{}{
+                               "name": "terraform-test-app",
+                               "config_vars": []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "FOO": "bar",
+                                       },
+                               },
+                       },
+               },
+               {
+                       "structure_multi.hcl",
+                       false,
+                       map[string]interface{}{
+                               "foo": []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "baz": []map[string]interface{}{
+                                                       map[string]interface{}{"key": 7},
+                                               },
+                                       },
+                                       map[string]interface{}{
+                                               "bar": []map[string]interface{}{
+                                                       map[string]interface{}{"key": 12},
+                                               },
+                                       },
+                               },
+                       },
+               },
+               {
+                       "structure_multi.json",
+                       false,
+                       map[string]interface{}{
+                               "foo": []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "baz": []map[string]interface{}{
+                                                       map[string]interface{}{"key": 7},
+                                               },
+                                       },
+                                       map[string]interface{}{
+                                               "bar": []map[string]interface{}{
+                                                       map[string]interface{}{"key": 12},
+                                               },
+                                       },
+                               },
+                       },
+               },
+               {
+                       "list_of_lists.hcl",
+                       false,
+                       map[string]interface{}{
+                               "foo": []interface{}{
+                                       []interface{}{"foo"},
+                                       []interface{}{"bar"},
+                               },
+                       },
+               },
+               {
+                       "list_of_maps.hcl",
+                       false,
+                       map[string]interface{}{
+                               "foo": []interface{}{
+                                       map[string]interface{}{"somekey1": "someval1"},
+                                       map[string]interface{}{"somekey2": "someval2", "someextrakey": "someextraval"},
+                               },
+                       },
+               },
+               {
+                       "assign_deep.hcl",
+                       false,
+                       map[string]interface{}{
+                               "resource": []interface{}{
+                                       map[string]interface{}{
+                                               "foo": []interface{}{
+                                                       map[string]interface{}{
+                                                               "bar": []map[string]interface{}{
+                                                                       map[string]interface{}{}}}}}}},
+               },
+               {
+                       "structure_list.hcl",
+                       false,
+                       map[string]interface{}{
+                               "foo": []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "key": 7,
+                                       },
+                                       map[string]interface{}{
+                                               "key": 12,
+                                       },
+                               },
+                       },
+               },
+               {
+                       "structure_list.json",
+                       false,
+                       map[string]interface{}{
+                               "foo": []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "key": 7,
+                                       },
+                                       map[string]interface{}{
+                                               "key": 12,
+                                       },
+                               },
+                       },
+               },
+               {
+                       "structure_list_deep.json",
+                       false,
+                       map[string]interface{}{
+                               "bar": []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "foo": []map[string]interface{}{
+                                                       map[string]interface{}{
+                                                               "name": "terraform_example",
+                                                               "ingress": []map[string]interface{}{
+                                                                       map[string]interface{}{
+                                                                               "from_port": 22,
+                                                                       },
+                                                                       map[string]interface{}{
+                                                                               "from_port": 80,
+                                                                       },
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+               },
+
+               {
+                       "structure_list_empty.json",
+                       false,
+                       map[string]interface{}{
+                               "foo": []interface{}{},
+                       },
+               },
+
+               {
+                       "nested_block_comment.hcl",
+                       false,
+                       map[string]interface{}{
+                               "bar": "value",
+                       },
+               },
+
+               {
+                       "unterminated_block_comment.hcl",
+                       true,
+                       nil,
+               },
+
+               {
+                       "unterminated_brace.hcl",
+                       true,
+                       nil,
+               },
+
+               {
+                       "nested_provider_bad.hcl",
+                       true,
+                       nil,
+               },
+
+               {
+                       "object_list.json",
+                       false,
+                       map[string]interface{}{
+                               "resource": []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "aws_instance": []map[string]interface{}{
+                                                       map[string]interface{}{
+                                                               "db": []map[string]interface{}{
+                                                                       map[string]interface{}{
+                                                                               "vpc": "foo",
+                                                                               "provisioner": []map[string]interface{}{
+                                                                                       map[string]interface{}{
+                                                                                               "file": []map[string]interface{}{
+                                                                                                       map[string]interface{}{
+                                                                                                               "source":      "foo",
+                                                                                                               "destination": "bar",
+                                                                                                       },
+                                                                                               },
+                                                                                       },
+                                                                               },
+                                                                       },
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+               },
+
+               // Terraform GH-8295 sanity test that basic decoding into
+               // interface{} works.
+               {
+                       "terraform_variable_invalid.json",
+                       false,
+                       map[string]interface{}{
+                               "variable": []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "whatever": "abc123",
+                                       },
+                               },
+                       },
+               },
+
+               {
+                       "interpolate.json",
+                       false,
+                       map[string]interface{}{
+                               "default": `${replace("europe-west", "-", " ")}`,
+                       },
+               },
+
+               {
+                       "block_assign.hcl",
+                       true,
+                       nil,
+               },
+
+               {
+                       "escape_backslash.hcl",
+                       false,
+                       map[string]interface{}{
+                               "output": []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "one":  `${replace(var.sub_domain, ".", "\\.")}`,
+                                               "two":  `${replace(var.sub_domain, ".", "\\\\.")}`,
+                                               "many": `${replace(var.sub_domain, ".", "\\\\\\\\.")}`,
+                                       },
+                               },
+                       },
+               },
+
+               {
+                       "git_crypt.hcl",
+                       true,
+                       nil,
+               },
+
+               {
+                       "object_with_bool.hcl",
+                       false,
+                       map[string]interface{}{
+                               "path": []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "policy": "write",
+                                               "permissions": []map[string]interface{}{
+                                                       map[string]interface{}{
+                                                               "bool": []interface{}{false},
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+
+       for _, tc := range cases {
+               t.Run(tc.File, func(t *testing.T) {
+                       d, err := ioutil.ReadFile(filepath.Join(fixtureDir, tc.File))
+                       if err != nil {
+                               t.Fatalf("err: %s", err)
+                       }
+
+                       var out interface{}
+                       err = Decode(&out, string(d))
+                       if (err != nil) != tc.Err {
+                               t.Fatalf("Input: %s\n\nError: %s", tc.File, err)
+                       }
+
+                       if !reflect.DeepEqual(out, tc.Out) {
+                               t.Fatalf("Input: %s. Actual, Expected.\n\n%#v\n\n%#v", tc.File, out, tc.Out)
+                       }
+
+                       var v interface{}
+                       err = Unmarshal(d, &v)
+                       if (err != nil) != tc.Err {
+                               t.Fatalf("Input: %s\n\nError: %s", tc.File, err)
+                       }
+
+                       if !reflect.DeepEqual(v, tc.Out) {
+                               t.Fatalf("Input: %s. Actual, Expected.\n\n%#v\n\n%#v", tc.File, out, tc.Out)
+                       }
+               })
+       }
+}
+
+func TestDecode_interfaceInline(t *testing.T) {
+       cases := []struct {
+               Value string
+               Err   bool
+               Out   interface{}
+       }{
+               {"t t e{{}}", true, nil},
+               {"t=0t d {}", true, map[string]interface{}{"t": 0}},
+               {"v=0E0v d{}", true, map[string]interface{}{"v": float64(0)}},
+       }
+
+       for _, tc := range cases {
+               t.Logf("Testing: %q", tc.Value)
+
+               var out interface{}
+               err := Decode(&out, tc.Value)
+               if (err != nil) != tc.Err {
+                       t.Fatalf("Input: %q\n\nError: %s", tc.Value, err)
+               }
+
+               if !reflect.DeepEqual(out, tc.Out) {
+                       t.Fatalf("Input: %q. Actual, Expected.\n\n%#v\n\n%#v", tc.Value, out, tc.Out)
+               }
+
+               var v interface{}
+               err = Unmarshal([]byte(tc.Value), &v)
+               if (err != nil) != tc.Err {
+                       t.Fatalf("Input: %q\n\nError: %s", tc.Value, err)
+               }
+
+               if !reflect.DeepEqual(v, tc.Out) {
+                       t.Fatalf("Input: %q. Actual, Expected.\n\n%#v\n\n%#v", tc.Value, out, tc.Out)
+               }
+       }
+}
+
+func TestDecode_equal(t *testing.T) {
+       cases := []struct {
+               One, Two string
+       }{
+               {
+                       "basic.hcl",
+                       "basic.json",
+               },
+               {
+                       "float.hcl",
+                       "float.json",
+               },
+               /*
+                       {
+                               "structure.hcl",
+                               "structure.json",
+                       },
+               */
+               {
+                       "structure.hcl",
+                       "structure_flat.json",
+               },
+               {
+                       "terraform_heroku.hcl",
+                       "terraform_heroku.json",
+               },
+       }
+
+       for _, tc := range cases {
+               p1 := filepath.Join(fixtureDir, tc.One)
+               p2 := filepath.Join(fixtureDir, tc.Two)
+
+               d1, err := ioutil.ReadFile(p1)
+               if err != nil {
+                       t.Fatalf("err: %s", err)
+               }
+
+               d2, err := ioutil.ReadFile(p2)
+               if err != nil {
+                       t.Fatalf("err: %s", err)
+               }
+
+               var i1, i2 interface{}
+               err = Decode(&i1, string(d1))
+               if err != nil {
+                       t.Fatalf("err: %s", err)
+               }
+
+               err = Decode(&i2, string(d2))
+               if err != nil {
+                       t.Fatalf("err: %s", err)
+               }
+
+               if !reflect.DeepEqual(i1, i2) {
+                       t.Fatalf(
+                               "%s != %s\n\n%#v\n\n%#v",
+                               tc.One, tc.Two,
+                               i1, i2)
+               }
+       }
+}
+
+func TestDecode_flatMap(t *testing.T) {
+       var val map[string]map[string]string
+
+       err := Decode(&val, testReadFile(t, "structure_flatmap.hcl"))
+       if err != nil {
+               t.Fatalf("err: %s", err)
+       }
+
+       expected := map[string]map[string]string{
+               "foo": map[string]string{
+                       "foo": "bar",
+                       "key": "7",
+               },
+       }
+
+       if !reflect.DeepEqual(val, expected) {
+               t.Fatalf("Actual: %#v\n\nExpected: %#v", val, expected)
+       }
+}
+
+func TestDecode_structure(t *testing.T) {
+       type Embedded interface{}
+
+       type V struct {
+               Embedded `hcl:"-"`
+               Key      int
+               Foo      string
+       }
+
+       var actual V
+
+       err := Decode(&actual, testReadFile(t, "flat.hcl"))
+       if err != nil {
+               t.Fatalf("err: %s", err)
+       }
+
+       expected := V{
+               Key: 7,
+               Foo: "bar",
+       }
+
+       if !reflect.DeepEqual(actual, expected) {
+               t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected)
+       }
+}
+
+func TestDecode_structurePtr(t *testing.T) {
+       type V struct {
+               Key int
+               Foo string
+       }
+
+       var actual *V
+
+       err := Decode(&actual, testReadFile(t, "flat.hcl"))
+       if err != nil {
+               t.Fatalf("err: %s", err)
+       }
+
+       expected := &V{
+               Key: 7,
+               Foo: "bar",
+       }
+
+       if !reflect.DeepEqual(actual, expected) {
+               t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected)
+       }
+}
+
+func TestDecode_structureArray(t *testing.T) {
+       // This test is extracted from a failure in Consul (consul.io),
+       // hence the interesting structure naming.
+
+       type KeyPolicyType string
+
+       type KeyPolicy struct {
+               Prefix string `hcl:",key"`
+               Policy KeyPolicyType
+       }
+
+       type Policy struct {
+               Keys []KeyPolicy `hcl:"key,expand"`
+       }
+
+       expected := Policy{
+               Keys: []KeyPolicy{
+                       KeyPolicy{
+                               Prefix: "",
+                               Policy: "read",
+                       },
+                       KeyPolicy{
+                               Prefix: "foo/",
+                               Policy: "write",
+                       },
+                       KeyPolicy{
+                               Prefix: "foo/bar/",
+                               Policy: "read",
+                       },
+                       KeyPolicy{
+                               Prefix: "foo/bar/baz",
+                               Policy: "deny",
+                       },
+               },
+       }
+
+       files := []string{
+               "decode_policy.hcl",
+               "decode_policy.json",
+       }
+
+       for _, f := range files {
+               var actual Policy
+
+               err := Decode(&actual, testReadFile(t, f))
+               if err != nil {
+                       t.Fatalf("Input: %s\n\nerr: %s", f, err)
+               }
+
+               if !reflect.DeepEqual(actual, expected) {
+                       t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
+               }
+       }
+}
+
+func TestDecode_sliceExpand(t *testing.T) {
+       type testInner struct {
+               Name string `hcl:",key"`
+               Key  string
+       }
+
+       type testStruct struct {
+               Services []testInner `hcl:"service,expand"`
+       }
+
+       expected := testStruct{
+               Services: []testInner{
+                       testInner{
+                               Name: "my-service-0",
+                               Key:  "value",
+                       },
+                       testInner{
+                               Name: "my-service-1",
+                               Key:  "value",
+                       },
+               },
+       }
+
+       files := []string{
+               "slice_expand.hcl",
+       }
+
+       for _, f := range files {
+               t.Logf("Testing: %s", f)
+
+               var actual testStruct
+               err := Decode(&actual, testReadFile(t, f))
+               if err != nil {
+                       t.Fatalf("Input: %s\n\nerr: %s", f, err)
+               }
+
+               if !reflect.DeepEqual(actual, expected) {
+                       t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
+               }
+       }
+}
+
+func TestDecode_structureMap(t *testing.T) {
+       // This test is extracted from a failure in Terraform (terraform.io),
+       // hence the interesting structure naming.
+
+       type hclVariable struct {
+               Default     interface{}
+               Description string
+               Fields      []string `hcl:",decodedFields"`
+       }
+
+       type rawConfig struct {
+               Variable map[string]hclVariable
+       }
+
+       expected := rawConfig{
+               Variable: map[string]hclVariable{
+                       "foo": hclVariable{
+                               Default:     "bar",
+                               Description: "bar",
+                               Fields:      []string{"Default", "Description"},
+                       },
+
+                       "amis": hclVariable{
+                               Default: []map[string]interface{}{
+                                       map[string]interface{}{
+                                               "east": "foo",
+                                       },
+                               },
+                               Fields: []string{"Default"},
+                       },
+               },
+       }
+
+       files := []string{
+               "decode_tf_variable.hcl",
+               "decode_tf_variable.json",
+       }
+
+       for _, f := range files {
+               t.Logf("Testing: %s", f)
+
+               var actual rawConfig
+               err := Decode(&actual, testReadFile(t, f))
+               if err != nil {
+                       t.Fatalf("Input: %s\n\nerr: %s", f, err)
+               }
+
+               if !reflect.DeepEqual(actual, expected) {
+                       t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
+               }
+       }
+}
+
+func TestDecode_structureMapInvalid(t *testing.T) {
+       // Terraform GH-8295
+
+       type hclVariable struct {
+               Default     interface{}
+               Description string
+               Fields      []string `hcl:",decodedFields"`
+       }
+
+       type rawConfig struct {
+               Variable map[string]*hclVariable
+       }
+
+       var actual rawConfig
+       err := Decode(&actual, testReadFile(t, "terraform_variable_invalid.json"))
+       if err == nil {
+               t.Fatal("expected error")
+       }
+}
+
+func TestDecode_interfaceNonPointer(t *testing.T) {
+       var value interface{}
+       err := Decode(value, testReadFile(t, "basic_int_string.hcl"))
+       if err == nil {
+               t.Fatal("should error")
+       }
+}
+
+func TestDecode_intString(t *testing.T) {
+       var value struct {
+               Count int
+       }
+
+       err := Decode(&value, testReadFile(t, "basic_int_string.hcl"))
+       if err != nil {
+               t.Fatalf("err: %s", err)
+       }
+
+       if value.Count != 3 {
+               t.Fatalf("bad: %#v", value.Count)
+       }
+}
+
+func TestDecode_float32(t *testing.T) {
+       var value struct {
+               A float32 `hcl:"a"`
+               B float32 `hcl:"b"`
+       }
+
+       err := Decode(&value, testReadFile(t, "float.hcl"))
+       if err != nil {
+               t.Fatalf("err: %s", err)
+       }
+
+       if got, want := value.A, float32(1.02); got != want {
+               t.Fatalf("wrong result %#v; want %#v", got, want)
+       }
+       if got, want := value.B, float32(2); got != want {
+               t.Fatalf("wrong result %#v; want %#v", got, want)
+       }
+}
+
+func TestDecode_float64(t *testing.T) {
+       var value struct {
+               A float64 `hcl:"a"`
+               B float64 `hcl:"b"`
+       }
+
+       err := Decode(&value, testReadFile(t, "float.hcl"))
+       if err != nil {
+               t.Fatalf("err: %s", err)
+       }
+
+       if got, want := value.A, float64(1.02); got != want {
+               t.Fatalf("wrong result %#v; want %#v", got, want)
+       }
+       if got, want := value.B, float64(2); got != want {
+               t.Fatalf("wrong result %#v; want %#v", got, want)
+       }
+}
+
+func TestDecode_intStringAliased(t *testing.T) {
+       var value struct {
+               Count time.Duration
+       }
+
+       err := Decode(&value, testReadFile(t, "basic_int_string.hcl"))
+       if err != nil {
+               t.Fatalf("err: %s", err)
+       }
+
+       if value.Count != time.Duration(3) {
+               t.Fatalf("bad: %#v", value.Count)
+       }
+}
+
+func TestDecode_Node(t *testing.T) {
+       // given
+       var value struct {
+               Content ast.Node
+               Nested  struct {
+                       Content ast.Node
+               }
+       }
+
+       content := `
+content {
+       hello = "world"
+}
+`
+
+       // when
+       err := Decode(&value, content)
+
+       // then
+       if err != nil {
+               t.Errorf("unable to decode content, %v", err)
+               return
+       }
+
+       // verify ast.Node can be decoded later
+       var v map[string]interface{}
+       err = DecodeObject(&v, value.Content)
+       if err != nil {
+               t.Errorf("unable to decode content, %v", err)
+               return
+       }
+
+       if v["hello"] != "world" {
+               t.Errorf("expected mapping to be returned")
+       }
+}
+
+func TestDecode_NestedNode(t *testing.T) {
+       // given
+       var value struct {
+               Nested struct {
+                       Content ast.Node
+               }
+       }
+
+       content := `
+nested "content" {
+       hello = "world"
+}
+`
+
+       // when
+       err := Decode(&value, content)
+
+       // then
+       if err != nil {
+               t.Errorf("unable to decode content, %v", err)
+               return
+       }
+
+       // verify ast.Node can be decoded later
+       var v map[string]interface{}
+       err = DecodeObject(&v, value.Nested.Content)
+       if err != nil {
+               t.Errorf("unable to decode content, %v", err)
+               return
+       }
+
+       if v["hello"] != "world" {
+               t.Errorf("expected mapping to be returned")
+       }
+}
+
+// https://github.com/hashicorp/hcl/issues/60
+func TestDecode_topLevelKeys(t *testing.T) {
+       type Template struct {
+               Source string
+       }
+
+       templates := struct {
+               Templates []*Template `hcl:"template"`
+       }{}
+
+       err := Decode(&templates, `
+       template {
+           source = "blah"
+       }
+
+       template {
+           source = "blahblah"
+       }`)
+
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       if templates.Templates[0].Source != "blah" {
+               t.Errorf("bad source: %s", templates.Templates[0].Source)
+       }
+
+       if templates.Templates[1].Source != "blahblah" {
+               t.Errorf("bad source: %s", templates.Templates[1].Source)
+       }
+}
+
+func TestDecode_flattenedJSON(t *testing.T) {
+       // make sure we can also correctly extract a Name key too
+       type V struct {
+               Name        string `hcl:",key"`
+               Description string
+               Default     map[string]string
+       }
+       type Vars struct {
+               Variable []*V
+       }
+
+       cases := []struct {
+               JSON     string
+               Out      interface{}
+               Expected interface{}
+       }{
+               { // Nested object, no sibling keys
+                       JSON: `
+{
+  "var_name": {
+    "default": {
+      "key1": "a",
+      "key2": "b"
+    }
+  }
+}
+                       `,
+                       Out: &[]*V{},
+                       Expected: &[]*V{
+                               &V{
+                                       Name:    "var_name",
+                                       Default: map[string]string{"key1": "a", "key2": "b"},
+                               },
+                       },
+               },
+
+               { // Nested object with a sibling key (this worked previously)
+                       JSON: `
+{
+  "var_name": {
+    "description": "Described",
+    "default": {
+      "key1": "a",
+      "key2": "b"
+    }
+  }
+}
+                       `,
+                       Out: &[]*V{},
+                       Expected: &[]*V{
+                               &V{
+                                       Name:        "var_name",
+                                       Description: "Described",
+                                       Default:     map[string]string{"key1": "a", "key2": "b"},
+                               },
+                       },
+               },
+
+               { // Multiple nested objects, one with a sibling key
+                       JSON: `
+{
+  "variable": {
+    "var_1": {
+      "default": {
+        "key1": "a",
+        "key2": "b"
+      }
+    },
+    "var_2": {
+      "description": "Described",
+      "default": {
+        "key1": "a",
+        "key2": "b"
+      }
+    }
+  }
+}
+                       `,
+                       Out: &Vars{},
+                       Expected: &Vars{
+                               Variable: []*V{
+                                       &V{
+                                               Name:    "var_1",
+                                               Default: map[string]string{"key1": "a", "key2": "b"},
+                                       },
+                                       &V{
+                                               Name:        "var_2",
+                                               Description: "Described",
+                                               Default:     map[string]string{"key1": "a", "key2": "b"},
+                                       },
+                               },
+                       },
+               },
+
+               { // Nested object to maps
+                       JSON: `
+{
+  "variable": {
+    "var_name": {
+      "description": "Described",
+      "default": {
+        "key1": "a",
+        "key2": "b"
+      }
+    }
+  }
+}
+                       `,
+                       Out: &[]map[string]interface{}{},
+                       Expected: &[]map[string]interface{}{
+                               {
+                                       "variable": []map[string]interface{}{
+                                               {
+                                                       "var_name": []map[string]interface{}{
+                                                               {
+                                                                       "description": "Described",
+                                                                       "default": []map[string]interface{}{
+                                                                               {
+                                                                                       "key1": "a",
+                                                                                       "key2": "b",
+                                                                               },
+                                                                       },
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+               },
+
+               { // Nested object to maps without a sibling key should decode the same as above
+                       JSON: `
+{
+  "variable": {
+    "var_name": {
+      "default": {
+        "key1": "a",
+        "key2": "b"
+      }
+    }
+  }
+}
+                       `,
+                       Out: &[]map[string]interface{}{},
+                       Expected: &[]map[string]interface{}{
+                               {
+                                       "variable": []map[string]interface{}{
+                                               {
+                                                       "var_name": []map[string]interface{}{
+                                                               {
+                                                                       "default": []map[string]interface{}{
+                                                                               {
+                                                                                       "key1": "a",
+                                                                                       "key2": "b",
+                                                                               },
+                                                                       },
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+               },
+
+               { // Nested objects, one with a sibling key, and one without
+                       JSON: `
+{
+  "variable": {
+    "var_1": {
+      "default": {
+        "key1": "a",
+        "key2": "b"
+      }
+    },
+    "var_2": {
+      "description": "Described",
+      "default": {
+        "key1": "a",
+        "key2": "b"
+      }
+    }
+  }
+}
+                       `,
+                       Out: &[]map[string]interface{}{},
+                       Expected: &[]map[string]interface{}{
+                               {
+                                       "variable": []map[string]interface{}{
+                                               {
+                                                       "var_1": []map[string]interface{}{
+                                                               {
+                                                                       "default": []map[string]interface{}{
+                                                                               {
+                                                                                       "key1": "a",
+                                                                                       "key2": "b",
+                                                                               },
+                                                                       },
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
+                               {
+                                       "variable": []map[string]interface{}{
+                                               {
+                                                       "var_2": []map[string]interface{}{
+                                                               {
+                                                                       "description": "Described",
+                                                                       "default": []map[string]interface{}{
+                                                                               {
+                                                                                       "key1": "a",
+                                                                                       "key2": "b",
+                                                                               },
+                                                                       },
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+
+       for i, tc := range cases {
+               err := Decode(tc.Out, tc.JSON)
+               if err != nil {
+                       t.Fatalf("[%d] err: %s", i, err)
+               }
+
+               if !reflect.DeepEqual(tc.Out, tc.Expected) {
+                       t.Fatalf("[%d]\ngot: %s\nexpected: %s\n", i, spew.Sdump(tc.Out), spew.Sdump(tc.Expected))
+               }
+       }
+}