github.com/hashicorp/hcl/v2@v2.20.0/ext/dynblock/expand_body_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package dynblock 5 6 import ( 7 "strings" 8 "testing" 9 10 "github.com/davecgh/go-spew/spew" 11 "github.com/hashicorp/hcl/v2" 12 "github.com/hashicorp/hcl/v2/hcldec" 13 "github.com/hashicorp/hcl/v2/hcltest" 14 "github.com/zclconf/go-cty-debug/ctydebug" 15 "github.com/zclconf/go-cty/cty" 16 ) 17 18 func TestExpand(t *testing.T) { 19 srcBody := hcltest.MockBody(&hcl.BodyContent{ 20 Blocks: hcl.Blocks{ 21 { 22 Type: "a", 23 Labels: []string{"static0"}, 24 LabelRanges: []hcl.Range{hcl.Range{}}, 25 Body: hcltest.MockBody(&hcl.BodyContent{ 26 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 27 "val": hcltest.MockExprLiteral(cty.StringVal("static a 0")), 28 }), 29 }), 30 }, 31 { 32 Type: "b", 33 Body: hcltest.MockBody(&hcl.BodyContent{ 34 Blocks: hcl.Blocks{ 35 { 36 Type: "c", 37 Body: hcltest.MockBody(&hcl.BodyContent{ 38 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 39 "val0": hcltest.MockExprLiteral(cty.StringVal("static c 0")), 40 }), 41 }), 42 }, 43 { 44 Type: "dynamic", 45 Labels: []string{"c"}, 46 LabelRanges: []hcl.Range{hcl.Range{}}, 47 Body: hcltest.MockBody(&hcl.BodyContent{ 48 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 49 "for_each": hcltest.MockExprLiteral(cty.ListVal([]cty.Value{ 50 cty.StringVal("dynamic c 0"), 51 cty.StringVal("dynamic c 1"), 52 })), 53 "iterator": hcltest.MockExprVariable("dyn_c"), 54 }), 55 Blocks: hcl.Blocks{ 56 { 57 Type: "content", 58 Body: hcltest.MockBody(&hcl.BodyContent{ 59 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 60 "val0": hcltest.MockExprTraversalSrc("dyn_c.value"), 61 }), 62 }), 63 }, 64 }, 65 }), 66 }, 67 }, 68 }), 69 }, 70 { 71 Type: "dynamic", 72 Labels: []string{"a"}, 73 LabelRanges: []hcl.Range{hcl.Range{}}, 74 Body: hcltest.MockBody(&hcl.BodyContent{ 75 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 76 "for_each": hcltest.MockExprLiteral(cty.ListVal([]cty.Value{ 77 cty.StringVal("dynamic a 0"), 78 cty.StringVal("dynamic a 1"), 79 cty.StringVal("dynamic a 2"), 80 })), 81 "labels": hcltest.MockExprList([]hcl.Expression{ 82 hcltest.MockExprTraversalSrc("a.key"), 83 }), 84 }), 85 Blocks: hcl.Blocks{ 86 { 87 Type: "content", 88 Body: hcltest.MockBody(&hcl.BodyContent{ 89 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 90 "val": hcltest.MockExprTraversalSrc("a.value"), 91 }), 92 }), 93 }, 94 }, 95 }), 96 }, 97 { 98 Type: "dynamic", 99 Labels: []string{"b"}, 100 LabelRanges: []hcl.Range{hcl.Range{}}, 101 Body: hcltest.MockBody(&hcl.BodyContent{ 102 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 103 "for_each": hcltest.MockExprLiteral(cty.ListVal([]cty.Value{ 104 cty.StringVal("dynamic b 0"), 105 cty.StringVal("dynamic b 1"), 106 })), 107 "iterator": hcltest.MockExprVariable("dyn_b"), 108 }), 109 Blocks: hcl.Blocks{ 110 { 111 Type: "content", 112 Body: hcltest.MockBody(&hcl.BodyContent{ 113 Blocks: hcl.Blocks{ 114 { 115 Type: "c", 116 Body: hcltest.MockBody(&hcl.BodyContent{ 117 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 118 "val0": hcltest.MockExprLiteral(cty.StringVal("static c 1")), 119 "val1": hcltest.MockExprTraversalSrc("dyn_b.value"), 120 }), 121 }), 122 }, 123 { 124 Type: "dynamic", 125 Labels: []string{"c"}, 126 LabelRanges: []hcl.Range{hcl.Range{}}, 127 Body: hcltest.MockBody(&hcl.BodyContent{ 128 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 129 "for_each": hcltest.MockExprLiteral(cty.ListVal([]cty.Value{ 130 cty.StringVal("dynamic c 2"), 131 cty.StringVal("dynamic c 3"), 132 })), 133 }), 134 Blocks: hcl.Blocks{ 135 { 136 Type: "content", 137 Body: hcltest.MockBody(&hcl.BodyContent{ 138 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 139 "val0": hcltest.MockExprTraversalSrc("c.value"), 140 "val1": hcltest.MockExprTraversalSrc("dyn_b.value"), 141 }), 142 }), 143 }, 144 }, 145 }), 146 }, 147 }, 148 }), 149 }, 150 }, 151 }), 152 }, 153 { 154 Type: "dynamic", 155 Labels: []string{"b"}, 156 LabelRanges: []hcl.Range{hcl.Range{}}, 157 Body: hcltest.MockBody(&hcl.BodyContent{ 158 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 159 "for_each": hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{ 160 "foo": cty.ListVal([]cty.Value{ 161 cty.StringVal("dynamic c nested 0"), 162 cty.StringVal("dynamic c nested 1"), 163 }), 164 })), 165 "iterator": hcltest.MockExprVariable("dyn_b"), 166 }), 167 Blocks: hcl.Blocks{ 168 { 169 Type: "content", 170 Body: hcltest.MockBody(&hcl.BodyContent{ 171 Blocks: hcl.Blocks{ 172 { 173 Type: "dynamic", 174 Labels: []string{"c"}, 175 LabelRanges: []hcl.Range{hcl.Range{}}, 176 Body: hcltest.MockBody(&hcl.BodyContent{ 177 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 178 "for_each": hcltest.MockExprTraversalSrc("dyn_b.value"), 179 }), 180 Blocks: hcl.Blocks{ 181 { 182 Type: "content", 183 Body: hcltest.MockBody(&hcl.BodyContent{ 184 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 185 "val0": hcltest.MockExprTraversalSrc("c.value"), 186 "val1": hcltest.MockExprTraversalSrc("dyn_b.key"), 187 }), 188 }), 189 }, 190 }, 191 }), 192 }, 193 }, 194 }), 195 }, 196 }, 197 }), 198 }, 199 { 200 Type: "a", 201 Labels: []string{"static1"}, 202 LabelRanges: []hcl.Range{hcl.Range{}}, 203 Body: hcltest.MockBody(&hcl.BodyContent{ 204 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 205 "val": hcltest.MockExprLiteral(cty.StringVal("static a 1")), 206 }), 207 }), 208 }, 209 }, 210 }) 211 212 dynBody := Expand(srcBody, nil) 213 var remain hcl.Body 214 215 t.Run("PartialDecode", func(t *testing.T) { 216 decSpec := &hcldec.BlockMapSpec{ 217 TypeName: "a", 218 LabelNames: []string{"key"}, 219 Nested: &hcldec.AttrSpec{ 220 Name: "val", 221 Type: cty.String, 222 Required: true, 223 }, 224 } 225 226 var got cty.Value 227 var diags hcl.Diagnostics 228 got, remain, diags = hcldec.PartialDecode(dynBody, decSpec, nil) 229 if len(diags) != 0 { 230 t.Errorf("unexpected diagnostics") 231 for _, diag := range diags { 232 t.Logf("- %s", diag) 233 } 234 return 235 } 236 237 want := cty.MapVal(map[string]cty.Value{ 238 "static0": cty.StringVal("static a 0"), 239 "static1": cty.StringVal("static a 1"), 240 "0": cty.StringVal("dynamic a 0"), 241 "1": cty.StringVal("dynamic a 1"), 242 "2": cty.StringVal("dynamic a 2"), 243 }) 244 245 if !got.RawEquals(want) { 246 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) 247 } 248 }) 249 250 t.Run("Decode", func(t *testing.T) { 251 decSpec := &hcldec.BlockListSpec{ 252 TypeName: "b", 253 Nested: &hcldec.BlockListSpec{ 254 TypeName: "c", 255 Nested: &hcldec.ObjectSpec{ 256 "val0": &hcldec.AttrSpec{ 257 Name: "val0", 258 Type: cty.String, 259 }, 260 "val1": &hcldec.AttrSpec{ 261 Name: "val1", 262 Type: cty.String, 263 }, 264 }, 265 }, 266 } 267 268 var got cty.Value 269 var diags hcl.Diagnostics 270 got, diags = hcldec.Decode(remain, decSpec, nil) 271 if len(diags) != 0 { 272 t.Errorf("unexpected diagnostics") 273 for _, diag := range diags { 274 t.Logf("- %s", diag) 275 } 276 return 277 } 278 279 want := cty.ListVal([]cty.Value{ 280 cty.ListVal([]cty.Value{ 281 cty.ObjectVal(map[string]cty.Value{ 282 "val0": cty.StringVal("static c 0"), 283 "val1": cty.NullVal(cty.String), 284 }), 285 cty.ObjectVal(map[string]cty.Value{ 286 "val0": cty.StringVal("dynamic c 0"), 287 "val1": cty.NullVal(cty.String), 288 }), 289 cty.ObjectVal(map[string]cty.Value{ 290 "val0": cty.StringVal("dynamic c 1"), 291 "val1": cty.NullVal(cty.String), 292 }), 293 }), 294 cty.ListVal([]cty.Value{ 295 cty.ObjectVal(map[string]cty.Value{ 296 "val0": cty.StringVal("static c 1"), 297 "val1": cty.StringVal("dynamic b 0"), 298 }), 299 cty.ObjectVal(map[string]cty.Value{ 300 "val0": cty.StringVal("dynamic c 2"), 301 "val1": cty.StringVal("dynamic b 0"), 302 }), 303 cty.ObjectVal(map[string]cty.Value{ 304 "val0": cty.StringVal("dynamic c 3"), 305 "val1": cty.StringVal("dynamic b 0"), 306 }), 307 }), 308 cty.ListVal([]cty.Value{ 309 cty.ObjectVal(map[string]cty.Value{ 310 "val0": cty.StringVal("static c 1"), 311 "val1": cty.StringVal("dynamic b 1"), 312 }), 313 cty.ObjectVal(map[string]cty.Value{ 314 "val0": cty.StringVal("dynamic c 2"), 315 "val1": cty.StringVal("dynamic b 1"), 316 }), 317 cty.ObjectVal(map[string]cty.Value{ 318 "val0": cty.StringVal("dynamic c 3"), 319 "val1": cty.StringVal("dynamic b 1"), 320 }), 321 }), 322 cty.ListVal([]cty.Value{ 323 cty.ObjectVal(map[string]cty.Value{ 324 "val0": cty.StringVal("dynamic c nested 0"), 325 "val1": cty.StringVal("foo"), 326 }), 327 cty.ObjectVal(map[string]cty.Value{ 328 "val0": cty.StringVal("dynamic c nested 1"), 329 "val1": cty.StringVal("foo"), 330 }), 331 }), 332 }) 333 334 if !got.RawEquals(want) { 335 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) 336 } 337 }) 338 339 } 340 341 func TestExpandWithForEachCheck(t *testing.T) { 342 forEachExpr := hcltest.MockExprLiteral(cty.MapValEmpty(cty.String).Mark("boop")) 343 evalCtx := &hcl.EvalContext{} 344 srcContent := &hcl.BodyContent{ 345 Blocks: hcl.Blocks{ 346 { 347 Type: "dynamic", 348 Labels: []string{"foo"}, 349 LabelRanges: []hcl.Range{{}}, 350 Body: hcltest.MockBody(&hcl.BodyContent{ 351 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 352 "for_each": forEachExpr, 353 }), 354 Blocks: hcl.Blocks{ 355 { 356 Type: "content", 357 Body: hcltest.MockBody(&hcl.BodyContent{}), 358 }, 359 }, 360 }), 361 }, 362 }, 363 } 364 srcBody := hcltest.MockBody(srcContent) 365 366 hookCalled := false 367 var gotV cty.Value 368 var gotEvalCtx *hcl.EvalContext 369 370 expBody := Expand( 371 srcBody, evalCtx, 372 OptCheckForEach(func(v cty.Value, e hcl.Expression, ec *hcl.EvalContext) hcl.Diagnostics { 373 hookCalled = true 374 gotV = v 375 gotEvalCtx = ec 376 return hcl.Diagnostics{ 377 &hcl.Diagnostic{ 378 Severity: hcl.DiagError, 379 Summary: "Bad for_each", 380 Detail: "I don't like it.", 381 Expression: e, 382 EvalContext: ec, 383 Extra: "diagnostic extra", 384 }, 385 } 386 }), 387 ) 388 389 _, diags := expBody.Content(&hcl.BodySchema{ 390 Blocks: []hcl.BlockHeaderSchema{ 391 { 392 Type: "foo", 393 }, 394 }, 395 }) 396 if !diags.HasErrors() { 397 t.Fatal("succeeded; want an error") 398 } 399 if len(diags) != 1 { 400 t.Fatalf("wrong number of diagnostics; want only one\n%s", spew.Sdump(diags)) 401 } 402 if got, want := diags[0].Summary, "Bad for_each"; got != want { 403 t.Fatalf("wrong error\ngot: %s\nwant: %s\n\n%s", got, want, spew.Sdump(diags[0])) 404 } 405 if got, want := diags[0].Extra, "diagnostic extra"; got != want { 406 // This is important to allow the application which provided the 407 // hook to pass application-specific extra values through this 408 // API in case the hook's diagnostics need some sort of special 409 // treatment. 410 t.Fatalf("diagnostic didn't preserve 'extra' field\ngot: %s\nwant: %s\n\n%s", got, want, spew.Sdump(diags[0])) 411 } 412 413 if !hookCalled { 414 t.Fatal("check hook wasn't called") 415 } 416 if !gotV.HasMark("boop") { 417 t.Errorf("wrong value passed to check hook; want the value marked \"boop\"\n%s", ctydebug.ValueString(gotV)) 418 } 419 if gotEvalCtx != evalCtx { 420 t.Error("wrong EvalContext passed to check hook; want the one passed to Expand") 421 } 422 } 423 424 func TestExpandUnknownBodies(t *testing.T) { 425 srcContent := &hcl.BodyContent{ 426 Blocks: hcl.Blocks{ 427 { 428 Type: "dynamic", 429 Labels: []string{"list"}, 430 LabelRanges: []hcl.Range{hcl.Range{}}, 431 Body: hcltest.MockBody(&hcl.BodyContent{ 432 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 433 "for_each": hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.String))), 434 }), 435 Blocks: hcl.Blocks{ 436 { 437 Type: "content", 438 Body: hcltest.MockBody(&hcl.BodyContent{ 439 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 440 "val": hcltest.MockExprTraversalSrc("each.value"), 441 }), 442 }), 443 }, 444 }, 445 }), 446 }, 447 { 448 Type: "dynamic", 449 Labels: []string{"tuple"}, 450 LabelRanges: []hcl.Range{hcl.Range{}}, 451 Body: hcltest.MockBody(&hcl.BodyContent{ 452 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 453 "for_each": hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.String))), 454 }), 455 Blocks: hcl.Blocks{ 456 { 457 Type: "content", 458 Body: hcltest.MockBody(&hcl.BodyContent{ 459 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 460 "val": hcltest.MockExprTraversalSrc("each.value"), 461 }), 462 }), 463 }, 464 }, 465 }), 466 }, 467 { 468 Type: "dynamic", 469 Labels: []string{"set"}, 470 LabelRanges: []hcl.Range{hcl.Range{}}, 471 Body: hcltest.MockBody(&hcl.BodyContent{ 472 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 473 "for_each": hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.String))), 474 }), 475 Blocks: hcl.Blocks{ 476 { 477 Type: "content", 478 Body: hcltest.MockBody(&hcl.BodyContent{ 479 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 480 "val": hcltest.MockExprTraversalSrc("each.value"), 481 }), 482 }), 483 }, 484 }, 485 }), 486 }, 487 { 488 Type: "dynamic", 489 Labels: []string{"map"}, 490 LabelRanges: []hcl.Range{hcl.Range{}}, 491 Body: hcltest.MockBody(&hcl.BodyContent{ 492 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 493 "for_each": hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.String))), 494 "labels": hcltest.MockExprList([]hcl.Expression{ 495 hcltest.MockExprLiteral(cty.StringVal("static")), 496 }), 497 }), 498 Blocks: hcl.Blocks{ 499 { 500 Type: "content", 501 Body: hcltest.MockBody(&hcl.BodyContent{ 502 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 503 "val": hcltest.MockExprTraversalSrc("each.value"), 504 }), 505 }), 506 }, 507 }, 508 }), 509 }, 510 { 511 Type: "dynamic", 512 Labels: []string{"object"}, 513 LabelRanges: []hcl.Range{hcl.Range{}}, 514 Body: hcltest.MockBody(&hcl.BodyContent{ 515 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 516 "for_each": hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.String))), 517 "labels": hcltest.MockExprList([]hcl.Expression{ 518 hcltest.MockExprLiteral(cty.StringVal("static")), 519 }), 520 }), 521 Blocks: hcl.Blocks{ 522 { 523 Type: "content", 524 Body: hcltest.MockBody(&hcl.BodyContent{ 525 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 526 "val": hcltest.MockExprTraversalSrc("each.value"), 527 }), 528 }), 529 }, 530 }, 531 }), 532 }, 533 { 534 Type: "dynamic", 535 Labels: []string{"invalid_list"}, 536 LabelRanges: []hcl.Range{hcl.Range{}}, 537 Body: hcltest.MockBody(&hcl.BodyContent{ 538 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 539 "for_each": hcltest.MockExprLiteral(cty.UnknownVal(cty.Map(cty.String))), 540 }), 541 Blocks: hcl.Blocks{ 542 { 543 Type: "content", 544 Body: hcltest.MockBody(&hcl.BodyContent{ 545 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 546 "val": hcltest.MockExprTraversalSrc("each.value"), 547 // unexpected attributes should still produce an error 548 "invalid": hcltest.MockExprLiteral(cty.StringVal("static")), 549 }), 550 }), 551 }, 552 }, 553 }), 554 }, 555 }, 556 } 557 558 srcBody := hcltest.MockBody(srcContent) 559 dynBody := Expand(srcBody, nil) 560 561 t.Run("DecodeList", func(t *testing.T) { 562 decSpec := &hcldec.BlockListSpec{ 563 TypeName: "list", 564 Nested: &hcldec.ObjectSpec{ 565 "val": &hcldec.AttrSpec{ 566 Name: "val", 567 Type: cty.String, 568 }, 569 }, 570 } 571 572 var got cty.Value 573 var diags hcl.Diagnostics 574 575 got, _, diags = hcldec.PartialDecode(dynBody, decSpec, nil) 576 if len(diags) != 0 { 577 t.Errorf("unexpected diagnostics") 578 for _, diag := range diags { 579 t.Logf("- %s", diag) 580 } 581 return 582 } 583 584 want := cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ 585 "val": cty.String, 586 }))) 587 588 if !got.RawEquals(want) { 589 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) 590 } 591 }) 592 593 t.Run("DecodeTuple", func(t *testing.T) { 594 decSpec := &hcldec.BlockTupleSpec{ 595 TypeName: "tuple", 596 Nested: &hcldec.ObjectSpec{ 597 "val": &hcldec.AttrSpec{ 598 Name: "val", 599 Type: cty.String, 600 }, 601 }, 602 } 603 604 var got cty.Value 605 var diags hcl.Diagnostics 606 607 got, _, diags = hcldec.PartialDecode(dynBody, decSpec, nil) 608 if len(diags) != 0 { 609 t.Errorf("unexpected diagnostics") 610 for _, diag := range diags { 611 t.Logf("- %s", diag) 612 } 613 return 614 } 615 616 want := cty.DynamicVal 617 618 if !got.RawEquals(want) { 619 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) 620 } 621 }) 622 623 t.Run("DecodeSet", func(t *testing.T) { 624 decSpec := &hcldec.BlockSetSpec{ 625 TypeName: "tuple", 626 Nested: &hcldec.ObjectSpec{ 627 "val": &hcldec.AttrSpec{ 628 Name: "val", 629 Type: cty.String, 630 }, 631 }, 632 } 633 634 var got cty.Value 635 var diags hcl.Diagnostics 636 637 got, _, diags = hcldec.PartialDecode(dynBody, decSpec, nil) 638 if len(diags) != 0 { 639 t.Errorf("unexpected diagnostics") 640 for _, diag := range diags { 641 t.Logf("- %s", diag) 642 } 643 return 644 } 645 646 want := cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{ 647 "val": cty.String, 648 }))) 649 650 if !got.RawEquals(want) { 651 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) 652 } 653 }) 654 655 t.Run("DecodeMap", func(t *testing.T) { 656 decSpec := &hcldec.BlockMapSpec{ 657 TypeName: "map", 658 LabelNames: []string{"key"}, 659 Nested: &hcldec.ObjectSpec{ 660 "val": &hcldec.AttrSpec{ 661 Name: "val", 662 Type: cty.String, 663 }, 664 }, 665 } 666 667 var got cty.Value 668 var diags hcl.Diagnostics 669 670 got, _, diags = hcldec.PartialDecode(dynBody, decSpec, nil) 671 if len(diags) != 0 { 672 t.Errorf("unexpected diagnostics") 673 for _, diag := range diags { 674 t.Logf("- %s", diag) 675 } 676 return 677 } 678 679 want := cty.UnknownVal(cty.Map(cty.Object(map[string]cty.Type{ 680 "val": cty.String, 681 }))) 682 683 if !got.RawEquals(want) { 684 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) 685 } 686 }) 687 688 t.Run("DecodeInvalidList", func(t *testing.T) { 689 decSpec := &hcldec.BlockListSpec{ 690 TypeName: "invalid_list", 691 Nested: &hcldec.ObjectSpec{ 692 "val": &hcldec.AttrSpec{ 693 Name: "val", 694 Type: cty.String, 695 }, 696 }, 697 } 698 699 _, _, diags := hcldec.PartialDecode(dynBody, decSpec, nil) 700 if len(diags) != 1 { 701 t.Error("expected 1 extraneous argument") 702 } 703 704 want := `Mock body has extraneous argument "invalid"` 705 706 if !strings.Contains(diags.Error(), want) { 707 t.Errorf("unexpected diagnostics: %v", diags) 708 } 709 }) 710 711 } 712 713 func TestExpandInvalidIteratorError(t *testing.T) { 714 srcBody := hcltest.MockBody(&hcl.BodyContent{ 715 Blocks: hcl.Blocks{ 716 { 717 Type: "dynamic", 718 Labels: []string{"b"}, 719 LabelRanges: []hcl.Range{hcl.Range{}}, 720 Body: hcltest.MockBody(&hcl.BodyContent{ 721 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 722 "for_each": hcltest.MockExprLiteral(cty.ListVal([]cty.Value{ 723 cty.StringVal("dynamic b 0"), 724 cty.StringVal("dynamic b 1"), 725 })), 726 "iterator": hcltest.MockExprLiteral(cty.StringVal("dyn_b")), 727 }), 728 Blocks: hcl.Blocks{ 729 { 730 Type: "content", 731 Body: hcltest.MockBody(&hcl.BodyContent{ 732 Blocks: hcl.Blocks{ 733 { 734 Type: "c", 735 Body: hcltest.MockBody(&hcl.BodyContent{ 736 Attributes: hcltest.MockAttrs(map[string]hcl.Expression{ 737 "val0": hcltest.MockExprLiteral(cty.StringVal("static c 1")), 738 "val1": hcltest.MockExprTraversalSrc("dyn_b.value"), 739 }), 740 }), 741 }, 742 }, 743 }), 744 }, 745 }, 746 }), 747 }, 748 }, 749 }) 750 751 dynBody := Expand(srcBody, nil) 752 753 t.Run("Decode", func(t *testing.T) { 754 decSpec := &hcldec.BlockListSpec{ 755 TypeName: "b", 756 Nested: &hcldec.BlockListSpec{ 757 TypeName: "c", 758 Nested: &hcldec.ObjectSpec{ 759 "val0": &hcldec.AttrSpec{ 760 Name: "val0", 761 Type: cty.String, 762 }, 763 "val1": &hcldec.AttrSpec{ 764 Name: "val1", 765 Type: cty.String, 766 }, 767 }, 768 }, 769 } 770 771 var diags hcl.Diagnostics 772 _, diags = hcldec.Decode(dynBody, decSpec, nil) 773 774 if len(diags) < 1 { 775 t.Errorf("Expected diagnostics, got none") 776 } 777 if len(diags) > 1 { 778 t.Errorf("Expected one diagnostic message, got %d", len(diags)) 779 for _, diag := range diags { 780 t.Logf("- %s", diag) 781 } 782 } 783 784 if diags[0].Summary != "Invalid expression" { 785 t.Errorf("Expected error subject to be invalid expression, instead it was %q", diags[0].Summary) 786 } 787 }) 788 789 }