github.com/hashicorp/hcl/v2@v2.20.0/ops_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hcl
     5  
     6  import (
     7  	"fmt"
     8  	"testing"
     9  
    10  	"github.com/zclconf/go-cty/cty"
    11  )
    12  
    13  func TestApplyPath(t *testing.T) {
    14  	tests := []struct {
    15  		Start   cty.Value
    16  		Path    cty.Path
    17  		Want    cty.Value
    18  		WantErr string
    19  	}{
    20  		{
    21  			cty.StringVal("hello"),
    22  			nil,
    23  			cty.StringVal("hello"),
    24  			``,
    25  		},
    26  		{
    27  			cty.StringVal("hello"),
    28  			(cty.Path)(nil).Index(cty.StringVal("boop")),
    29  			cty.NilVal,
    30  			`Invalid index: This value does not have any indices.`,
    31  		},
    32  		{
    33  			cty.StringVal("hello"),
    34  			(cty.Path)(nil).Index(cty.NumberIntVal(0)),
    35  			cty.NilVal,
    36  			`Invalid index: This value does not have any indices.`,
    37  		},
    38  		{
    39  			cty.ListVal([]cty.Value{
    40  				cty.StringVal("hello"),
    41  			}),
    42  			(cty.Path)(nil).Index(cty.NumberIntVal(0)),
    43  			cty.StringVal("hello"),
    44  			``,
    45  		},
    46  		{
    47  			cty.ListVal([]cty.Value{
    48  				cty.StringVal("hello"),
    49  			}).Mark("x"),
    50  			(cty.Path)(nil).Index(cty.NumberIntVal(0)),
    51  			cty.StringVal("hello").Mark("x"),
    52  			``,
    53  		},
    54  		{
    55  			cty.TupleVal([]cty.Value{
    56  				cty.StringVal("hello"),
    57  			}),
    58  			(cty.Path)(nil).Index(cty.NumberIntVal(0)),
    59  			cty.StringVal("hello"),
    60  			``,
    61  		},
    62  		{
    63  			cty.MapVal(map[string]cty.Value{
    64  				"a": cty.StringVal("foo").Mark("x"),
    65  				"b": cty.StringVal("bar").Mark("x"),
    66  			}).Mark("x"),
    67  			cty.GetAttrPath("a"),
    68  			cty.StringVal("foo").Mark("x"),
    69  			``,
    70  		},
    71  
    72  		{
    73  			cty.ListValEmpty(cty.String),
    74  			(cty.Path)(nil).Index(cty.NumberIntVal(0)),
    75  			cty.NilVal,
    76  			`Invalid index: The given key does not identify an element in this collection value: the collection has no elements.`,
    77  		},
    78  		{
    79  			cty.ListVal([]cty.Value{
    80  				cty.StringVal("hello"),
    81  			}),
    82  			(cty.Path)(nil).Index(cty.NumberIntVal(1)),
    83  			cty.NilVal,
    84  			`Invalid index: The given key does not identify an element in this collection value: the given index is greater than or equal to the length of the collection.`,
    85  		},
    86  		{
    87  			cty.ListVal([]cty.Value{
    88  				cty.StringVal("hello"),
    89  			}).Mark("boop"), // prevents us from making statements about the length of the list
    90  			(cty.Path)(nil).Index(cty.NumberIntVal(1)),
    91  			cty.NilVal,
    92  			`Invalid index: The given key does not identify an element in this collection value.`,
    93  		},
    94  		{
    95  			cty.ListVal([]cty.Value{
    96  				cty.StringVal("hello"),
    97  			}),
    98  			(cty.Path)(nil).Index(cty.NumberIntVal(-1)),
    99  			cty.NilVal,
   100  			`Invalid index: The given key does not identify an element in this collection value: a negative number is not a valid index for a sequence.`,
   101  		},
   102  		{
   103  			cty.ListVal([]cty.Value{
   104  				cty.StringVal("hello"),
   105  			}),
   106  			(cty.Path)(nil).Index(cty.NumberFloatVal(0.5)),
   107  			cty.NilVal,
   108  			`Invalid index: The given key does not identify an element in this collection value: indexing a sequence requires a whole number, but the given index has a fractional part.`,
   109  		},
   110  		{
   111  			cty.ListVal([]cty.Value{
   112  				cty.StringVal("hello"),
   113  			}),
   114  			(cty.Path)(nil).Index(cty.NumberIntVal(0)).GetAttr("foo"),
   115  			cty.NilVal,
   116  			`Unsupported attribute: Can't access attributes on a primitive-typed value (string).`,
   117  		},
   118  		{
   119  			cty.ListVal([]cty.Value{
   120  				cty.EmptyObjectVal,
   121  			}),
   122  			(cty.Path)(nil).Index(cty.NumberIntVal(0)).GetAttr("foo"),
   123  			cty.NilVal,
   124  			`Unsupported attribute: This object does not have an attribute named "foo".`,
   125  		},
   126  		{
   127  			cty.ListVal([]cty.Value{
   128  				cty.EmptyObjectVal,
   129  			}),
   130  			(cty.Path)(nil).GetAttr("foo"),
   131  			cty.NilVal,
   132  			`Unsupported attribute: Can't access attributes on a list of objects. Did you mean to access an attribute for a specific element of the list, or across all elements of the list?`,
   133  		},
   134  		{
   135  			cty.ListVal([]cty.Value{
   136  				cty.ObjectVal(map[string]cty.Value{
   137  					"foo": cty.True,
   138  				}),
   139  			}),
   140  			(cty.Path)(nil).GetAttr("foo"),
   141  			cty.NilVal,
   142  			`Unsupported attribute: Can't access attributes on a list of objects. Did you mean to access attribute "foo" for a specific element of the list, or across all elements of the list?`,
   143  		},
   144  
   145  		{
   146  			cty.EmptyTupleVal,
   147  			(cty.Path)(nil).Index(cty.NumberIntVal(0)),
   148  			cty.NilVal,
   149  			`Invalid index: The given key does not identify an element in this collection value: the collection has no elements.`,
   150  		},
   151  		{
   152  			cty.TupleVal([]cty.Value{
   153  				cty.StringVal("hello"),
   154  			}),
   155  			(cty.Path)(nil).Index(cty.NumberIntVal(1)),
   156  			cty.NilVal,
   157  			`Invalid index: The given key does not identify an element in this collection value: the given index is greater than or equal to the length of the collection.`,
   158  		},
   159  		{
   160  			cty.TupleVal([]cty.Value{
   161  				cty.StringVal("hello"),
   162  			}).Mark("boop"),
   163  			(cty.Path)(nil).Index(cty.NumberIntVal(1)),
   164  			cty.NilVal,
   165  			`Invalid index: The given key does not identify an element in this collection value.`,
   166  		},
   167  		{
   168  			cty.TupleVal([]cty.Value{
   169  				cty.StringVal("hello"),
   170  			}),
   171  			(cty.Path)(nil).Index(cty.NumberIntVal(-1)),
   172  			cty.NilVal,
   173  			`Invalid index: The given key does not identify an element in this collection value: a negative number is not a valid index for a sequence.`,
   174  		},
   175  		{
   176  			cty.TupleVal([]cty.Value{
   177  				cty.StringVal("hello"),
   178  			}),
   179  			(cty.Path)(nil).Index(cty.NumberFloatVal(0.5)),
   180  			cty.NilVal,
   181  			`Invalid index: The given key does not identify an element in this collection value: indexing a sequence requires a whole number, but the given index has a fractional part.`,
   182  		},
   183  		{
   184  			cty.TupleVal([]cty.Value{
   185  				cty.StringVal("hello"),
   186  			}),
   187  			(cty.Path)(nil).Index(cty.NumberIntVal(0)).GetAttr("foo"),
   188  			cty.NilVal,
   189  			`Unsupported attribute: Can't access attributes on a primitive-typed value (string).`,
   190  		},
   191  		{
   192  			cty.TupleVal([]cty.Value{
   193  				cty.EmptyObjectVal,
   194  			}),
   195  			(cty.Path)(nil).Index(cty.NumberIntVal(0)).GetAttr("foo"),
   196  			cty.NilVal,
   197  			`Unsupported attribute: This object does not have an attribute named "foo".`,
   198  		},
   199  		{
   200  			cty.TupleVal([]cty.Value{
   201  				cty.EmptyObjectVal,
   202  			}),
   203  			(cty.Path)(nil).GetAttr("foo"),
   204  			cty.NilVal,
   205  			`Unsupported attribute: This value does not have any attributes.`,
   206  		},
   207  		{
   208  			cty.TupleVal([]cty.Value{
   209  				cty.ObjectVal(map[string]cty.Value{
   210  					"foo": cty.True,
   211  				}),
   212  			}),
   213  			(cty.Path)(nil).GetAttr("foo"),
   214  			cty.NilVal,
   215  			`Unsupported attribute: This value does not have any attributes.`,
   216  		},
   217  
   218  		{
   219  			cty.SetVal([]cty.Value{
   220  				cty.StringVal("hello"),
   221  			}),
   222  			(cty.Path)(nil).Index(cty.NumberIntVal(1)),
   223  			cty.NilVal,
   224  			`Invalid index: Elements of a set are identified only by their value and don't have any separate index or key to select with, so it's only possible to perform operations across all elements of the set.`,
   225  		},
   226  		{
   227  			cty.SetVal([]cty.Value{
   228  				cty.EmptyObjectVal,
   229  			}),
   230  			(cty.Path)(nil).GetAttr("foo"),
   231  			cty.NilVal,
   232  			`Unsupported attribute: Can't access attributes on a set of objects. Did you mean to access an attribute across all elements of the set?`,
   233  		},
   234  		{
   235  			cty.NullVal(cty.List(cty.String)),
   236  			(cty.Path)(nil).Index(cty.NumberIntVal(0)),
   237  			cty.NilVal,
   238  			`Attempt to index null value: This value is null, so it does not have any indices.`,
   239  		},
   240  		{
   241  			cty.NullVal(cty.Map(cty.String)),
   242  			(cty.Path)(nil).Index(cty.NumberIntVal(0)),
   243  			cty.NilVal,
   244  			`Attempt to index null value: This value is null, so it does not have any indices.`,
   245  		},
   246  		{
   247  			cty.NullVal(cty.EmptyObject),
   248  			(cty.Path)(nil).GetAttr("foo"),
   249  			cty.NilVal,
   250  			`Attempt to get attribute from null value: This value is null, so it does not have any attributes.`,
   251  		},
   252  	}
   253  
   254  	for _, test := range tests {
   255  		t.Run(fmt.Sprintf("%#v %#v", test.Start, test.Path), func(t *testing.T) {
   256  			got, diags := ApplyPath(test.Start, test.Path, nil)
   257  			t.Logf("testing ApplyPath\nstart: %#v\npath:  %#v", test.Start, test.Path)
   258  
   259  			for _, diag := range diags {
   260  				t.Logf(diag.Error())
   261  			}
   262  
   263  			if test.WantErr != "" {
   264  				if !diags.HasErrors() {
   265  					t.Fatalf("succeeded, but want error\nwant error: %s", test.WantErr)
   266  				}
   267  				if len(diags) != 1 {
   268  					t.Fatalf("wrong number of diagnostics %d; want 1", len(diags))
   269  				}
   270  
   271  				if gotErrStr := diags[0].Summary + ": " + diags[0].Detail; gotErrStr != test.WantErr {
   272  					t.Fatalf("wrong error\ngot error:  %s\nwant error: %s", gotErrStr, test.WantErr)
   273  				}
   274  				return
   275  			}
   276  
   277  			if diags.HasErrors() {
   278  				t.Fatalf("failed, but want success\ngot diagnostics:\n%s", diags.Error())
   279  			}
   280  			if !test.Want.RawEquals(got) {
   281  				t.Fatalf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   282  			}
   283  		})
   284  	}
   285  }
   286  
   287  func TestIndex(t *testing.T) {
   288  	tests := map[string]struct {
   289  		coll cty.Value
   290  		key  cty.Value
   291  		want cty.Value
   292  		err  string
   293  	}{
   294  		"marked key to maked value": {
   295  			coll: cty.ListVal([]cty.Value{
   296  				cty.StringVal("a"),
   297  			}),
   298  			key:  cty.NumberIntVal(0).Mark("marked"),
   299  			want: cty.StringVal("a").Mark("marked"),
   300  		},
   301  		"missing list key": {
   302  			coll: cty.ListVal([]cty.Value{
   303  				cty.StringVal("a"),
   304  			}),
   305  			key:  cty.NumberIntVal(1).Mark("marked"),
   306  			want: cty.DynamicVal,
   307  			err:  "Invalid index",
   308  		},
   309  		"null marked key": {
   310  			coll: cty.ListVal([]cty.Value{
   311  				cty.StringVal("a"),
   312  			}),
   313  			key:  cty.NullVal(cty.Number).Mark("marked"),
   314  			want: cty.DynamicVal,
   315  			err:  "Invalid index",
   316  		},
   317  		"dynamic key": {
   318  			coll: cty.ListVal([]cty.Value{
   319  				cty.StringVal("a"),
   320  			}),
   321  			key:  cty.DynamicVal,
   322  			want: cty.DynamicVal,
   323  		},
   324  		"invalid marked key type": {
   325  			coll: cty.ListVal([]cty.Value{
   326  				cty.StringVal("a"),
   327  			}),
   328  			key:  cty.StringVal("foo").Mark("marked"),
   329  			want: cty.DynamicVal,
   330  			err:  "Invalid index",
   331  		},
   332  		"marked map key": {
   333  			coll: cty.MapVal(map[string]cty.Value{
   334  				"foo": cty.StringVal("a"),
   335  			}),
   336  			key:  cty.StringVal("foo").Mark("marked"),
   337  			want: cty.StringVal("a").Mark("marked"),
   338  		},
   339  		"missing marked map key": {
   340  			coll: cty.MapVal(map[string]cty.Value{
   341  				"foo": cty.StringVal("a"),
   342  			}),
   343  			key:  cty.StringVal("bar").Mark("mark"),
   344  			want: cty.DynamicVal,
   345  			err:  "Invalid index",
   346  		},
   347  		"marked object key": {
   348  			coll: cty.ObjectVal(map[string]cty.Value{
   349  				"foo": cty.StringVal("a"),
   350  			}),
   351  			key: cty.StringVal("foo").Mark("marked"),
   352  			// an object attribute is fetched by string index, and the marks
   353  			// are not maintained
   354  			want: cty.StringVal("a"),
   355  		},
   356  		"invalid marked object key type": {
   357  			coll: cty.ObjectVal(map[string]cty.Value{
   358  				"foo": cty.StringVal("a"),
   359  			}),
   360  			key:  cty.ListVal([]cty.Value{cty.NullVal(cty.String)}).Mark("marked"),
   361  			want: cty.DynamicVal,
   362  			err:  "Invalid index",
   363  		},
   364  		"invalid marked object key": {
   365  			coll: cty.ObjectVal(map[string]cty.Value{
   366  				"foo": cty.StringVal("a"),
   367  			}),
   368  			key:  cty.NumberIntVal(0).Mark("marked"),
   369  			want: cty.DynamicVal,
   370  			err:  "Invalid index",
   371  		},
   372  	}
   373  
   374  	for name, tc := range tests {
   375  		t.Run(name, func(t *testing.T) {
   376  			t.Logf("testing Index\ncollection: %#v\nkey:  %#v", tc.coll, tc.key)
   377  
   378  			got, diags := Index(tc.coll, tc.key, nil)
   379  
   380  			for _, diag := range diags {
   381  				t.Logf(diag.Error())
   382  			}
   383  
   384  			if tc.err != "" {
   385  				if !diags.HasErrors() {
   386  					t.Fatalf("succeeded, but want error\nwant error: %s", tc.err)
   387  				}
   388  				if len(diags) != 1 {
   389  					t.Fatalf("wrong number of diagnostics %d; want 1", len(diags))
   390  				}
   391  
   392  				if gotErrStr := diags[0].Summary; gotErrStr != tc.err {
   393  					t.Fatalf("wrong error\ngot error:  %s\nwant error: %s", gotErrStr, tc.err)
   394  				}
   395  				return
   396  			}
   397  
   398  			if diags.HasErrors() {
   399  				t.Fatalf("failed, but want success\ngot diagnostics:\n%s", diags.Error())
   400  			}
   401  			if !tc.want.RawEquals(got) {
   402  				t.Fatalf("wrong result\ngot:  %#v\nwant: %#v", got, tc.want)
   403  			}
   404  		})
   405  	}
   406  }