hcldec: BlockSetSpec and BlockMapSpec decode implementations
This commit is contained in:
parent
cbdf66e80a
commit
a6dff4e9f9
@ -245,6 +245,149 @@ b {}
|
|||||||
cty.ListVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}),
|
cty.ListVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}),
|
||||||
1, // insufficient b blocks
|
1, // insufficient b blocks
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b {}
|
||||||
|
b {}
|
||||||
|
`,
|
||||||
|
&BlockSetSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
MaxItems: 2,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.SetVal([]cty.Value{cty.EmptyObjectVal, cty.EmptyObjectVal}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" {}
|
||||||
|
b "bar" {}
|
||||||
|
`,
|
||||||
|
&BlockMapSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.MapVal(map[string]cty.Value{"foo": cty.EmptyObjectVal, "bar": cty.EmptyObjectVal}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" "bar" {}
|
||||||
|
b "bar" "baz" {}
|
||||||
|
`,
|
||||||
|
&BlockMapSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key1", "key2"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"foo": cty.MapVal(map[string]cty.Value{
|
||||||
|
"bar": cty.EmptyObjectVal,
|
||||||
|
}),
|
||||||
|
"bar": cty.MapVal(map[string]cty.Value{
|
||||||
|
"baz": cty.EmptyObjectVal,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" "bar" {}
|
||||||
|
b "bar" "bar" {}
|
||||||
|
`,
|
||||||
|
&BlockMapSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key1", "key2"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"foo": cty.MapVal(map[string]cty.Value{
|
||||||
|
"bar": cty.EmptyObjectVal,
|
||||||
|
}),
|
||||||
|
"bar": cty.MapVal(map[string]cty.Value{
|
||||||
|
"bar": cty.EmptyObjectVal,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" "bar" {}
|
||||||
|
b "foo" "baz" {}
|
||||||
|
`,
|
||||||
|
&BlockMapSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key1", "key2"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.MapVal(map[string]cty.Value{
|
||||||
|
"foo": cty.MapVal(map[string]cty.Value{
|
||||||
|
"bar": cty.EmptyObjectVal,
|
||||||
|
"baz": cty.EmptyObjectVal,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" "bar" {}
|
||||||
|
`,
|
||||||
|
&BlockMapSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.MapValEmpty(cty.DynamicPseudoType),
|
||||||
|
1, // too many labels
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "bar" {}
|
||||||
|
`,
|
||||||
|
&BlockMapSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key1", "key2"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.MapValEmpty(cty.DynamicPseudoType),
|
||||||
|
1, // not enough labels
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" {}
|
||||||
|
b "foo" {}
|
||||||
|
`,
|
||||||
|
&BlockMapSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.MapVal(map[string]cty.Value{"foo": cty.EmptyObjectVal}),
|
||||||
|
1, // duplicate b block
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`
|
||||||
|
b "foo" "bar" {}
|
||||||
|
b "foo" "bar" {}
|
||||||
|
`,
|
||||||
|
&BlockMapSpec{
|
||||||
|
TypeName: "b",
|
||||||
|
LabelNames: []string{"key1", "key2"},
|
||||||
|
Nested: ObjectSpec{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
cty.MapVal(map[string]cty.Value{"foo": cty.MapVal(map[string]cty.Value{"bar": cty.EmptyObjectVal})}),
|
||||||
|
1, // duplicate b block
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
|
165
hcldec/spec.go
165
hcldec/spec.go
@ -1,6 +1,7 @@
|
|||||||
package hcldec
|
package hcldec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
@ -391,7 +392,7 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *
|
|||||||
var diags hcl.Diagnostics
|
var diags hcl.Diagnostics
|
||||||
|
|
||||||
if s.Nested == nil {
|
if s.Nested == nil {
|
||||||
panic("BlockSpec with no Nested Spec")
|
panic("BlockListSpec with no Nested Spec")
|
||||||
}
|
}
|
||||||
|
|
||||||
var elems []cty.Value
|
var elems []cty.Value
|
||||||
@ -470,8 +471,77 @@ func (s *BlockSetSpec) visitSameBodyChildren(cb visitFunc) {
|
|||||||
// leaf node ("Nested" does not use the same body)
|
// leaf node ("Nested" does not use the same body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blockSpec implementation
|
||||||
|
func (s *BlockSetSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
||||||
|
return []hcl.BlockHeaderSchema{
|
||||||
|
{
|
||||||
|
Type: s.TypeName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// specNeedingVariables implementation
|
||||||
|
func (s *BlockSetSpec) 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 *BlockSetSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
func (s *BlockSetSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
panic("BlockSetSpec.decode not yet implemented")
|
var diags hcl.Diagnostics
|
||||||
|
|
||||||
|
if s.Nested == nil {
|
||||||
|
panic("BlockSetSpec 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, childBlock, ctx, s.Nested, false)
|
||||||
|
diags = append(diags, childDiags...)
|
||||||
|
elems = append(elems, val)
|
||||||
|
sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, 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 {
|
||||||
|
// FIXME: We don't currently have enough info to construct a type for
|
||||||
|
// an empty list, so we'll just stub it out.
|
||||||
|
ret = cty.SetValEmpty(cty.DynamicPseudoType)
|
||||||
|
} else {
|
||||||
|
ret = cty.SetVal(elems)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||||
@ -510,8 +580,97 @@ func (s *BlockMapSpec) visitSameBodyChildren(cb visitFunc) {
|
|||||||
// leaf node ("Nested" does not use the same body)
|
// leaf node ("Nested" does not use the same body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blockSpec implementation
|
||||||
|
func (s *BlockMapSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
||||||
|
return []hcl.BlockHeaderSchema{
|
||||||
|
{
|
||||||
|
Type: s.TypeName,
|
||||||
|
LabelNames: s.LabelNames,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// specNeedingVariables implementation
|
||||||
|
func (s *BlockMapSpec) 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 *BlockMapSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
func (s *BlockMapSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||||
panic("BlockMapSpec.decode not yet implemented")
|
var diags hcl.Diagnostics
|
||||||
|
|
||||||
|
if s.Nested == nil {
|
||||||
|
panic("BlockSetSpec with no Nested Spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
elems := map[string]interface{}{}
|
||||||
|
for _, childBlock := range content.Blocks {
|
||||||
|
if childBlock.Type != s.TypeName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val, _, childDiags := decode(childBlock.Body, childBlock, ctx, s.Nested, false)
|
||||||
|
targetMap := elems
|
||||||
|
for _, key := range childBlock.Labels[:len(childBlock.LabelRanges)-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(childBlock.Labels)-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
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctyMap func(map[string]interface{}, int) cty.Value
|
||||||
|
ctyMap = func(raw map[string]interface{}, depth int) cty.Value {
|
||||||
|
if len(raw) == 0 {
|
||||||
|
// FIXME: We don't currently have enough info to construct a type for
|
||||||
|
// an empty map, so we'll just stub it out.
|
||||||
|
return cty.MapValEmpty(cty.DynamicPseudoType)
|
||||||
|
}
|
||||||
|
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] = ctyMap(v.(map[string]interface{}), depth-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cty.MapVal(vals)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctyMap(elems, len(s.LabelNames)), diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||||
|
Loading…
Reference in New Issue
Block a user