diff options
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/hcldec/spec.go')
-rw-r--r-- | vendor/github.com/hashicorp/hcl2/hcldec/spec.go | 571 |
1 files changed, 570 insertions, 1 deletions
diff --git a/vendor/github.com/hashicorp/hcl2/hcldec/spec.go b/vendor/github.com/hashicorp/hcl2/hcldec/spec.go index 25cafcd..f9da7f6 100644 --- a/vendor/github.com/hashicorp/hcl2/hcldec/spec.go +++ b/vendor/github.com/hashicorp/hcl2/hcldec/spec.go | |||
@@ -3,6 +3,7 @@ package hcldec | |||
3 | import ( | 3 | import ( |
4 | "bytes" | 4 | "bytes" |
5 | "fmt" | 5 | "fmt" |
6 | "sort" | ||
6 | 7 | ||
7 | "github.com/hashicorp/hcl2/hcl" | 8 | "github.com/hashicorp/hcl2/hcl" |
8 | "github.com/zclconf/go-cty/cty" | 9 | "github.com/zclconf/go-cty/cty" |
@@ -477,6 +478,44 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabe | |||
477 | if len(elems) == 0 { | 478 | if len(elems) == 0 { |
478 | ret = cty.ListValEmpty(s.Nested.impliedType()) | 479 | ret = cty.ListValEmpty(s.Nested.impliedType()) |
479 | } else { | 480 | } else { |
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 { | ||
488 | etys[i] = v.Type() | ||
489 | } | ||
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], | ||
498 | }) | ||
499 | return cty.DynamicVal, diags | ||
500 | } | ||
501 | for i, v := range elems { | ||
502 | if convs[i] != nil { | ||
503 | newV, err := convs[i](v) | ||
504 | if err != nil { | ||
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], | ||
511 | }) | ||
512 | // Bail early here so we won't panic below in cty.ListVal | ||
513 | return cty.DynamicVal, diags | ||
514 | } | ||
515 | elems[i] = newV | ||
516 | } | ||
517 | } | ||
518 | |||
480 | ret = cty.ListVal(elems) | 519 | ret = cty.ListVal(elems) |
481 | } | 520 | } |
482 | 521 | ||
@@ -508,6 +547,127 @@ func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, blockLabels []bloc | |||
508 | return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) | 547 | return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) |
509 | } | 548 | } |
510 | 549 | ||
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. | ||
552 | // | ||
553 | // This is similar to BlockListSpec, but it permits the nested blocks to have | ||
554 | // different result types in situations where cty.DynamicPseudoType attributes | ||
555 | // are present. | ||
556 | type BlockTupleSpec struct { | ||
557 | TypeName string | ||
558 | Nested Spec | ||
559 | MinItems int | ||
560 | MaxItems int | ||
561 | } | ||
562 | |||
563 | func (s *BlockTupleSpec) visitSameBodyChildren(cb visitFunc) { | ||
564 | // leaf node ("Nested" does not use the same body) | ||
565 | } | ||
566 | |||
567 | // blockSpec implementation | ||
568 | func (s *BlockTupleSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { | ||
569 | return []hcl.BlockHeaderSchema{ | ||
570 | { | ||
571 | Type: s.TypeName, | ||
572 | LabelNames: findLabelSpecs(s.Nested), | ||
573 | }, | ||
574 | } | ||
575 | } | ||
576 | |||
577 | // blockSpec implementation | ||
578 | func (s *BlockTupleSpec) nestedSpec() Spec { | ||
579 | return s.Nested | ||
580 | } | ||
581 | |||
582 | // specNeedingVariables implementation | ||
583 | func (s *BlockTupleSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { | ||
584 | var ret []hcl.Traversal | ||
585 | |||
586 | for _, childBlock := range content.Blocks { | ||
587 | if childBlock.Type != s.TypeName { | ||
588 | continue | ||
589 | } | ||
590 | |||
591 | ret = append(ret, Variables(childBlock.Body, s.Nested)...) | ||
592 | } | ||
593 | |||
594 | return ret | ||
595 | } | ||
596 | |||
597 | func (s *BlockTupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
598 | var diags hcl.Diagnostics | ||
599 | |||
600 | if s.Nested == nil { | ||
601 | panic("BlockListSpec with no Nested Spec") | ||
602 | } | ||
603 | |||
604 | var elems []cty.Value | ||
605 | var sourceRanges []hcl.Range | ||
606 | for _, childBlock := range content.Blocks { | ||
607 | if childBlock.Type != s.TypeName { | ||
608 | continue | ||
609 | } | ||
610 | |||
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)) | ||
615 | } | ||
616 | |||
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, | ||
623 | }) | ||
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], | ||
630 | }) | ||
631 | } | ||
632 | |||
633 | var ret cty.Value | ||
634 | |||
635 | if len(elems) == 0 { | ||
636 | ret = cty.EmptyTupleVal | ||
637 | } else { | ||
638 | ret = cty.TupleVal(elems) | ||
639 | } | ||
640 | |||
641 | return ret, diags | ||
642 | } | ||
643 | |||
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 | ||
648 | } | ||
649 | |||
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. | ||
653 | |||
654 | var childBlock *hcl.Block | ||
655 | for _, candidate := range content.Blocks { | ||
656 | if candidate.Type != s.TypeName { | ||
657 | continue | ||
658 | } | ||
659 | |||
660 | childBlock = candidate | ||
661 | break | ||
662 | } | ||
663 | |||
664 | if childBlock == nil { | ||
665 | return content.MissingItemRange | ||
666 | } | ||
667 | |||
668 | return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) | ||
669 | } | ||
670 | |||
511 | // A BlockSetSpec is a Spec that produces a cty set of the results of | 671 | // A BlockSetSpec is a Spec that produces a cty set of the results of |
512 | // decoding all of the nested blocks of a given type, using a nested spec. | 672 | // decoding all of the nested blocks of a given type, using a nested spec. |
513 | type BlockSetSpec struct { | 673 | type BlockSetSpec struct { |
@@ -592,6 +752,44 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel | |||
592 | if len(elems) == 0 { | 752 | if len(elems) == 0 { |
593 | ret = cty.SetValEmpty(s.Nested.impliedType()) | 753 | ret = cty.SetValEmpty(s.Nested.impliedType()) |
594 | } else { | 754 | } else { |
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 { | ||
762 | etys[i] = v.Type() | ||
763 | } | ||
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], | ||
772 | }) | ||
773 | return cty.DynamicVal, diags | ||
774 | } | ||
775 | for i, v := range elems { | ||
776 | if convs[i] != nil { | ||
777 | newV, err := convs[i](v) | ||
778 | if err != nil { | ||
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], | ||
785 | }) | ||
786 | // Bail early here so we won't panic below in cty.ListVal | ||
787 | return cty.DynamicVal, diags | ||
788 | } | ||
789 | elems[i] = newV | ||
790 | } | ||
791 | } | ||
792 | |||
595 | ret = cty.SetVal(elems) | 793 | ret = cty.SetVal(elems) |
596 | } | 794 | } |
597 | 795 | ||
@@ -672,7 +870,10 @@ func (s *BlockMapSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel | |||
672 | var diags hcl.Diagnostics | 870 | var diags hcl.Diagnostics |
673 | 871 | ||
674 | if s.Nested == nil { | 872 | if s.Nested == nil { |
675 | panic("BlockSetSpec with no Nested Spec") | 873 | panic("BlockMapSpec with no Nested Spec") |
874 | } | ||
875 | if ImpliedType(s).HasDynamicTypes() { | ||
876 | panic("cty.DynamicPseudoType attributes may not be used inside a BlockMapSpec") | ||
676 | } | 877 | } |
677 | 878 | ||
678 | elems := map[string]interface{}{} | 879 | elems := map[string]interface{}{} |
@@ -765,6 +966,307 @@ func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, blockLabels []block | |||
765 | return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) | 966 | return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) |
766 | } | 967 | } |
767 | 968 | ||
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. | ||
971 | // | ||
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. | ||
974 | // | ||
975 | // This is similar to BlockMapSpec, but it permits the nested blocks to have | ||
976 | // different result types in situations where cty.DynamicPseudoType attributes | ||
977 | // are present. | ||
978 | type BlockObjectSpec struct { | ||
979 | TypeName string | ||
980 | LabelNames []string | ||
981 | Nested Spec | ||
982 | } | ||
983 | |||
984 | func (s *BlockObjectSpec) visitSameBodyChildren(cb visitFunc) { | ||
985 | // leaf node ("Nested" does not use the same body) | ||
986 | } | ||
987 | |||
988 | // blockSpec implementation | ||
989 | func (s *BlockObjectSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { | ||
990 | return []hcl.BlockHeaderSchema{ | ||
991 | { | ||
992 | Type: s.TypeName, | ||
993 | LabelNames: append(s.LabelNames, findLabelSpecs(s.Nested)...), | ||
994 | }, | ||
995 | } | ||
996 | } | ||
997 | |||
998 | // blockSpec implementation | ||
999 | func (s *BlockObjectSpec) nestedSpec() Spec { | ||
1000 | return s.Nested | ||
1001 | } | ||
1002 | |||
1003 | // specNeedingVariables implementation | ||
1004 | func (s *BlockObjectSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { | ||
1005 | var ret []hcl.Traversal | ||
1006 | |||
1007 | for _, childBlock := range content.Blocks { | ||
1008 | if childBlock.Type != s.TypeName { | ||
1009 | continue | ||
1010 | } | ||
1011 | |||
1012 | ret = append(ret, Variables(childBlock.Body, s.Nested)...) | ||
1013 | } | ||
1014 | |||
1015 | return ret | ||
1016 | } | ||
1017 | |||
1018 | func (s *BlockObjectSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
1019 | var diags hcl.Diagnostics | ||
1020 | |||
1021 | if s.Nested == nil { | ||
1022 | panic("BlockObjectSpec with no Nested Spec") | ||
1023 | } | ||
1024 | |||
1025 | elems := map[string]interface{}{} | ||
1026 | for _, childBlock := range content.Blocks { | ||
1027 | if childBlock.Type != s.TypeName { | ||
1028 | continue | ||
1029 | } | ||
1030 | |||
1031 | childLabels := labelsForBlock(childBlock) | ||
1032 | val, _, childDiags := decode(childBlock.Body, childLabels[len(s.LabelNames):], ctx, s.Nested, false) | ||
1033 | targetMap := elems | ||
1034 | for _, key := range childBlock.Labels[:len(s.LabelNames)-1] { | ||
1035 | if _, exists := targetMap[key]; !exists { | ||
1036 | targetMap[key] = make(map[string]interface{}) | ||
1037 | } | ||
1038 | targetMap = targetMap[key].(map[string]interface{}) | ||
1039 | } | ||
1040 | |||
1041 | diags = append(diags, childDiags...) | ||
1042 | |||
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) | ||
1048 | } | ||
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, | ||
1055 | ), | ||
1056 | Subject: &childBlock.DefRange, | ||
1057 | }) | ||
1058 | continue | ||
1059 | } | ||
1060 | |||
1061 | targetMap[key] = val | ||
1062 | } | ||
1063 | |||
1064 | if len(elems) == 0 { | ||
1065 | return cty.EmptyObjectVal, diags | ||
1066 | } | ||
1067 | |||
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)) | ||
1071 | if depth == 1 { | ||
1072 | for k, v := range raw { | ||
1073 | vals[k] = v.(cty.Value) | ||
1074 | } | ||
1075 | } else { | ||
1076 | for k, v := range raw { | ||
1077 | vals[k] = ctyObj(v.(map[string]interface{}), depth-1) | ||
1078 | } | ||
1079 | } | ||
1080 | return cty.ObjectVal(vals) | ||
1081 | } | ||
1082 | |||
1083 | return ctyObj(elems, len(s.LabelNames)), diags | ||
1084 | } | ||
1085 | |||
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 | ||
1090 | } | ||
1091 | |||
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. | ||
1095 | |||
1096 | var childBlock *hcl.Block | ||
1097 | for _, candidate := range content.Blocks { | ||
1098 | if candidate.Type != s.TypeName { | ||
1099 | continue | ||
1100 | } | ||
1101 | |||
1102 | childBlock = candidate | ||
1103 | break | ||
1104 | } | ||
1105 | |||
1106 | if childBlock == nil { | ||
1107 | return content.MissingItemRange | ||
1108 | } | ||
1109 | |||
1110 | return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) | ||
1111 | } | ||
1112 | |||
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. | ||
1118 | // | ||
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. | ||
1122 | // | ||
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 { | ||
1129 | TypeName string | ||
1130 | ElementType cty.Type | ||
1131 | Required bool | ||
1132 | } | ||
1133 | |||
1134 | func (s *BlockAttrsSpec) visitSameBodyChildren(cb visitFunc) { | ||
1135 | // leaf node | ||
1136 | } | ||
1137 | |||
1138 | // blockSpec implementation | ||
1139 | func (s *BlockAttrsSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { | ||
1140 | return []hcl.BlockHeaderSchema{ | ||
1141 | { | ||
1142 | Type: s.TypeName, | ||
1143 | LabelNames: nil, | ||
1144 | }, | ||
1145 | } | ||
1146 | } | ||
1147 | |||
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. | ||
1155 | return noopSpec{} | ||
1156 | } | ||
1157 | |||
1158 | // specNeedingVariables implementation | ||
1159 | func (s *BlockAttrsSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { | ||
1160 | |||
1161 | block, _ := s.findBlock(content) | ||
1162 | if block == nil { | ||
1163 | return nil | ||
1164 | } | ||
1165 | |||
1166 | var vars []hcl.Traversal | ||
1167 | |||
1168 | attrs, diags := block.Body.JustAttributes() | ||
1169 | if diags.HasErrors() { | ||
1170 | return nil | ||
1171 | } | ||
1172 | |||
1173 | for _, attr := range attrs { | ||
1174 | vars = append(vars, attr.Expr.Variables()...) | ||
1175 | } | ||
1176 | |||
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 | ||
1181 | }) | ||
1182 | |||
1183 | return vars | ||
1184 | } | ||
1185 | |||
1186 | func (s *BlockAttrsSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
1187 | var diags hcl.Diagnostics | ||
1188 | |||
1189 | block, other := s.findBlock(content) | ||
1190 | if block == nil { | ||
1191 | if s.Required { | ||
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, | ||
1197 | ), | ||
1198 | Subject: &content.MissingItemRange, | ||
1199 | }) | ||
1200 | } | ||
1201 | return cty.NullVal(cty.Map(s.ElementType)), diags | ||
1202 | } | ||
1203 | if other != nil { | ||
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(), | ||
1210 | ), | ||
1211 | Subject: &other.DefRange, | ||
1212 | }) | ||
1213 | } | ||
1214 | |||
1215 | attrs, attrDiags := block.Body.JustAttributes() | ||
1216 | diags = append(diags, attrDiags...) | ||
1217 | |||
1218 | if len(attrs) == 0 { | ||
1219 | return cty.MapValEmpty(s.ElementType), diags | ||
1220 | } | ||
1221 | |||
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...) | ||
1226 | |||
1227 | attrVal, err := convert.Convert(attrVal, s.ElementType) | ||
1228 | if err != nil { | ||
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(), | ||
1234 | }) | ||
1235 | attrVal = cty.UnknownVal(s.ElementType) | ||
1236 | } | ||
1237 | |||
1238 | vals[name] = attrVal | ||
1239 | } | ||
1240 | |||
1241 | return cty.MapVal(vals), diags | ||
1242 | } | ||
1243 | |||
1244 | func (s *BlockAttrsSpec) impliedType() cty.Type { | ||
1245 | return cty.Map(s.ElementType) | ||
1246 | } | ||
1247 | |||
1248 | func (s *BlockAttrsSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
1249 | block, _ := s.findBlock(content) | ||
1250 | if block == nil { | ||
1251 | return content.MissingItemRange | ||
1252 | } | ||
1253 | return block.DefRange | ||
1254 | } | ||
1255 | |||
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 { | ||
1259 | continue | ||
1260 | } | ||
1261 | if block != nil { | ||
1262 | return block, candidate | ||
1263 | } | ||
1264 | block = candidate | ||
1265 | } | ||
1266 | |||
1267 | return block, nil | ||
1268 | } | ||
1269 | |||
768 | // A BlockLabelSpec is a Spec that returns a cty.String representing the | 1270 | // A BlockLabelSpec is a Spec that returns a cty.String representing the |
769 | // label of the block its given body belongs to, if indeed its given body | 1271 | // label of the block its given body belongs to, if indeed its given body |
770 | // belongs to a block. It is a programming error to use this in a non-block | 1272 | // belongs to a block. It is a programming error to use this in a non-block |
@@ -848,6 +1350,16 @@ func findLabelSpecs(spec Spec) []string { | |||
848 | // | 1350 | // |
849 | // The two specifications must have the same implied result type for correct | 1351 | // The two specifications must have the same implied result type for correct |
850 | // operation. If not, the result is undefined. | 1352 | // operation. If not, the result is undefined. |
1353 | // | ||
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. | ||
1358 | // | ||
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 | ||
1362 | // undefined. | ||
851 | type DefaultSpec struct { | 1363 | type DefaultSpec struct { |
852 | Primary Spec | 1364 | Primary Spec |
853 | Default Spec | 1365 | Default Spec |
@@ -872,6 +1384,38 @@ func (s *DefaultSpec) impliedType() cty.Type { | |||
872 | return s.Primary.impliedType() | 1384 | return s.Primary.impliedType() |
873 | } | 1385 | } |
874 | 1386 | ||
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()...) | ||
1394 | } | ||
1395 | if as, ok := s.Default.(attrSpec); ok { | ||
1396 | ret = append(ret, as.attrSchemata()...) | ||
1397 | } | ||
1398 | return ret | ||
1399 | } | ||
1400 | |||
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() | ||
1407 | } | ||
1408 | return nil | ||
1409 | } | ||
1410 | |||
1411 | // blockSpec implementation | ||
1412 | func (s *DefaultSpec) nestedSpec() Spec { | ||
1413 | if bs, ok := s.Primary.(blockSpec); ok { | ||
1414 | return bs.nestedSpec() | ||
1415 | } | ||
1416 | return nil | ||
1417 | } | ||
1418 | |||
875 | func (s *DefaultSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | 1419 | func (s *DefaultSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { |
876 | // We can't tell from here which of the two specs will ultimately be used | 1420 | // We can't tell from here which of the two specs will ultimately be used |
877 | // in our result, so we'll just assume the first. This is usually the right | 1421 | // in our result, so we'll just assume the first. This is usually the right |
@@ -996,3 +1540,28 @@ func (s *TransformFuncSpec) sourceRange(content *hcl.BodyContent, blockLabels [] | |||
996 | // not super-accurate, because there's nothing better to return. | 1540 | // not super-accurate, because there's nothing better to return. |
997 | return s.Wrapped.sourceRange(content, blockLabels) | 1541 | return s.Wrapped.sourceRange(content, blockLabels) |
998 | } | 1542 | } |
1543 | |||
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 { | ||
1548 | } | ||
1549 | |||
1550 | func (s noopSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
1551 | return cty.NullVal(cty.DynamicPseudoType), nil | ||
1552 | } | ||
1553 | |||
1554 | func (s noopSpec) impliedType() cty.Type { | ||
1555 | return cty.DynamicPseudoType | ||
1556 | } | ||
1557 | |||
1558 | func (s noopSpec) visitSameBodyChildren(cb visitFunc) { | ||
1559 | // nothing to do | ||
1560 | } | ||
1561 | |||
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. | ||
1564 | return hcl.Range{ | ||
1565 | Filename: "noopSpec", | ||
1566 | } | ||
1567 | } | ||