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 }