hcldec: BlockTupleSpec and BlockObjectSpec
When nested attributes are of type cty.DynamicPseudoType, a block spec that is backed by a cty collection is annoying to use because it requires all of the blocks to have homogenous types for such attributes. These new specs are similar to BlockListSpec and BlockMapSpec respectively, but permit each nested block result to have its own distinct type. In return for this flexibility, we lose the ability to predict the exact type of the result: these specs must just indicate their type as being cty.DynamicPseudoType themselves, since we need to know how many blocks there are and what types are inside them before we can know our final result type.
This commit is contained in:
parent
b82170e941
commit
ed8144cda1
@ -714,6 +714,309 @@ b "foo" {}
|
|||||||
cty.MapValEmpty(cty.String),
|
cty.MapValEmpty(cty.String),
|
||||||
1, // missing name
|
1, // missing name
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b {}
|
||||||
|
b {}
|
||||||
|
`,
|
||||||
|
&BlockTupleSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.TupleVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
``,
|
||||||
|
&BlockTupleSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.EmptyTupleVal,
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" {}
|
||||||
|
b "bar" {}
|
||||||
|
`,
|
||||||
|
&BlockTupleSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
Nested: &BlockLabelSpec{
|
||||||
|
Name: "name",
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b {}
|
||||||
|
b {}
|
||||||
|
b {}
|
||||||
|
`,
|
||||||
|
&BlockTupleSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
MaxItems: 2,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.TupleVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal, cty.EmptyObjectVal}),
|
||||||
|
1, // too many b blocks
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b {}
|
||||||
|
b {}
|
||||||
|
`,
|
||||||
|
&BlockTupleSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
MinItems: 10,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.TupleVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}),
|
||||||
|
1, // insufficient b blocks
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b {
|
||||||
|
a = true
|
||||||
|
}
|
||||||
|
b {
|
||||||
|
a = 1
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
&BlockTupleSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
Nested: &AttrSpec{
|
||||||
|
Name: "a",
|
||||||
|
Type: cty.DynamicPseudoType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.True,
|
||||||
|
cty.NumberIntVal(1),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b {
|
||||||
|
a = true
|
||||||
|
}
|
||||||
|
b {
|
||||||
|
a = "not a bool"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
&BlockTupleSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
Nested: &AttrSpec{
|
||||||
|
Name: "a",
|
||||||
|
Type: cty.DynamicPseudoType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.TupleVal([]cty.Value{
|
||||||
|
cty.True,
|
||||||
|
cty.StringVal("not a bool"),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" {}
|
||||||
|
b "bar" {}
|
||||||
|
`,
|
||||||
|
&BlockObjectSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.ObjectVal(map[string]cty.Value{"foo": cty.EmptyObjectVal, "bar": cty.EmptyObjectVal}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" "bar" {}
|
||||||
|
b "bar" "baz" {}
|
||||||
|
`,
|
||||||
|
&BlockObjectSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key1", "key2"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.EmptyObjectVal,
|
||||||
|
}),
|
||||||
|
"bar": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"baz": cty.EmptyObjectVal,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" "bar" {}
|
||||||
|
b "bar" "bar" {}
|
||||||
|
`,
|
||||||
|
&BlockObjectSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key1", "key2"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.EmptyObjectVal,
|
||||||
|
}),
|
||||||
|
"bar": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.EmptyObjectVal,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" "bar" {}
|
||||||
|
b "foo" "baz" {}
|
||||||
|
`,
|
||||||
|
&BlockObjectSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key1", "key2"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.EmptyObjectVal,
|
||||||
|
"baz": cty.EmptyObjectVal,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" "bar" {}
|
||||||
|
`,
|
||||||
|
&BlockObjectSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.EmptyObjectVal,
|
||||||
|
1, // too many labels
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "bar" {}
|
||||||
|
`,
|
||||||
|
&BlockObjectSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key1", "key2"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.EmptyObjectVal,
|
||||||
|
1, // not enough labels
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" {}
|
||||||
|
b "foo" {}
|
||||||
|
`,
|
||||||
|
&BlockObjectSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.ObjectVal(map[string]cty.Value{"foo": cty.EmptyObjectVal}),
|
||||||
|
1, // duplicate b block
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" "bar" {}
|
||||||
|
b "foo" "bar" {}
|
||||||
|
`,
|
||||||
|
&BlockObjectSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key1", "key2"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.ObjectVal(map[string]cty.Value{"foo": cty.ObjectVal(map[string]cty.Value{"bar": cty.EmptyObjectVal})}),
|
||||||
|
1, // duplicate b block
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" "bar" {}
|
||||||
|
b "bar" "baz" {}
|
||||||
|
`,
|
||||||
|
&BlockObjectSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"type"},
|
||||||
|
Nested: &BlockLabelSpec{
|
||||||
|
Name: "name",
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.StringVal("bar"),
|
||||||
|
"bar": cty.StringVal("baz"),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" {}
|
||||||
|
`,
|
||||||
|
&BlockObjectSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"type"},
|
||||||
|
Nested: &BlockLabelSpec{
|
||||||
|
Name: "name",
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.EmptyObjectVal,
|
||||||
|
1, // missing name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" {
|
||||||
|
arg = true
|
||||||
|
}
|
||||||
|
b "bar" {
|
||||||
|
arg = 1
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
&BlockObjectSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"type"},
|
||||||
|
Nested: &AttrSpec{
|
||||||
|
Name: "arg",
|
||||||
|
Type: cty.DynamicPseudoType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.True,
|
||||||
|
"bar": cty.NumberIntVal(1),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
|
267
hcldec/spec.go
267
hcldec/spec.go
@ -547,6 +547,127 @@ func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, blockLabels []bloc
|
|||||||
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A BlockTupleSpec is a Spec that produces a cty tuple of the results of
|
||||||
|
// decoding all of the nested blocks of a given type, using a nested spec.
|
||||||
|
//
|
||||||
|
// This is similar to BlockListSpec, but it permits the nested blocks to have
|
||||||
|
// different result types in situations where cty.DynamicPseudoType attributes
|
||||||
|
// are present.
|
||||||
|
type BlockTupleSpec struct {
|
||||||
|
TypeName string
|
||||||
|
Nested Spec
|
||||||
|
MinItems int
|
||||||
|
MaxItems int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlockTupleSpec) visitSameBodyChildren(cb visitFunc) {
|
||||||
|
// leaf node ("Nested" does not use the same body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockSpec implementation
|
||||||
|
func (s *BlockTupleSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
||||||
|
return []hcl.BlockHeaderSchema{
|
||||||
|
{
|
||||||
|
Type: s.TypeName,
|
||||||
|
LabelNames: findLabelSpecs(s.Nested),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockSpec implementation
|
||||||
|
func (s *BlockTupleSpec) nestedSpec() Spec {
|
||||||
|
return s.Nested
|
||||||
|
}
|
||||||
|
|
||||||
|
// specNeedingVariables implementation
|
||||||
|
func (s *BlockTupleSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
|
||||||
|
var ret []hcl.Traversal
|
||||||
|
|
||||||
|
for _, childBlock := range content.Blocks {
|
||||||
|
if childBlock.Type != s.TypeName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, Variables(childBlock.Body, s.Nested)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlockTupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
|
var diags hcl.Diagnostics
|
||||||
|
|
||||||
|
if s.Nested == nil {
|
||||||
|
panic("BlockListSpec with no Nested Spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
var elems []cty.Value
|
||||||
|
var sourceRanges []hcl.Range
|
||||||
|
for _, childBlock := range content.Blocks {
|
||||||
|
if childBlock.Type != s.TypeName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
|
||||||
|
diags = append(diags, childDiags...)
|
||||||
|
elems = append(elems, val)
|
||||||
|
sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(elems) < s.MinItems {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: fmt.Sprintf("Insufficient %s blocks", s.TypeName),
|
||||||
|
Detail: fmt.Sprintf("At least %d %q blocks are required.", s.MinItems, s.TypeName),
|
||||||
|
Subject: &content.MissingItemRange,
|
||||||
|
})
|
||||||
|
} else if s.MaxItems > 0 && len(elems) > s.MaxItems {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: fmt.Sprintf("Too many %s blocks", s.TypeName),
|
||||||
|
Detail: fmt.Sprintf("No more than %d %q blocks are allowed", s.MaxItems, s.TypeName),
|
||||||
|
Subject: &sourceRanges[s.MaxItems],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret cty.Value
|
||||||
|
|
||||||
|
if len(elems) == 0 {
|
||||||
|
ret = cty.EmptyTupleVal
|
||||||
|
} else {
|
||||||
|
ret = cty.TupleVal(elems)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlockTupleSpec) impliedType() cty.Type {
|
||||||
|
// We can't predict our type, because we don't know how many blocks
|
||||||
|
// there will be until we decode.
|
||||||
|
return cty.DynamicPseudoType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlockTupleSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||||
|
// We return the source range of the _first_ block of the given type,
|
||||||
|
// since they are not guaranteed to form a contiguous range.
|
||||||
|
|
||||||
|
var childBlock *hcl.Block
|
||||||
|
for _, candidate := range content.Blocks {
|
||||||
|
if candidate.Type != s.TypeName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
childBlock = candidate
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if childBlock == nil {
|
||||||
|
return content.MissingItemRange
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
||||||
|
}
|
||||||
|
|
||||||
// A BlockSetSpec is a Spec that produces a cty set of the results of
|
// A BlockSetSpec is a Spec that produces a cty set of the results of
|
||||||
// decoding all of the nested blocks of a given type, using a nested spec.
|
// decoding all of the nested blocks of a given type, using a nested spec.
|
||||||
type BlockSetSpec struct {
|
type BlockSetSpec struct {
|
||||||
@ -749,7 +870,7 @@ func (s *BlockMapSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel
|
|||||||
var diags hcl.Diagnostics
|
var diags hcl.Diagnostics
|
||||||
|
|
||||||
if s.Nested == nil {
|
if s.Nested == nil {
|
||||||
panic("BlockSetSpec with no Nested Spec")
|
panic("BlockMapSpec with no Nested Spec")
|
||||||
}
|
}
|
||||||
if ImpliedType(s).HasDynamicTypes() {
|
if ImpliedType(s).HasDynamicTypes() {
|
||||||
panic("cty.DynamicPseudoType attributes may not be used inside a BlockMapSpec")
|
panic("cty.DynamicPseudoType attributes may not be used inside a BlockMapSpec")
|
||||||
@ -845,6 +966,150 @@ func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, blockLabels []block
|
|||||||
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A BlockObjectSpec is a Spec that produces a cty object of the results of
|
||||||
|
// decoding all of the nested blocks of a given type, using a nested spec.
|
||||||
|
//
|
||||||
|
// One level of object structure is created for each of the given label names.
|
||||||
|
// There must be at least one given label name.
|
||||||
|
//
|
||||||
|
// This is similar to BlockMapSpec, but it permits the nested blocks to have
|
||||||
|
// different result types in situations where cty.DynamicPseudoType attributes
|
||||||
|
// are present.
|
||||||
|
type BlockObjectSpec struct {
|
||||||
|
TypeName string
|
||||||
|
LabelNames []string
|
||||||
|
Nested Spec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlockObjectSpec) visitSameBodyChildren(cb visitFunc) {
|
||||||
|
// leaf node ("Nested" does not use the same body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockSpec implementation
|
||||||
|
func (s *BlockObjectSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
||||||
|
return []hcl.BlockHeaderSchema{
|
||||||
|
{
|
||||||
|
Type: s.TypeName,
|
||||||
|
LabelNames: append(s.LabelNames, findLabelSpecs(s.Nested)...),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockSpec implementation
|
||||||
|
func (s *BlockObjectSpec) nestedSpec() Spec {
|
||||||
|
return s.Nested
|
||||||
|
}
|
||||||
|
|
||||||
|
// specNeedingVariables implementation
|
||||||
|
func (s *BlockObjectSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
|
||||||
|
var ret []hcl.Traversal
|
||||||
|
|
||||||
|
for _, childBlock := range content.Blocks {
|
||||||
|
if childBlock.Type != s.TypeName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, Variables(childBlock.Body, s.Nested)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlockObjectSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
|
var diags hcl.Diagnostics
|
||||||
|
|
||||||
|
if s.Nested == nil {
|
||||||
|
panic("BlockObjectSpec with no Nested Spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
elems := map[string]interface{}{}
|
||||||
|
for _, childBlock := range content.Blocks {
|
||||||
|
if childBlock.Type != s.TypeName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
childLabels := labelsForBlock(childBlock)
|
||||||
|
val, _, childDiags := decode(childBlock.Body, childLabels[len(s.LabelNames):], ctx, s.Nested, false)
|
||||||
|
targetMap := elems
|
||||||
|
for _, key := range childBlock.Labels[:len(s.LabelNames)-1] {
|
||||||
|
if _, exists := targetMap[key]; !exists {
|
||||||
|
targetMap[key] = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
targetMap = targetMap[key].(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
diags = append(diags, childDiags...)
|
||||||
|
|
||||||
|
key := childBlock.Labels[len(s.LabelNames)-1]
|
||||||
|
if _, exists := targetMap[key]; exists {
|
||||||
|
labelsBuf := bytes.Buffer{}
|
||||||
|
for _, label := range childBlock.Labels {
|
||||||
|
fmt.Fprintf(&labelsBuf, " %q", label)
|
||||||
|
}
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: fmt.Sprintf("Duplicate %s block", s.TypeName),
|
||||||
|
Detail: fmt.Sprintf(
|
||||||
|
"A block for %s%s was already defined. The %s labels must be unique.",
|
||||||
|
s.TypeName, labelsBuf.String(), s.TypeName,
|
||||||
|
),
|
||||||
|
Subject: &childBlock.DefRange,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
targetMap[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(elems) == 0 {
|
||||||
|
return cty.EmptyObjectVal, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctyObj func(map[string]interface{}, int) cty.Value
|
||||||
|
ctyObj = func(raw map[string]interface{}, depth int) cty.Value {
|
||||||
|
vals := make(map[string]cty.Value, len(raw))
|
||||||
|
if depth == 1 {
|
||||||
|
for k, v := range raw {
|
||||||
|
vals[k] = v.(cty.Value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for k, v := range raw {
|
||||||
|
vals[k] = ctyObj(v.(map[string]interface{}), depth-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cty.ObjectVal(vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctyObj(elems, len(s.LabelNames)), diags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlockObjectSpec) impliedType() cty.Type {
|
||||||
|
// We can't predict our type, since we don't know how many blocks are
|
||||||
|
// present and what labels they have until we decode.
|
||||||
|
return cty.DynamicPseudoType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *BlockObjectSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||||
|
// We return the source range of the _first_ block of the given type,
|
||||||
|
// since they are not guaranteed to form a contiguous range.
|
||||||
|
|
||||||
|
var childBlock *hcl.Block
|
||||||
|
for _, candidate := range content.Blocks {
|
||||||
|
if candidate.Type != s.TypeName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
childBlock = candidate
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if childBlock == nil {
|
||||||
|
return content.MissingItemRange
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
||||||
|
}
|
||||||
|
|
||||||
// A BlockAttrsSpec is a Spec that interprets a single block as if it were
|
// A BlockAttrsSpec is a Spec that interprets a single block as if it were
|
||||||
// a map of some element type. That is, each attribute within the block
|
// a map of some element type. That is, each attribute within the block
|
||||||
// becomes a key in the resulting map and the attribute's value becomes the
|
// becomes a key in the resulting map and the attribute's value becomes the
|
||||||
|
Loading…
Reference in New Issue
Block a user