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  }