|
|
|
@ -1,6 +1,7 @@
|
|
|
|
|
package hcldec
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"github.com/hashicorp/hcl2/hcl"
|
|
|
|
@ -19,7 +20,11 @@ type Spec interface {
|
|
|
|
|
//
|
|
|
|
|
// "block" is provided only by the nested calls performed by the spec
|
|
|
|
|
// types that work on block bodies.
|
|
|
|
|
decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
|
|
|
|
|
decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
|
|
|
|
|
|
|
|
|
|
// Return the cty.Type that should be returned when decoding a body with
|
|
|
|
|
// this spec.
|
|
|
|
|
impliedType() cty.Type
|
|
|
|
|
|
|
|
|
|
// Call the given callback once for each of the nested specs that would
|
|
|
|
|
// get decoded with the same body and block as the receiver. This should
|
|
|
|
@ -30,7 +35,7 @@ type Spec interface {
|
|
|
|
|
// spec in the given content, in the context of the given block
|
|
|
|
|
// (which might be null). If the corresponding item is missing, return
|
|
|
|
|
// a place where it might be inserted.
|
|
|
|
|
sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range
|
|
|
|
|
sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type visitFunc func(spec Spec)
|
|
|
|
@ -61,24 +66,32 @@ func (s ObjectSpec) visitSameBodyChildren(cb visitFunc) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s ObjectSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
func (s ObjectSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
vals := make(map[string]cty.Value, len(s))
|
|
|
|
|
var diags hcl.Diagnostics
|
|
|
|
|
|
|
|
|
|
for k, spec := range s {
|
|
|
|
|
var kd hcl.Diagnostics
|
|
|
|
|
vals[k], kd = spec.decode(content, block, ctx)
|
|
|
|
|
vals[k], kd = spec.decode(content, blockLabels, ctx)
|
|
|
|
|
diags = append(diags, kd...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cty.ObjectVal(vals), diags
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s ObjectSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
|
|
|
|
if block != nil {
|
|
|
|
|
return block.DefRange
|
|
|
|
|
func (s ObjectSpec) impliedType() cty.Type {
|
|
|
|
|
if len(s) == 0 {
|
|
|
|
|
return cty.EmptyObject
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
attrTypes := make(map[string]cty.Type)
|
|
|
|
|
for k, childSpec := range s {
|
|
|
|
|
attrTypes[k] = childSpec.impliedType()
|
|
|
|
|
}
|
|
|
|
|
return cty.Object(attrTypes)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s ObjectSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
|
|
|
|
// This is not great, but the best we can do. In practice, it's rather
|
|
|
|
|
// strange to ask for the source range of an entire top-level body, since
|
|
|
|
|
// that's already readily available to the caller.
|
|
|
|
@ -95,24 +108,32 @@ func (s TupleSpec) visitSameBodyChildren(cb visitFunc) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s TupleSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
func (s TupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
vals := make([]cty.Value, len(s))
|
|
|
|
|
var diags hcl.Diagnostics
|
|
|
|
|
|
|
|
|
|
for i, spec := range s {
|
|
|
|
|
var ed hcl.Diagnostics
|
|
|
|
|
vals[i], ed = spec.decode(content, block, ctx)
|
|
|
|
|
vals[i], ed = spec.decode(content, blockLabels, ctx)
|
|
|
|
|
diags = append(diags, ed...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cty.TupleVal(vals), diags
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s TupleSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
|
|
|
|
if block != nil {
|
|
|
|
|
return block.DefRange
|
|
|
|
|
func (s TupleSpec) impliedType() cty.Type {
|
|
|
|
|
if len(s) == 0 {
|
|
|
|
|
return cty.EmptyTuple
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
attrTypes := make([]cty.Type, len(s))
|
|
|
|
|
for i, childSpec := range s {
|
|
|
|
|
attrTypes[i] = childSpec.impliedType()
|
|
|
|
|
}
|
|
|
|
|
return cty.Tuple(attrTypes)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s TupleSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
|
|
|
|
// This is not great, but the best we can do. In practice, it's rather
|
|
|
|
|
// strange to ask for the source range of an entire top-level body, since
|
|
|
|
|
// that's already readily available to the caller.
|
|
|
|
@ -152,7 +173,7 @@ func (s *AttrSpec) attrSchemata() []hcl.AttributeSchema {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *AttrSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
|
|
|
|
func (s *AttrSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
|
|
|
|
attr, exists := content.Attributes[s.Name]
|
|
|
|
|
if !exists {
|
|
|
|
|
return content.MissingItemRange
|
|
|
|
@ -161,7 +182,7 @@ func (s *AttrSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.R
|
|
|
|
|
return attr.Expr.Range()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *AttrSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
func (s *AttrSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
attr, exists := content.Attributes[s.Name]
|
|
|
|
|
if !exists {
|
|
|
|
|
// We don't need to check required and emit a diagnostic here, because
|
|
|
|
@ -193,6 +214,10 @@ func (s *AttrSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.E
|
|
|
|
|
return val, diags
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *AttrSpec) impliedType() cty.Type {
|
|
|
|
|
return s.Type
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A LiteralSpec is a Spec that produces the given literal value, ignoring
|
|
|
|
|
// the given body.
|
|
|
|
|
type LiteralSpec struct {
|
|
|
|
@ -203,11 +228,15 @@ func (s *LiteralSpec) visitSameBodyChildren(cb visitFunc) {
|
|
|
|
|
// leaf node
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *LiteralSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
func (s *LiteralSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
return s.Value, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *LiteralSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
|
|
|
|
func (s *LiteralSpec) impliedType() cty.Type {
|
|
|
|
|
return s.Value.Type()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *LiteralSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
|
|
|
|
// No sensible range to return for a literal, so the caller had better
|
|
|
|
|
// ensure it doesn't cause any diagnostics.
|
|
|
|
|
return hcl.Range{
|
|
|
|
@ -230,11 +259,16 @@ func (s *ExprSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
|
|
|
|
|
return s.Expr.Variables()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *ExprSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
func (s *ExprSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
return s.Expr.Value(ctx)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *ExprSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
|
|
|
|
func (s *ExprSpec) impliedType() cty.Type {
|
|
|
|
|
// We can't know the type of our expression until we evaluate it
|
|
|
|
|
return cty.DynamicPseudoType
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *ExprSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
|
|
|
|
return s.Expr.Range()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -258,7 +292,8 @@ func (s *BlockSpec) visitSameBodyChildren(cb visitFunc) {
|
|
|
|
|
func (s *BlockSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
|
|
|
|
return []hcl.BlockHeaderSchema{
|
|
|
|
|
{
|
|
|
|
|
Type: s.TypeName,
|
|
|
|
|
Type: s.TypeName,
|
|
|
|
|
LabelNames: findLabelSpecs(s.Nested),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -282,7 +317,7 @@ func (s *BlockSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
|
|
|
|
|
return Variables(childBlock.Body, s.Nested)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
func (s *BlockSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
var diags hcl.Diagnostics
|
|
|
|
|
|
|
|
|
|
var childBlock *hcl.Block
|
|
|
|
@ -318,18 +353,22 @@ func (s *BlockSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.
|
|
|
|
|
Subject: &content.MissingItemRange,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return cty.NullVal(cty.DynamicPseudoType), diags
|
|
|
|
|
return cty.NullVal(s.Nested.impliedType()), diags
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if s.Nested == nil {
|
|
|
|
|
panic("BlockSpec with no Nested Spec")
|
|
|
|
|
}
|
|
|
|
|
val, _, childDiags := decode(childBlock.Body, childBlock, ctx, s.Nested, false)
|
|
|
|
|
val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
|
|
|
|
|
diags = append(diags, childDiags...)
|
|
|
|
|
return val, diags
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
|
|
|
|
func (s *BlockSpec) impliedType() cty.Type {
|
|
|
|
|
return s.Nested.impliedType()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
|
|
|
|
var childBlock *hcl.Block
|
|
|
|
|
for _, candidate := range content.Blocks {
|
|
|
|
|
if candidate.Type != s.TypeName {
|
|
|
|
@ -344,7 +383,7 @@ func (s *BlockSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.
|
|
|
|
|
return content.MissingItemRange
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sourceRange(childBlock.Body, childBlock, s.Nested)
|
|
|
|
|
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A BlockListSpec is a Spec that produces a cty list of the results of
|
|
|
|
@ -364,10 +403,8 @@ func (s *BlockListSpec) visitSameBodyChildren(cb visitFunc) {
|
|
|
|
|
func (s *BlockListSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
|
|
|
|
return []hcl.BlockHeaderSchema{
|
|
|
|
|
{
|
|
|
|
|
Type: s.TypeName,
|
|
|
|
|
// FIXME: Need to peek into s.Nested to see if it has any
|
|
|
|
|
// BlockLabelSpec instances, which will define then how many
|
|
|
|
|
// labels we need.
|
|
|
|
|
Type: s.TypeName,
|
|
|
|
|
LabelNames: findLabelSpecs(s.Nested),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -387,11 +424,11 @@ func (s *BlockListSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversa
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
var diags hcl.Diagnostics
|
|
|
|
|
|
|
|
|
|
if s.Nested == nil {
|
|
|
|
|
panic("BlockSpec with no Nested Spec")
|
|
|
|
|
panic("BlockListSpec with no Nested Spec")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var elems []cty.Value
|
|
|
|
@ -401,24 +438,24 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val, _, childDiags := decode(childBlock.Body, childBlock, ctx, s.Nested, false)
|
|
|
|
|
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, childBlock, s.Nested))
|
|
|
|
|
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 blocks are required", s.MinItems),
|
|
|
|
|
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 blocks are allowed", s.MaxItems),
|
|
|
|
|
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],
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
@ -426,9 +463,7 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *
|
|
|
|
|
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.ListValEmpty(cty.DynamicPseudoType)
|
|
|
|
|
ret = cty.ListValEmpty(s.Nested.impliedType())
|
|
|
|
|
} else {
|
|
|
|
|
ret = cty.ListVal(elems)
|
|
|
|
|
}
|
|
|
|
@ -436,7 +471,11 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *
|
|
|
|
|
return ret, diags
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
|
|
|
|
func (s *BlockListSpec) impliedType() cty.Type {
|
|
|
|
|
return cty.List(s.Nested.impliedType())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockListSpec) 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.
|
|
|
|
|
|
|
|
|
@ -454,7 +493,7 @@ func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block)
|
|
|
|
|
return content.MissingItemRange
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sourceRange(childBlock.Body, childBlock, s.Nested)
|
|
|
|
|
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A BlockSetSpec is a Spec that produces a cty set of the results of
|
|
|
|
@ -470,11 +509,83 @@ func (s *BlockSetSpec) visitSameBodyChildren(cb visitFunc) {
|
|
|
|
|
// leaf node ("Nested" does not use the same body)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockSetSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
panic("BlockSetSpec.decode not yet implemented")
|
|
|
|
|
// blockSpec implementation
|
|
|
|
|
func (s *BlockSetSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
|
|
|
|
return []hcl.BlockHeaderSchema{
|
|
|
|
|
{
|
|
|
|
|
Type: s.TypeName,
|
|
|
|
|
LabelNames: findLabelSpecs(s.Nested),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
|
|
|
|
// 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, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
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, 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.SetValEmpty(s.Nested.impliedType())
|
|
|
|
|
} else {
|
|
|
|
|
ret = cty.SetVal(elems)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret, diags
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockSetSpec) impliedType() cty.Type {
|
|
|
|
|
return cty.Set(s.Nested.impliedType())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockSetSpec) 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.
|
|
|
|
|
|
|
|
|
@ -492,7 +603,7 @@ func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) h
|
|
|
|
|
return content.MissingItemRange
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sourceRange(childBlock.Body, childBlock, s.Nested)
|
|
|
|
|
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A BlockMapSpec is a Spec that produces a cty map of the results of
|
|
|
|
@ -510,11 +621,108 @@ func (s *BlockMapSpec) visitSameBodyChildren(cb visitFunc) {
|
|
|
|
|
// leaf node ("Nested" does not use the same body)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockMapSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
panic("BlockMapSpec.decode not yet implemented")
|
|
|
|
|
// blockSpec implementation
|
|
|
|
|
func (s *BlockMapSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
|
|
|
|
return []hcl.BlockHeaderSchema{
|
|
|
|
|
{
|
|
|
|
|
Type: s.TypeName,
|
|
|
|
|
LabelNames: append(s.LabelNames, findLabelSpecs(s.Nested)...),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
|
|
|
|
// 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, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.MapValEmpty(s.Nested.impliedType()), diags
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ctyMap func(map[string]interface{}, int) cty.Value
|
|
|
|
|
ctyMap = 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] = ctyMap(v.(map[string]interface{}), depth-1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return cty.MapVal(vals)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ctyMap(elems, len(s.LabelNames)), diags
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockMapSpec) impliedType() cty.Type {
|
|
|
|
|
ret := s.Nested.impliedType()
|
|
|
|
|
for _ = range s.LabelNames {
|
|
|
|
|
ret = cty.Map(ret)
|
|
|
|
|
}
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockMapSpec) 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.
|
|
|
|
|
|
|
|
|
@ -532,7 +740,7 @@ func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) h
|
|
|
|
|
return content.MissingItemRange
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sourceRange(childBlock.Body, childBlock, s.Nested)
|
|
|
|
|
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A BlockLabelSpec is a Spec that returns a cty.String representing the
|
|
|
|
@ -555,14 +763,97 @@ func (s *BlockLabelSpec) visitSameBodyChildren(cb visitFunc) {
|
|
|
|
|
// leaf node
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockLabelSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
panic("BlockLabelSpec.decode not yet implemented")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockLabelSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
|
|
|
|
if block == nil {
|
|
|
|
|
func (s *BlockLabelSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
if s.Index >= len(blockLabels) {
|
|
|
|
|
panic("BlockListSpec used in non-block context")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return block.LabelRanges[s.Index]
|
|
|
|
|
return cty.StringVal(blockLabels[s.Index].Value), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockLabelSpec) impliedType() cty.Type {
|
|
|
|
|
return cty.String // labels are always strings
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *BlockLabelSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
|
|
|
|
if s.Index >= len(blockLabels) {
|
|
|
|
|
panic("BlockListSpec used in non-block context")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return blockLabels[s.Index].Range
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func findLabelSpecs(spec Spec) []string {
|
|
|
|
|
maxIdx := -1
|
|
|
|
|
var names map[int]string
|
|
|
|
|
|
|
|
|
|
var visit visitFunc
|
|
|
|
|
visit = func(s Spec) {
|
|
|
|
|
if ls, ok := s.(*BlockLabelSpec); ok {
|
|
|
|
|
if maxIdx < ls.Index {
|
|
|
|
|
maxIdx = ls.Index
|
|
|
|
|
}
|
|
|
|
|
if names == nil {
|
|
|
|
|
names = make(map[int]string)
|
|
|
|
|
}
|
|
|
|
|
names[ls.Index] = ls.Name
|
|
|
|
|
}
|
|
|
|
|
s.visitSameBodyChildren(visit)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
visit(spec)
|
|
|
|
|
|
|
|
|
|
if maxIdx < 0 {
|
|
|
|
|
return nil // no labels at all
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret := make([]string, maxIdx+1)
|
|
|
|
|
for i := range ret {
|
|
|
|
|
name := names[i]
|
|
|
|
|
if name == "" {
|
|
|
|
|
// Should never happen if the spec is conformant, since we require
|
|
|
|
|
// consecutive indices starting at zero.
|
|
|
|
|
name = fmt.Sprintf("missing%02d", i)
|
|
|
|
|
}
|
|
|
|
|
ret[i] = name
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DefaultSpec is a spec that wraps two specs, evaluating the primary first
|
|
|
|
|
// and then evaluating the default if the primary returns a null value.
|
|
|
|
|
//
|
|
|
|
|
// The two specifications must have the same implied result type for correct
|
|
|
|
|
// operation. If not, the result is undefined.
|
|
|
|
|
type DefaultSpec struct {
|
|
|
|
|
Primary Spec
|
|
|
|
|
Default Spec
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *DefaultSpec) visitSameBodyChildren(cb visitFunc) {
|
|
|
|
|
cb(s.Primary)
|
|
|
|
|
cb(s.Default)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *DefaultSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
|
|
|
val, diags := s.Primary.decode(content, blockLabels, ctx)
|
|
|
|
|
if val.IsNull() {
|
|
|
|
|
var moreDiags hcl.Diagnostics
|
|
|
|
|
val, moreDiags = s.Default.decode(content, blockLabels, ctx)
|
|
|
|
|
diags = append(diags, moreDiags...)
|
|
|
|
|
}
|
|
|
|
|
return val, diags
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *DefaultSpec) impliedType() cty.Type {
|
|
|
|
|
return s.Primary.impliedType()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *DefaultSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
|
|
|
|
// We can't tell from here which of the two specs will ultimately be used
|
|
|
|
|
// in our result, so we'll just assume the first. This is usually the right
|
|
|
|
|
// choice because the default is often a literal spec that doesn't have a
|
|
|
|
|
// reasonable source range to return anyway.
|
|
|
|
|
return s.Primary.sourceRange(content, blockLabels)
|
|
|
|
|
}
|
|
|
|
|