8 "github.com/hashicorp/hcl2/hcl"
9 "github.com/zclconf/go-cty/cty"
10 "github.com/zclconf/go-cty/cty/convert"
11 "github.com/zclconf/go-cty/cty/function"
14 // A Spec is a description of how to decode a hcl.Body to a cty.Value.
16 // The various other types in this package whose names end in "Spec" are
17 // the spec implementations. The most common top-level spec is ObjectSpec,
18 // which decodes body content into a cty.Value of an object type.
20 // Perform the decode operation on the given body, in the context of
21 // the given block (which might be null), using the given eval context.
23 // "block" is provided only by the nested calls performed by the spec
24 // types that work on block bodies.
25 decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
27 // Return the cty.Type that should be returned when decoding a body with
29 impliedType() cty.Type
31 // Call the given callback once for each of the nested specs that would
32 // get decoded with the same body and block as the receiver. This should
33 // not descend into the nested specs used when decoding blocks.
34 visitSameBodyChildren(cb visitFunc)
36 // Determine the source range of the value that would be returned for the
37 // spec in the given content, in the context of the given block
38 // (which might be null). If the corresponding item is missing, return
39 // a place where it might be inserted.
40 sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range
43 type visitFunc func(spec Spec)
45 // An ObjectSpec is a Spec that produces a cty.Value of an object type whose
46 // attributes correspond to the keys of the spec map.
47 type ObjectSpec map[string]Spec
49 // attrSpec is implemented by specs that require attributes from the body.
50 type attrSpec interface {
51 attrSchemata() []hcl.AttributeSchema
54 // blockSpec is implemented by specs that require blocks from the body.
55 type blockSpec interface {
56 blockHeaderSchemata() []hcl.BlockHeaderSchema
60 // specNeedingVariables is implemented by specs that can use variables
61 // from the EvalContext, to declare which variables they need.
62 type specNeedingVariables interface {
63 variablesNeeded(content *hcl.BodyContent) []hcl.Traversal
66 func (s ObjectSpec) visitSameBodyChildren(cb visitFunc) {
72 func (s ObjectSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
73 vals := make(map[string]cty.Value, len(s))
74 var diags hcl.Diagnostics
76 for k, spec := range s {
77 var kd hcl.Diagnostics
78 vals[k], kd = spec.decode(content, blockLabels, ctx)
79 diags = append(diags, kd...)
82 return cty.ObjectVal(vals), diags
85 func (s ObjectSpec) impliedType() cty.Type {
87 return cty.EmptyObject
90 attrTypes := make(map[string]cty.Type)
91 for k, childSpec := range s {
92 attrTypes[k] = childSpec.impliedType()
94 return cty.Object(attrTypes)
97 func (s ObjectSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
98 // This is not great, but the best we can do. In practice, it's rather
99 // strange to ask for the source range of an entire top-level body, since
100 // that's already readily available to the caller.
101 return content.MissingItemRange
104 // A TupleSpec is a Spec that produces a cty.Value of a tuple type whose
105 // elements correspond to the elements of the spec slice.
106 type TupleSpec []Spec
108 func (s TupleSpec) visitSameBodyChildren(cb visitFunc) {
109 for _, c := range s {
114 func (s TupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
115 vals := make([]cty.Value, len(s))
116 var diags hcl.Diagnostics
118 for i, spec := range s {
119 var ed hcl.Diagnostics
120 vals[i], ed = spec.decode(content, blockLabels, ctx)
121 diags = append(diags, ed...)
124 return cty.TupleVal(vals), diags
127 func (s TupleSpec) impliedType() cty.Type {
129 return cty.EmptyTuple
132 attrTypes := make([]cty.Type, len(s))
133 for i, childSpec := range s {
134 attrTypes[i] = childSpec.impliedType()
136 return cty.Tuple(attrTypes)
139 func (s TupleSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
140 // This is not great, but the best we can do. In practice, it's rather
141 // strange to ask for the source range of an entire top-level body, since
142 // that's already readily available to the caller.
143 return content.MissingItemRange
146 // An AttrSpec is a Spec that evaluates a particular attribute expression in
147 // the body and returns its resulting value converted to the requested type,
148 // or produces a diagnostic if the type is incorrect.
149 type AttrSpec struct {
155 func (s *AttrSpec) visitSameBodyChildren(cb visitFunc) {
159 // specNeedingVariables implementation
160 func (s *AttrSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
161 attr, exists := content.Attributes[s.Name]
166 return attr.Expr.Variables()
169 // attrSpec implementation
170 func (s *AttrSpec) attrSchemata() []hcl.AttributeSchema {
171 return []hcl.AttributeSchema{
174 Required: s.Required,
179 func (s *AttrSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
180 attr, exists := content.Attributes[s.Name]
182 return content.MissingItemRange
185 return attr.Expr.Range()
188 func (s *AttrSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
189 attr, exists := content.Attributes[s.Name]
191 // We don't need to check required and emit a diagnostic here, because
192 // that would already have happened when building "content".
193 return cty.NullVal(s.Type), nil
196 val, diags := attr.Expr.Value(ctx)
198 convVal, err := convert.Convert(val, s.Type)
200 diags = append(diags, &hcl.Diagnostic{
201 Severity: hcl.DiagError,
202 Summary: "Incorrect attribute value type",
204 "Inappropriate value for attribute %q: %s.",
207 Subject: attr.Expr.StartRange().Ptr(),
208 Context: hcl.RangeBetween(attr.NameRange, attr.Expr.StartRange()).Ptr(),
210 // We'll return an unknown value of the _correct_ type so that the
211 // incomplete result can still be used for some analysis use-cases.
212 val = cty.UnknownVal(s.Type)
220 func (s *AttrSpec) impliedType() cty.Type {
224 // A LiteralSpec is a Spec that produces the given literal value, ignoring
226 type LiteralSpec struct {
230 func (s *LiteralSpec) visitSameBodyChildren(cb visitFunc) {
234 func (s *LiteralSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
238 func (s *LiteralSpec) impliedType() cty.Type {
239 return s.Value.Type()
242 func (s *LiteralSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
243 // No sensible range to return for a literal, so the caller had better
244 // ensure it doesn't cause any diagnostics.
246 Filename: "<unknown>",
250 // An ExprSpec is a Spec that evaluates the given expression, ignoring the
252 type ExprSpec struct {
256 func (s *ExprSpec) visitSameBodyChildren(cb visitFunc) {
260 // specNeedingVariables implementation
261 func (s *ExprSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
262 return s.Expr.Variables()
265 func (s *ExprSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
266 return s.Expr.Value(ctx)
269 func (s *ExprSpec) impliedType() cty.Type {
270 // We can't know the type of our expression until we evaluate it
271 return cty.DynamicPseudoType
274 func (s *ExprSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
275 return s.Expr.Range()
278 // A BlockSpec is a Spec that produces a cty.Value by decoding the contents
279 // of a single nested block of a given type, using a nested spec.
281 // If the Required flag is not set, the nested block may be omitted, in which
282 // case a null value is produced. If it _is_ set, an error diagnostic is
283 // produced if there are no nested blocks of the given type.
284 type BlockSpec struct {
290 func (s *BlockSpec) visitSameBodyChildren(cb visitFunc) {
291 // leaf node ("Nested" does not use the same body)
294 // blockSpec implementation
295 func (s *BlockSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
296 return []hcl.BlockHeaderSchema{
299 LabelNames: findLabelSpecs(s.Nested),
304 // blockSpec implementation
305 func (s *BlockSpec) nestedSpec() Spec {
309 // specNeedingVariables implementation
310 func (s *BlockSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
311 var childBlock *hcl.Block
312 for _, candidate := range content.Blocks {
313 if candidate.Type != s.TypeName {
317 childBlock = candidate
321 if childBlock == nil {
325 return Variables(childBlock.Body, s.Nested)
328 func (s *BlockSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
329 var diags hcl.Diagnostics
331 var childBlock *hcl.Block
332 for _, candidate := range content.Blocks {
333 if candidate.Type != s.TypeName {
337 if childBlock != nil {
338 diags = append(diags, &hcl.Diagnostic{
339 Severity: hcl.DiagError,
340 Summary: fmt.Sprintf("Duplicate %s block", s.TypeName),
342 "Only one block of type %q is allowed. Previous definition was at %s.",
343 s.TypeName, childBlock.DefRange.String(),
345 Subject: &candidate.DefRange,
350 childBlock = candidate
353 if childBlock == nil {
355 diags = append(diags, &hcl.Diagnostic{
356 Severity: hcl.DiagError,
357 Summary: fmt.Sprintf("Missing %s block", s.TypeName),
359 "A block of type %q is required here.", s.TypeName,
361 Subject: &content.MissingItemRange,
364 return cty.NullVal(s.Nested.impliedType()), diags
368 panic("BlockSpec with no Nested Spec")
370 val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
371 diags = append(diags, childDiags...)
375 func (s *BlockSpec) impliedType() cty.Type {
376 return s.Nested.impliedType()
379 func (s *BlockSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
380 var childBlock *hcl.Block
381 for _, candidate := range content.Blocks {
382 if candidate.Type != s.TypeName {
386 childBlock = candidate
390 if childBlock == nil {
391 return content.MissingItemRange
394 return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
397 // A BlockListSpec is a Spec that produces a cty list of the results of
398 // decoding all of the nested blocks of a given type, using a nested spec.
399 type BlockListSpec struct {
406 func (s *BlockListSpec) visitSameBodyChildren(cb visitFunc) {
407 // leaf node ("Nested" does not use the same body)
410 // blockSpec implementation
411 func (s *BlockListSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
412 return []hcl.BlockHeaderSchema{
415 LabelNames: findLabelSpecs(s.Nested),
420 // blockSpec implementation
421 func (s *BlockListSpec) nestedSpec() Spec {
425 // specNeedingVariables implementation
426 func (s *BlockListSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
427 var ret []hcl.Traversal
429 for _, childBlock := range content.Blocks {
430 if childBlock.Type != s.TypeName {
434 ret = append(ret, Variables(childBlock.Body, s.Nested)...)
440 func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
441 var diags hcl.Diagnostics
444 panic("BlockListSpec with no Nested Spec")
447 var elems []cty.Value
448 var sourceRanges []hcl.Range
449 for _, childBlock := range content.Blocks {
450 if childBlock.Type != s.TypeName {
454 val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
455 diags = append(diags, childDiags...)
456 elems = append(elems, val)
457 sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested))
460 if len(elems) < s.MinItems {
461 diags = append(diags, &hcl.Diagnostic{
462 Severity: hcl.DiagError,
463 Summary: fmt.Sprintf("Insufficient %s blocks", s.TypeName),
464 Detail: fmt.Sprintf("At least %d %q blocks are required.", s.MinItems, s.TypeName),
465 Subject: &content.MissingItemRange,
467 } else if s.MaxItems > 0 && len(elems) > s.MaxItems {
468 diags = append(diags, &hcl.Diagnostic{
469 Severity: hcl.DiagError,
470 Summary: fmt.Sprintf("Too many %s blocks", s.TypeName),
471 Detail: fmt.Sprintf("No more than %d %q blocks are allowed", s.MaxItems, s.TypeName),
472 Subject: &sourceRanges[s.MaxItems],
479 ret = cty.ListValEmpty(s.Nested.impliedType())
481 // Since our target is a list, all of the decoded elements must have the
482 // same type or cty.ListVal will panic below. Different types can arise
483 // if there is an attribute spec of type cty.DynamicPseudoType in the
484 // nested spec; all given values must be convertable to a single type
485 // in order for the result to be considered valid.
486 etys := make([]cty.Type, len(elems))
487 for i, v := range elems {
490 ety, convs := convert.UnifyUnsafe(etys)
491 if ety == cty.NilType {
492 // FIXME: This is a pretty terrible error message.
493 diags = append(diags, &hcl.Diagnostic{
494 Severity: hcl.DiagError,
495 Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
496 Detail: "Corresponding attributes in all blocks of this type must be the same.",
497 Subject: &sourceRanges[0],
499 return cty.DynamicVal, diags
501 for i, v := range elems {
503 newV, err := convs[i](v)
505 // FIXME: This is a pretty terrible error message.
506 diags = append(diags, &hcl.Diagnostic{
507 Severity: hcl.DiagError,
508 Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
509 Detail: fmt.Sprintf("Block with index %d has inconsistent argument types: %s.", i, err),
510 Subject: &sourceRanges[i],
512 // Bail early here so we won't panic below in cty.ListVal
513 return cty.DynamicVal, diags
519 ret = cty.ListVal(elems)
525 func (s *BlockListSpec) impliedType() cty.Type {
526 return cty.List(s.Nested.impliedType())
529 func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
530 // We return the source range of the _first_ block of the given type,
531 // since they are not guaranteed to form a contiguous range.
533 var childBlock *hcl.Block
534 for _, candidate := range content.Blocks {
535 if candidate.Type != s.TypeName {
539 childBlock = candidate
543 if childBlock == nil {
544 return content.MissingItemRange
547 return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
550 // A BlockTupleSpec is a Spec that produces a cty tuple of the results of
551 // decoding all of the nested blocks of a given type, using a nested spec.
553 // This is similar to BlockListSpec, but it permits the nested blocks to have
554 // different result types in situations where cty.DynamicPseudoType attributes
556 type BlockTupleSpec struct {
563 func (s *BlockTupleSpec) visitSameBodyChildren(cb visitFunc) {
564 // leaf node ("Nested" does not use the same body)
567 // blockSpec implementation
568 func (s *BlockTupleSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
569 return []hcl.BlockHeaderSchema{
572 LabelNames: findLabelSpecs(s.Nested),
577 // blockSpec implementation
578 func (s *BlockTupleSpec) nestedSpec() Spec {
582 // specNeedingVariables implementation
583 func (s *BlockTupleSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
584 var ret []hcl.Traversal
586 for _, childBlock := range content.Blocks {
587 if childBlock.Type != s.TypeName {
591 ret = append(ret, Variables(childBlock.Body, s.Nested)...)
597 func (s *BlockTupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
598 var diags hcl.Diagnostics
601 panic("BlockListSpec with no Nested Spec")
604 var elems []cty.Value
605 var sourceRanges []hcl.Range
606 for _, childBlock := range content.Blocks {
607 if childBlock.Type != s.TypeName {
611 val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
612 diags = append(diags, childDiags...)
613 elems = append(elems, val)
614 sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested))
617 if len(elems) < s.MinItems {
618 diags = append(diags, &hcl.Diagnostic{
619 Severity: hcl.DiagError,
620 Summary: fmt.Sprintf("Insufficient %s blocks", s.TypeName),
621 Detail: fmt.Sprintf("At least %d %q blocks are required.", s.MinItems, s.TypeName),
622 Subject: &content.MissingItemRange,
624 } else if s.MaxItems > 0 && len(elems) > s.MaxItems {
625 diags = append(diags, &hcl.Diagnostic{
626 Severity: hcl.DiagError,
627 Summary: fmt.Sprintf("Too many %s blocks", s.TypeName),
628 Detail: fmt.Sprintf("No more than %d %q blocks are allowed", s.MaxItems, s.TypeName),
629 Subject: &sourceRanges[s.MaxItems],
636 ret = cty.EmptyTupleVal
638 ret = cty.TupleVal(elems)
644 func (s *BlockTupleSpec) impliedType() cty.Type {
645 // We can't predict our type, because we don't know how many blocks
646 // there will be until we decode.
647 return cty.DynamicPseudoType
650 func (s *BlockTupleSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
651 // We return the source range of the _first_ block of the given type,
652 // since they are not guaranteed to form a contiguous range.
654 var childBlock *hcl.Block
655 for _, candidate := range content.Blocks {
656 if candidate.Type != s.TypeName {
660 childBlock = candidate
664 if childBlock == nil {
665 return content.MissingItemRange
668 return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
671 // A BlockSetSpec is a Spec that produces a cty set of the results of
672 // decoding all of the nested blocks of a given type, using a nested spec.
673 type BlockSetSpec struct {
680 func (s *BlockSetSpec) visitSameBodyChildren(cb visitFunc) {
681 // leaf node ("Nested" does not use the same body)
684 // blockSpec implementation
685 func (s *BlockSetSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
686 return []hcl.BlockHeaderSchema{
689 LabelNames: findLabelSpecs(s.Nested),
694 // blockSpec implementation
695 func (s *BlockSetSpec) nestedSpec() Spec {
699 // specNeedingVariables implementation
700 func (s *BlockSetSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
701 var ret []hcl.Traversal
703 for _, childBlock := range content.Blocks {
704 if childBlock.Type != s.TypeName {
708 ret = append(ret, Variables(childBlock.Body, s.Nested)...)
714 func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
715 var diags hcl.Diagnostics
718 panic("BlockSetSpec with no Nested Spec")
721 var elems []cty.Value
722 var sourceRanges []hcl.Range
723 for _, childBlock := range content.Blocks {
724 if childBlock.Type != s.TypeName {
728 val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
729 diags = append(diags, childDiags...)
730 elems = append(elems, val)
731 sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested))
734 if len(elems) < s.MinItems {
735 diags = append(diags, &hcl.Diagnostic{
736 Severity: hcl.DiagError,
737 Summary: fmt.Sprintf("Insufficient %s blocks", s.TypeName),
738 Detail: fmt.Sprintf("At least %d %q blocks are required.", s.MinItems, s.TypeName),
739 Subject: &content.MissingItemRange,
741 } else if s.MaxItems > 0 && len(elems) > s.MaxItems {
742 diags = append(diags, &hcl.Diagnostic{
743 Severity: hcl.DiagError,
744 Summary: fmt.Sprintf("Too many %s blocks", s.TypeName),
745 Detail: fmt.Sprintf("No more than %d %q blocks are allowed", s.MaxItems, s.TypeName),
746 Subject: &sourceRanges[s.MaxItems],
753 ret = cty.SetValEmpty(s.Nested.impliedType())
755 // Since our target is a set, all of the decoded elements must have the
756 // same type or cty.SetVal will panic below. Different types can arise
757 // if there is an attribute spec of type cty.DynamicPseudoType in the
758 // nested spec; all given values must be convertable to a single type
759 // in order for the result to be considered valid.
760 etys := make([]cty.Type, len(elems))
761 for i, v := range elems {
764 ety, convs := convert.UnifyUnsafe(etys)
765 if ety == cty.NilType {
766 // FIXME: This is a pretty terrible error message.
767 diags = append(diags, &hcl.Diagnostic{
768 Severity: hcl.DiagError,
769 Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
770 Detail: "Corresponding attributes in all blocks of this type must be the same.",
771 Subject: &sourceRanges[0],
773 return cty.DynamicVal, diags
775 for i, v := range elems {
777 newV, err := convs[i](v)
779 // FIXME: This is a pretty terrible error message.
780 diags = append(diags, &hcl.Diagnostic{
781 Severity: hcl.DiagError,
782 Summary: fmt.Sprintf("Unconsistent argument types in %s blocks", s.TypeName),
783 Detail: fmt.Sprintf("Block with index %d has inconsistent argument types: %s.", i, err),
784 Subject: &sourceRanges[i],
786 // Bail early here so we won't panic below in cty.ListVal
787 return cty.DynamicVal, diags
793 ret = cty.SetVal(elems)
799 func (s *BlockSetSpec) impliedType() cty.Type {
800 return cty.Set(s.Nested.impliedType())
803 func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
804 // We return the source range of the _first_ block of the given type,
805 // since they are not guaranteed to form a contiguous range.
807 var childBlock *hcl.Block
808 for _, candidate := range content.Blocks {
809 if candidate.Type != s.TypeName {
813 childBlock = candidate
817 if childBlock == nil {
818 return content.MissingItemRange
821 return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
824 // A BlockMapSpec is a Spec that produces a cty map of the results of
825 // decoding all of the nested blocks of a given type, using a nested spec.
827 // One level of map structure is created for each of the given label names.
828 // There must be at least one given label name.
829 type BlockMapSpec struct {
835 func (s *BlockMapSpec) visitSameBodyChildren(cb visitFunc) {
836 // leaf node ("Nested" does not use the same body)
839 // blockSpec implementation
840 func (s *BlockMapSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
841 return []hcl.BlockHeaderSchema{
844 LabelNames: append(s.LabelNames, findLabelSpecs(s.Nested)...),
849 // blockSpec implementation
850 func (s *BlockMapSpec) nestedSpec() Spec {
854 // specNeedingVariables implementation
855 func (s *BlockMapSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
856 var ret []hcl.Traversal
858 for _, childBlock := range content.Blocks {
859 if childBlock.Type != s.TypeName {
863 ret = append(ret, Variables(childBlock.Body, s.Nested)...)
869 func (s *BlockMapSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
870 var diags hcl.Diagnostics
873 panic("BlockMapSpec with no Nested Spec")
875 if ImpliedType(s).HasDynamicTypes() {
876 panic("cty.DynamicPseudoType attributes may not be used inside a BlockMapSpec")
879 elems := map[string]interface{}{}
880 for _, childBlock := range content.Blocks {
881 if childBlock.Type != s.TypeName {
885 childLabels := labelsForBlock(childBlock)
886 val, _, childDiags := decode(childBlock.Body, childLabels[len(s.LabelNames):], ctx, s.Nested, false)
888 for _, key := range childBlock.Labels[:len(s.LabelNames)-1] {
889 if _, exists := targetMap[key]; !exists {
890 targetMap[key] = make(map[string]interface{})
892 targetMap = targetMap[key].(map[string]interface{})
895 diags = append(diags, childDiags...)
897 key := childBlock.Labels[len(s.LabelNames)-1]
898 if _, exists := targetMap[key]; exists {
899 labelsBuf := bytes.Buffer{}
900 for _, label := range childBlock.Labels {
901 fmt.Fprintf(&labelsBuf, " %q", label)
903 diags = append(diags, &hcl.Diagnostic{
904 Severity: hcl.DiagError,
905 Summary: fmt.Sprintf("Duplicate %s block", s.TypeName),
907 "A block for %s%s was already defined. The %s labels must be unique.",
908 s.TypeName, labelsBuf.String(), s.TypeName,
910 Subject: &childBlock.DefRange,
919 return cty.MapValEmpty(s.Nested.impliedType()), diags
922 var ctyMap func(map[string]interface{}, int) cty.Value
923 ctyMap = func(raw map[string]interface{}, depth int) cty.Value {
924 vals := make(map[string]cty.Value, len(raw))
926 for k, v := range raw {
927 vals[k] = v.(cty.Value)
930 for k, v := range raw {
931 vals[k] = ctyMap(v.(map[string]interface{}), depth-1)
934 return cty.MapVal(vals)
937 return ctyMap(elems, len(s.LabelNames)), diags
940 func (s *BlockMapSpec) impliedType() cty.Type {
941 ret := s.Nested.impliedType()
942 for _ = range s.LabelNames {
948 func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
949 // We return the source range of the _first_ block of the given type,
950 // since they are not guaranteed to form a contiguous range.
952 var childBlock *hcl.Block
953 for _, candidate := range content.Blocks {
954 if candidate.Type != s.TypeName {
958 childBlock = candidate
962 if childBlock == nil {
963 return content.MissingItemRange
966 return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
969 // A BlockObjectSpec is a Spec that produces a cty object of the results of
970 // decoding all of the nested blocks of a given type, using a nested spec.
972 // One level of object structure is created for each of the given label names.
973 // There must be at least one given label name.
975 // This is similar to BlockMapSpec, but it permits the nested blocks to have
976 // different result types in situations where cty.DynamicPseudoType attributes
978 type BlockObjectSpec struct {
984 func (s *BlockObjectSpec) visitSameBodyChildren(cb visitFunc) {
985 // leaf node ("Nested" does not use the same body)
988 // blockSpec implementation
989 func (s *BlockObjectSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
990 return []hcl.BlockHeaderSchema{
993 LabelNames: append(s.LabelNames, findLabelSpecs(s.Nested)...),
998 // blockSpec implementation
999 func (s *BlockObjectSpec) nestedSpec() Spec {
1003 // specNeedingVariables implementation
1004 func (s *BlockObjectSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
1005 var ret []hcl.Traversal
1007 for _, childBlock := range content.Blocks {
1008 if childBlock.Type != s.TypeName {
1012 ret = append(ret, Variables(childBlock.Body, s.Nested)...)
1018 func (s *BlockObjectSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
1019 var diags hcl.Diagnostics
1021 if s.Nested == nil {
1022 panic("BlockObjectSpec with no Nested Spec")
1025 elems := map[string]interface{}{}
1026 for _, childBlock := range content.Blocks {
1027 if childBlock.Type != s.TypeName {
1031 childLabels := labelsForBlock(childBlock)
1032 val, _, childDiags := decode(childBlock.Body, childLabels[len(s.LabelNames):], ctx, s.Nested, false)
1034 for _, key := range childBlock.Labels[:len(s.LabelNames)-1] {
1035 if _, exists := targetMap[key]; !exists {
1036 targetMap[key] = make(map[string]interface{})
1038 targetMap = targetMap[key].(map[string]interface{})
1041 diags = append(diags, childDiags...)
1043 key := childBlock.Labels[len(s.LabelNames)-1]
1044 if _, exists := targetMap[key]; exists {
1045 labelsBuf := bytes.Buffer{}
1046 for _, label := range childBlock.Labels {
1047 fmt.Fprintf(&labelsBuf, " %q", label)
1049 diags = append(diags, &hcl.Diagnostic{
1050 Severity: hcl.DiagError,
1051 Summary: fmt.Sprintf("Duplicate %s block", s.TypeName),
1052 Detail: fmt.Sprintf(
1053 "A block for %s%s was already defined. The %s labels must be unique.",
1054 s.TypeName, labelsBuf.String(), s.TypeName,
1056 Subject: &childBlock.DefRange,
1061 targetMap[key] = val
1064 if len(elems) == 0 {
1065 return cty.EmptyObjectVal, diags
1068 var ctyObj func(map[string]interface{}, int) cty.Value
1069 ctyObj = func(raw map[string]interface{}, depth int) cty.Value {
1070 vals := make(map[string]cty.Value, len(raw))
1072 for k, v := range raw {
1073 vals[k] = v.(cty.Value)
1076 for k, v := range raw {
1077 vals[k] = ctyObj(v.(map[string]interface{}), depth-1)
1080 return cty.ObjectVal(vals)
1083 return ctyObj(elems, len(s.LabelNames)), diags
1086 func (s *BlockObjectSpec) impliedType() cty.Type {
1087 // We can't predict our type, since we don't know how many blocks are
1088 // present and what labels they have until we decode.
1089 return cty.DynamicPseudoType
1092 func (s *BlockObjectSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
1093 // We return the source range of the _first_ block of the given type,
1094 // since they are not guaranteed to form a contiguous range.
1096 var childBlock *hcl.Block
1097 for _, candidate := range content.Blocks {
1098 if candidate.Type != s.TypeName {
1102 childBlock = candidate
1106 if childBlock == nil {
1107 return content.MissingItemRange
1110 return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
1113 // A BlockAttrsSpec is a Spec that interprets a single block as if it were
1114 // a map of some element type. That is, each attribute within the block
1115 // becomes a key in the resulting map and the attribute's value becomes the
1116 // element value, after conversion to the given element type. The resulting
1117 // value is a cty.Map of the given element type.
1119 // This spec imposes a validation constraint that there be exactly one block
1120 // of the given type name and that this block may contain only attributes. The
1121 // block does not accept any labels.
1123 // This is an alternative to an AttrSpec of a map type for situations where
1124 // block syntax is desired. Note that block syntax does not permit dynamic
1125 // keys, construction of the result via a "for" expression, etc. In most cases
1126 // an AttrSpec is preferred if the desired result is a map whose keys are
1127 // chosen by the user rather than by schema.
1128 type BlockAttrsSpec struct {
1130 ElementType cty.Type
1134 func (s *BlockAttrsSpec) visitSameBodyChildren(cb visitFunc) {
1138 // blockSpec implementation
1139 func (s *BlockAttrsSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
1140 return []hcl.BlockHeaderSchema{
1148 // blockSpec implementation
1149 func (s *BlockAttrsSpec) nestedSpec() Spec {
1150 // This is an odd case: we aren't actually going to apply a nested spec
1151 // in this case, since we're going to interpret the body directly as
1152 // attributes, but we need to return something non-nil so that the
1153 // decoder will recognize this as a block spec. We won't actually be
1154 // using this for anything at decode time.
1158 // specNeedingVariables implementation
1159 func (s *BlockAttrsSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
1161 block, _ := s.findBlock(content)
1166 var vars []hcl.Traversal
1168 attrs, diags := block.Body.JustAttributes()
1169 if diags.HasErrors() {
1173 for _, attr := range attrs {
1174 vars = append(vars, attr.Expr.Variables()...)
1177 // We'll return the variables references in source order so that any
1178 // error messages that result are also in source order.
1179 sort.Slice(vars, func(i, j int) bool {
1180 return vars[i].SourceRange().Start.Byte < vars[j].SourceRange().Start.Byte
1186 func (s *BlockAttrsSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
1187 var diags hcl.Diagnostics
1189 block, other := s.findBlock(content)
1192 diags = append(diags, &hcl.Diagnostic{
1193 Severity: hcl.DiagError,
1194 Summary: fmt.Sprintf("Missing %s block", s.TypeName),
1195 Detail: fmt.Sprintf(
1196 "A block of type %q is required here.", s.TypeName,
1198 Subject: &content.MissingItemRange,
1201 return cty.NullVal(cty.Map(s.ElementType)), diags
1204 diags = append(diags, &hcl.Diagnostic{
1205 Severity: hcl.DiagError,
1206 Summary: fmt.Sprintf("Duplicate %s block", s.TypeName),
1207 Detail: fmt.Sprintf(
1208 "Only one block of type %q is allowed. Previous definition was at %s.",
1209 s.TypeName, block.DefRange.String(),
1211 Subject: &other.DefRange,
1215 attrs, attrDiags := block.Body.JustAttributes()
1216 diags = append(diags, attrDiags...)
1218 if len(attrs) == 0 {
1219 return cty.MapValEmpty(s.ElementType), diags
1222 vals := make(map[string]cty.Value, len(attrs))
1223 for name, attr := range attrs {
1224 attrVal, attrDiags := attr.Expr.Value(ctx)
1225 diags = append(diags, attrDiags...)
1227 attrVal, err := convert.Convert(attrVal, s.ElementType)
1229 diags = append(diags, &hcl.Diagnostic{
1230 Severity: hcl.DiagError,
1231 Summary: "Invalid attribute value",
1232 Detail: fmt.Sprintf("Invalid value for attribute of %q block: %s.", s.TypeName, err),
1233 Subject: attr.Expr.Range().Ptr(),
1235 attrVal = cty.UnknownVal(s.ElementType)
1238 vals[name] = attrVal
1241 return cty.MapVal(vals), diags
1244 func (s *BlockAttrsSpec) impliedType() cty.Type {
1245 return cty.Map(s.ElementType)
1248 func (s *BlockAttrsSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
1249 block, _ := s.findBlock(content)
1251 return content.MissingItemRange
1253 return block.DefRange
1256 func (s *BlockAttrsSpec) findBlock(content *hcl.BodyContent) (block *hcl.Block, other *hcl.Block) {
1257 for _, candidate := range content.Blocks {
1258 if candidate.Type != s.TypeName {
1262 return block, candidate
1270 // A BlockLabelSpec is a Spec that returns a cty.String representing the
1271 // label of the block its given body belongs to, if indeed its given body
1272 // belongs to a block. It is a programming error to use this in a non-block
1273 // context, so this spec will panic in that case.
1275 // This spec only works in the nested spec within a BlockSpec, BlockListSpec,
1276 // BlockSetSpec or BlockMapSpec.
1278 // The full set of label specs used against a particular block must have a
1279 // consecutive set of indices starting at zero. The maximum index found
1280 // defines how many labels the corresponding blocks must have in cty source.
1281 type BlockLabelSpec struct {
1286 func (s *BlockLabelSpec) visitSameBodyChildren(cb visitFunc) {
1290 func (s *BlockLabelSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
1291 if s.Index >= len(blockLabels) {
1292 panic("BlockListSpec used in non-block context")
1295 return cty.StringVal(blockLabels[s.Index].Value), nil
1298 func (s *BlockLabelSpec) impliedType() cty.Type {
1299 return cty.String // labels are always strings
1302 func (s *BlockLabelSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
1303 if s.Index >= len(blockLabels) {
1304 panic("BlockListSpec used in non-block context")
1307 return blockLabels[s.Index].Range
1310 func findLabelSpecs(spec Spec) []string {
1312 var names map[int]string
1315 visit = func(s Spec) {
1316 if ls, ok := s.(*BlockLabelSpec); ok {
1317 if maxIdx < ls.Index {
1321 names = make(map[int]string)
1323 names[ls.Index] = ls.Name
1325 s.visitSameBodyChildren(visit)
1331 return nil // no labels at all
1334 ret := make([]string, maxIdx+1)
1335 for i := range ret {
1338 // Should never happen if the spec is conformant, since we require
1339 // consecutive indices starting at zero.
1340 name = fmt.Sprintf("missing%02d", i)
1348 // DefaultSpec is a spec that wraps two specs, evaluating the primary first
1349 // and then evaluating the default if the primary returns a null value.
1351 // The two specifications must have the same implied result type for correct
1352 // operation. If not, the result is undefined.
1354 // Any requirements imposed by the "Default" spec apply even if "Primary" does
1355 // not return null. For example, if the "Default" spec is for a required
1356 // attribute then that attribute is always required, regardless of the result
1357 // of the "Primary" spec.
1359 // The "Default" spec must not describe a nested block, since otherwise the
1360 // result of ChildBlockTypes would not be decidable without evaluation. If
1361 // the default spec _does_ describe a nested block then the result is
1363 type DefaultSpec struct {
1368 func (s *DefaultSpec) visitSameBodyChildren(cb visitFunc) {
1373 func (s *DefaultSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
1374 val, diags := s.Primary.decode(content, blockLabels, ctx)
1376 var moreDiags hcl.Diagnostics
1377 val, moreDiags = s.Default.decode(content, blockLabels, ctx)
1378 diags = append(diags, moreDiags...)
1383 func (s *DefaultSpec) impliedType() cty.Type {
1384 return s.Primary.impliedType()
1387 // attrSpec implementation
1388 func (s *DefaultSpec) attrSchemata() []hcl.AttributeSchema {
1389 // We must pass through the union of both of our nested specs so that
1390 // we'll have both values available in the result.
1391 var ret []hcl.AttributeSchema
1392 if as, ok := s.Primary.(attrSpec); ok {
1393 ret = append(ret, as.attrSchemata()...)
1395 if as, ok := s.Default.(attrSpec); ok {
1396 ret = append(ret, as.attrSchemata()...)
1401 // blockSpec implementation
1402 func (s *DefaultSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
1403 // Only the primary spec may describe a block, since otherwise
1404 // our nestedSpec method below can't know which to return.
1405 if bs, ok := s.Primary.(blockSpec); ok {
1406 return bs.blockHeaderSchemata()
1411 // blockSpec implementation
1412 func (s *DefaultSpec) nestedSpec() Spec {
1413 if bs, ok := s.Primary.(blockSpec); ok {
1414 return bs.nestedSpec()
1419 func (s *DefaultSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
1420 // We can't tell from here which of the two specs will ultimately be used
1421 // in our result, so we'll just assume the first. This is usually the right
1422 // choice because the default is often a literal spec that doesn't have a
1423 // reasonable source range to return anyway.
1424 return s.Primary.sourceRange(content, blockLabels)
1427 // TransformExprSpec is a spec that wraps another and then evaluates a given
1428 // hcl.Expression on the result.
1430 // The implied type of this spec is determined by evaluating the expression
1431 // with an unknown value of the nested spec's implied type, which may cause
1432 // the result to be imprecise. This spec should not be used in situations where
1433 // precise result type information is needed.
1434 type TransformExprSpec struct {
1437 TransformCtx *hcl.EvalContext
1441 func (s *TransformExprSpec) visitSameBodyChildren(cb visitFunc) {
1445 func (s *TransformExprSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
1446 wrappedVal, diags := s.Wrapped.decode(content, blockLabels, ctx)
1447 if diags.HasErrors() {
1448 // We won't try to run our function in this case, because it'll probably
1449 // generate confusing additional errors that will distract from the
1451 return cty.UnknownVal(s.impliedType()), diags
1454 chiCtx := s.TransformCtx.NewChild()
1455 chiCtx.Variables = map[string]cty.Value{
1456 s.VarName: wrappedVal,
1458 resultVal, resultDiags := s.Expr.Value(chiCtx)
1459 diags = append(diags, resultDiags...)
1460 return resultVal, diags
1463 func (s *TransformExprSpec) impliedType() cty.Type {
1464 wrappedTy := s.Wrapped.impliedType()
1465 chiCtx := s.TransformCtx.NewChild()
1466 chiCtx.Variables = map[string]cty.Value{
1467 s.VarName: cty.UnknownVal(wrappedTy),
1469 resultVal, _ := s.Expr.Value(chiCtx)
1470 return resultVal.Type()
1473 func (s *TransformExprSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
1474 // We'll just pass through our wrapped range here, even though that's
1475 // not super-accurate, because there's nothing better to return.
1476 return s.Wrapped.sourceRange(content, blockLabels)
1479 // TransformFuncSpec is a spec that wraps another and then evaluates a given
1480 // cty function with the result. The given function must expect exactly one
1481 // argument, where the result of the wrapped spec will be passed.
1483 // The implied type of this spec is determined by type-checking the function
1484 // with an unknown value of the nested spec's implied type, which may cause
1485 // the result to be imprecise. This spec should not be used in situations where
1486 // precise result type information is needed.
1488 // If the given function produces an error when run, this spec will produce
1489 // a non-user-actionable diagnostic message. It's the caller's responsibility
1490 // to ensure that the given function cannot fail for any non-error result
1491 // of the wrapped spec.
1492 type TransformFuncSpec struct {
1494 Func function.Function
1497 func (s *TransformFuncSpec) visitSameBodyChildren(cb visitFunc) {
1501 func (s *TransformFuncSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
1502 wrappedVal, diags := s.Wrapped.decode(content, blockLabels, ctx)
1503 if diags.HasErrors() {
1504 // We won't try to run our function in this case, because it'll probably
1505 // generate confusing additional errors that will distract from the
1507 return cty.UnknownVal(s.impliedType()), diags
1510 resultVal, err := s.Func.Call([]cty.Value{wrappedVal})
1512 // This is not a good example of a diagnostic because it is reporting
1513 // a programming error in the calling application, rather than something
1514 // an end-user could act on.
1515 diags = append(diags, &hcl.Diagnostic{
1516 Severity: hcl.DiagError,
1517 Summary: "Transform function failed",
1518 Detail: fmt.Sprintf("Decoder transform returned an error: %s", err),
1519 Subject: s.sourceRange(content, blockLabels).Ptr(),
1521 return cty.UnknownVal(s.impliedType()), diags
1524 return resultVal, diags
1527 func (s *TransformFuncSpec) impliedType() cty.Type {
1528 wrappedTy := s.Wrapped.impliedType()
1529 resultTy, err := s.Func.ReturnType([]cty.Type{wrappedTy})
1531 // Should never happen with a correctly-configured spec
1532 return cty.DynamicPseudoType
1538 func (s *TransformFuncSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
1539 // We'll just pass through our wrapped range here, even though that's
1540 // not super-accurate, because there's nothing better to return.
1541 return s.Wrapped.sourceRange(content, blockLabels)
1544 // noopSpec is a placeholder spec that does nothing, used in situations where
1545 // a non-nil placeholder spec is required. It is not exported because there is
1546 // no reason to use it directly; it is always an implementation detail only.
1547 type noopSpec struct {
1550 func (s noopSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
1551 return cty.NullVal(cty.DynamicPseudoType), nil
1554 func (s noopSpec) impliedType() cty.Type {
1555 return cty.DynamicPseudoType
1558 func (s noopSpec) visitSameBodyChildren(cb visitFunc) {
1562 func (s noopSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
1563 // No useful range for a noopSpec, and nobody should be calling this anyway.
1565 Filename: "noopSpec",