...

Source file src/github.com/hashicorp/hcl/decoder_test.go

Documentation: github.com/hashicorp/hcl

     1  package hcl
     2  
     3  import (
     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  
    15  func 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  
   453  func 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  
   489  func 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  
   551  func 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  
   571  func 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  
   597  func 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  
   620  func 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  
   675  func 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  
   717  func 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  
   770  func 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  
   790  func 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  
   836  func TestDecode_structureMapExtraKeysNestedObjects(t *testing.T) {
   837  	type hclVariable struct {
   838  		A     int
   839  		B     struct{}
   840  		Found []string               `hcl:",decodedFields"`
   841  		Extra map[string][]token.Pos `hcl:",unusedKeyPositions"`
   842  	}
   843  
   844  	var j hclVariable
   845  	ast, _ := Parse(testReadFile(t, "structure_map_nested_keys.json"))
   846  	DecodeObject(&j, ast)
   847  	if len(j.Extra) > 0 {
   848  		t.Fatalf("Extra keys found in map: %x", j.Extra)
   849  	}
   850  }
   851  
   852  func TestDecode_interfaceNonPointer(t *testing.T) {
   853  	var value interface{}
   854  	err := Decode(value, testReadFile(t, "basic_int_string.hcl"))
   855  	if err == nil {
   856  		t.Fatal("should error")
   857  	}
   858  }
   859  
   860  func TestDecode_intString(t *testing.T) {
   861  	var value struct {
   862  		Count int
   863  	}
   864  
   865  	err := Decode(&value, testReadFile(t, "basic_int_string.hcl"))
   866  	if err != nil {
   867  		t.Fatalf("err: %s", err)
   868  	}
   869  
   870  	if value.Count != 3 {
   871  		t.Fatalf("bad: %#v", value.Count)
   872  	}
   873  }
   874  
   875  func TestDecode_float32(t *testing.T) {
   876  	var value struct {
   877  		A float32 `hcl:"a"`
   878  		B float32 `hcl:"b"`
   879  	}
   880  
   881  	err := Decode(&value, testReadFile(t, "float.hcl"))
   882  	if err != nil {
   883  		t.Fatalf("err: %s", err)
   884  	}
   885  
   886  	if got, want := value.A, float32(1.02); got != want {
   887  		t.Fatalf("wrong result %#v; want %#v", got, want)
   888  	}
   889  	if got, want := value.B, float32(2); got != want {
   890  		t.Fatalf("wrong result %#v; want %#v", got, want)
   891  	}
   892  }
   893  
   894  func TestDecode_float64(t *testing.T) {
   895  	var value struct {
   896  		A float64 `hcl:"a"`
   897  		B float64 `hcl:"b"`
   898  	}
   899  
   900  	err := Decode(&value, testReadFile(t, "float.hcl"))
   901  	if err != nil {
   902  		t.Fatalf("err: %s", err)
   903  	}
   904  
   905  	if got, want := value.A, float64(1.02); got != want {
   906  		t.Fatalf("wrong result %#v; want %#v", got, want)
   907  	}
   908  	if got, want := value.B, float64(2); got != want {
   909  		t.Fatalf("wrong result %#v; want %#v", got, want)
   910  	}
   911  }
   912  
   913  func TestDecode_intStringAliased(t *testing.T) {
   914  	var value struct {
   915  		Count time.Duration
   916  	}
   917  
   918  	err := Decode(&value, testReadFile(t, "basic_int_string.hcl"))
   919  	if err != nil {
   920  		t.Fatalf("err: %s", err)
   921  	}
   922  
   923  	if value.Count != time.Duration(3) {
   924  		t.Fatalf("bad: %#v", value.Count)
   925  	}
   926  }
   927  
   928  func TestDecode_Node(t *testing.T) {
   929  	// given
   930  	var value struct {
   931  		Content ast.Node
   932  		Nested  struct {
   933  			Content ast.Node
   934  		}
   935  	}
   936  
   937  	content := `
   938  content {
   939  	hello = "world"
   940  }
   941  `
   942  
   943  	// when
   944  	err := Decode(&value, content)
   945  
   946  	// then
   947  	if err != nil {
   948  		t.Errorf("unable to decode content, %v", err)
   949  		return
   950  	}
   951  
   952  	// verify ast.Node can be decoded later
   953  	var v map[string]interface{}
   954  	err = DecodeObject(&v, value.Content)
   955  	if err != nil {
   956  		t.Errorf("unable to decode content, %v", err)
   957  		return
   958  	}
   959  
   960  	if v["hello"] != "world" {
   961  		t.Errorf("expected mapping to be returned")
   962  	}
   963  }
   964  
   965  func TestDecode_NestedNode(t *testing.T) {
   966  	// given
   967  	var value struct {
   968  		Nested struct {
   969  			Content ast.Node
   970  		}
   971  	}
   972  
   973  	content := `
   974  nested "content" {
   975  	hello = "world"
   976  }
   977  `
   978  
   979  	// when
   980  	err := Decode(&value, content)
   981  
   982  	// then
   983  	if err != nil {
   984  		t.Errorf("unable to decode content, %v", err)
   985  		return
   986  	}
   987  
   988  	// verify ast.Node can be decoded later
   989  	var v map[string]interface{}
   990  	err = DecodeObject(&v, value.Nested.Content)
   991  	if err != nil {
   992  		t.Errorf("unable to decode content, %v", err)
   993  		return
   994  	}
   995  
   996  	if v["hello"] != "world" {
   997  		t.Errorf("expected mapping to be returned")
   998  	}
   999  }
  1000  
  1001  // https://github.com/hashicorp/hcl/issues/60
  1002  func TestDecode_topLevelKeys(t *testing.T) {
  1003  	type Template struct {
  1004  		Source string
  1005  	}
  1006  
  1007  	templates := struct {
  1008  		Templates []*Template `hcl:"template"`
  1009  	}{}
  1010  
  1011  	err := Decode(&templates, `
  1012  	template {
  1013  	    source = "blah"
  1014  	}
  1015  
  1016  	template {
  1017  	    source = "blahblah"
  1018  	}`)
  1019  
  1020  	if err != nil {
  1021  		t.Fatal(err)
  1022  	}
  1023  
  1024  	if templates.Templates[0].Source != "blah" {
  1025  		t.Errorf("bad source: %s", templates.Templates[0].Source)
  1026  	}
  1027  
  1028  	if templates.Templates[1].Source != "blahblah" {
  1029  		t.Errorf("bad source: %s", templates.Templates[1].Source)
  1030  	}
  1031  }
  1032  
  1033  func TestDecode_flattenedJSON(t *testing.T) {
  1034  	// make sure we can also correctly extract a Name key too
  1035  	type V struct {
  1036  		Name        string `hcl:",key"`
  1037  		Description string
  1038  		Default     map[string]string
  1039  	}
  1040  	type Vars struct {
  1041  		Variable []*V
  1042  	}
  1043  
  1044  	cases := []struct {
  1045  		JSON     string
  1046  		Out      interface{}
  1047  		Expected interface{}
  1048  	}{
  1049  		{ // Nested object, no sibling keys
  1050  			JSON: `
  1051  {
  1052    "var_name": {
  1053      "default": {
  1054        "key1": "a",
  1055        "key2": "b"
  1056      }
  1057    }
  1058  }
  1059  			`,
  1060  			Out: &[]*V{},
  1061  			Expected: &[]*V{
  1062  				&V{
  1063  					Name:    "var_name",
  1064  					Default: map[string]string{"key1": "a", "key2": "b"},
  1065  				},
  1066  			},
  1067  		},
  1068  
  1069  		{ // Nested object with a sibling key (this worked previously)
  1070  			JSON: `
  1071  {
  1072    "var_name": {
  1073      "description": "Described",
  1074      "default": {
  1075        "key1": "a",
  1076        "key2": "b"
  1077      }
  1078    }
  1079  }
  1080  			`,
  1081  			Out: &[]*V{},
  1082  			Expected: &[]*V{
  1083  				&V{
  1084  					Name:        "var_name",
  1085  					Description: "Described",
  1086  					Default:     map[string]string{"key1": "a", "key2": "b"},
  1087  				},
  1088  			},
  1089  		},
  1090  
  1091  		{ // Multiple nested objects, one with a sibling key
  1092  			JSON: `
  1093  {
  1094    "variable": {
  1095      "var_1": {
  1096        "default": {
  1097          "key1": "a",
  1098          "key2": "b"
  1099        }
  1100      },
  1101      "var_2": {
  1102        "description": "Described",
  1103        "default": {
  1104          "key1": "a",
  1105          "key2": "b"
  1106        }
  1107      }
  1108    }
  1109  }
  1110  			`,
  1111  			Out: &Vars{},
  1112  			Expected: &Vars{
  1113  				Variable: []*V{
  1114  					&V{
  1115  						Name:    "var_1",
  1116  						Default: map[string]string{"key1": "a", "key2": "b"},
  1117  					},
  1118  					&V{
  1119  						Name:        "var_2",
  1120  						Description: "Described",
  1121  						Default:     map[string]string{"key1": "a", "key2": "b"},
  1122  					},
  1123  				},
  1124  			},
  1125  		},
  1126  
  1127  		{ // Nested object to maps
  1128  			JSON: `
  1129  {
  1130    "variable": {
  1131      "var_name": {
  1132        "description": "Described",
  1133        "default": {
  1134          "key1": "a",
  1135          "key2": "b"
  1136        }
  1137      }
  1138    }
  1139  }
  1140  			`,
  1141  			Out: &[]map[string]interface{}{},
  1142  			Expected: &[]map[string]interface{}{
  1143  				{
  1144  					"variable": []map[string]interface{}{
  1145  						{
  1146  							"var_name": []map[string]interface{}{
  1147  								{
  1148  									"description": "Described",
  1149  									"default": []map[string]interface{}{
  1150  										{
  1151  											"key1": "a",
  1152  											"key2": "b",
  1153  										},
  1154  									},
  1155  								},
  1156  							},
  1157  						},
  1158  					},
  1159  				},
  1160  			},
  1161  		},
  1162  
  1163  		{ // Nested object to maps without a sibling key should decode the same as above
  1164  			JSON: `
  1165  {
  1166    "variable": {
  1167      "var_name": {
  1168        "default": {
  1169          "key1": "a",
  1170          "key2": "b"
  1171        }
  1172      }
  1173    }
  1174  }
  1175  			`,
  1176  			Out: &[]map[string]interface{}{},
  1177  			Expected: &[]map[string]interface{}{
  1178  				{
  1179  					"variable": []map[string]interface{}{
  1180  						{
  1181  							"var_name": []map[string]interface{}{
  1182  								{
  1183  									"default": []map[string]interface{}{
  1184  										{
  1185  											"key1": "a",
  1186  											"key2": "b",
  1187  										},
  1188  									},
  1189  								},
  1190  							},
  1191  						},
  1192  					},
  1193  				},
  1194  			},
  1195  		},
  1196  
  1197  		{ // Nested objects, one with a sibling key, and one without
  1198  			JSON: `
  1199  {
  1200    "variable": {
  1201      "var_1": {
  1202        "default": {
  1203          "key1": "a",
  1204          "key2": "b"
  1205        }
  1206      },
  1207      "var_2": {
  1208        "description": "Described",
  1209        "default": {
  1210          "key1": "a",
  1211          "key2": "b"
  1212        }
  1213      }
  1214    }
  1215  }
  1216  			`,
  1217  			Out: &[]map[string]interface{}{},
  1218  			Expected: &[]map[string]interface{}{
  1219  				{
  1220  					"variable": []map[string]interface{}{
  1221  						{
  1222  							"var_1": []map[string]interface{}{
  1223  								{
  1224  									"default": []map[string]interface{}{
  1225  										{
  1226  											"key1": "a",
  1227  											"key2": "b",
  1228  										},
  1229  									},
  1230  								},
  1231  							},
  1232  						},
  1233  					},
  1234  				},
  1235  				{
  1236  					"variable": []map[string]interface{}{
  1237  						{
  1238  							"var_2": []map[string]interface{}{
  1239  								{
  1240  									"description": "Described",
  1241  									"default": []map[string]interface{}{
  1242  										{
  1243  											"key1": "a",
  1244  											"key2": "b",
  1245  										},
  1246  									},
  1247  								},
  1248  							},
  1249  						},
  1250  					},
  1251  				},
  1252  			},
  1253  		},
  1254  	}
  1255  
  1256  	for i, tc := range cases {
  1257  		err := Decode(tc.Out, tc.JSON)
  1258  		if err != nil {
  1259  			t.Fatalf("[%d] err: %s", i, err)
  1260  		}
  1261  
  1262  		if !reflect.DeepEqual(tc.Out, tc.Expected) {
  1263  			t.Fatalf("[%d]\ngot: %s\nexpected: %s\n", i, spew.Sdump(tc.Out), spew.Sdump(tc.Expected))
  1264  		}
  1265  	}
  1266  }
  1267  

View as plain text