]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/hcl2/hcldec/spec.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / hcl2 / hcldec / spec.go
CommitLineData
15c0b25d
AP
1package hcldec
2
3import (
4 "bytes"
5 "fmt"
107c1cdb 6 "sort"
15c0b25d
AP
7
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"
12)
13
14// A Spec is a description of how to decode a hcl.Body to a cty.Value.
15//
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.
19type Spec interface {
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.
22 //
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)
26
27 // Return the cty.Type that should be returned when decoding a body with
28 // this spec.
29 impliedType() cty.Type
30
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)
35
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
41}
42
43type visitFunc func(spec Spec)
44
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.
47type ObjectSpec map[string]Spec
48
49// attrSpec is implemented by specs that require attributes from the body.
50type attrSpec interface {
51 attrSchemata() []hcl.AttributeSchema
52}
53
54// blockSpec is implemented by specs that require blocks from the body.
55type blockSpec interface {
56 blockHeaderSchemata() []hcl.BlockHeaderSchema
57 nestedSpec() Spec
58}
59
60// specNeedingVariables is implemented by specs that can use variables
61// from the EvalContext, to declare which variables they need.
62type specNeedingVariables interface {
63 variablesNeeded(content *hcl.BodyContent) []hcl.Traversal
64}
65
66func (s ObjectSpec) visitSameBodyChildren(cb visitFunc) {
67 for _, c := range s {
68 cb(c)
69 }
70}
71
72func (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
75
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...)
80 }
81
82 return cty.ObjectVal(vals), diags
83}
84
85func (s ObjectSpec) impliedType() cty.Type {
86 if len(s) == 0 {
87 return cty.EmptyObject
88 }
89
90 attrTypes := make(map[string]cty.Type)
91 for k, childSpec := range s {
92 attrTypes[k] = childSpec.impliedType()
93 }
94 return cty.Object(attrTypes)
95}
96
97func (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
102}
103
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.
106type TupleSpec []Spec
107
108func (s TupleSpec) visitSameBodyChildren(cb visitFunc) {
109 for _, c := range s {
110 cb(c)
111 }
112}
113
114func (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
117
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...)
122 }
123
124 return cty.TupleVal(vals), diags
125}
126
127func (s TupleSpec) impliedType() cty.Type {
128 if len(s) == 0 {
129 return cty.EmptyTuple
130 }
131
132 attrTypes := make([]cty.Type, len(s))
133 for i, childSpec := range s {
134 attrTypes[i] = childSpec.impliedType()
135 }
136 return cty.Tuple(attrTypes)
137}
138
139func (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
144}
145
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.
149type AttrSpec struct {
150 Name string
151 Type cty.Type
152 Required bool
153}
154
155func (s *AttrSpec) visitSameBodyChildren(cb visitFunc) {
156 // leaf node
157}
158
159// specNeedingVariables implementation
160func (s *AttrSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
161 attr, exists := content.Attributes[s.Name]
162 if !exists {
163 return nil
164 }
165
166 return attr.Expr.Variables()
167}
168
169// attrSpec implementation
170func (s *AttrSpec) attrSchemata() []hcl.AttributeSchema {
171 return []hcl.AttributeSchema{
172 {
173 Name: s.Name,
174 Required: s.Required,
175 },
176 }
177}
178
179func (s *AttrSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
180 attr, exists := content.Attributes[s.Name]
181 if !exists {
182 return content.MissingItemRange
183 }
184
185 return attr.Expr.Range()
186}
187
188func (s *AttrSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
189 attr, exists := content.Attributes[s.Name]
190 if !exists {
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
194 }
195
196 val, diags := attr.Expr.Value(ctx)
197
198 convVal, err := convert.Convert(val, s.Type)
199 if err != nil {
200 diags = append(diags, &hcl.Diagnostic{
201 Severity: hcl.DiagError,
202 Summary: "Incorrect attribute value type",
203 Detail: fmt.Sprintf(
204 "Inappropriate value for attribute %q: %s.",
205 s.Name, err.Error(),
206 ),
207 Subject: attr.Expr.StartRange().Ptr(),
208 Context: hcl.RangeBetween(attr.NameRange, attr.Expr.StartRange()).Ptr(),
209 })
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)
213 } else {
214 val = convVal
215 }
216
217 return val, diags
218}
219
220func (s *AttrSpec) impliedType() cty.Type {
221 return s.Type
222}
223
224// A LiteralSpec is a Spec that produces the given literal value, ignoring
225// the given body.
226type LiteralSpec struct {
227 Value cty.Value
228}
229
230func (s *LiteralSpec) visitSameBodyChildren(cb visitFunc) {
231 // leaf node
232}
233
234func (s *LiteralSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
235 return s.Value, nil
236}
237
238func (s *LiteralSpec) impliedType() cty.Type {
239 return s.Value.Type()
240}
241
242func (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.
245 return hcl.Range{
246 Filename: "<unknown>",
247 }
248}
249
250// An ExprSpec is a Spec that evaluates the given expression, ignoring the
251// given body.
252type ExprSpec struct {
253 Expr hcl.Expression
254}
255
256func (s *ExprSpec) visitSameBodyChildren(cb visitFunc) {
257 // leaf node
258}
259
260// specNeedingVariables implementation
261func (s *ExprSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
262 return s.Expr.Variables()
263}
264
265func (s *ExprSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
266 return s.Expr.Value(ctx)
267}
268
269func (s *ExprSpec) impliedType() cty.Type {
270 // We can't know the type of our expression until we evaluate it
271 return cty.DynamicPseudoType
272}
273
274func (s *ExprSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
275 return s.Expr.Range()
276}
277
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.
280//
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.
284type BlockSpec struct {
285 TypeName string
286 Nested Spec
287 Required bool
288}
289
290func (s *BlockSpec) visitSameBodyChildren(cb visitFunc) {
291 // leaf node ("Nested" does not use the same body)
292}
293
294// blockSpec implementation
295func (s *BlockSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
296 return []hcl.BlockHeaderSchema{
297 {
298 Type: s.TypeName,
299 LabelNames: findLabelSpecs(s.Nested),
300 },
301 }
302}
303
304// blockSpec implementation
305func (s *BlockSpec) nestedSpec() Spec {
306 return s.Nested
307}
308
309// specNeedingVariables implementation
310func (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 {
314 continue
315 }
316
317 childBlock = candidate
318 break
319 }
320
321 if childBlock == nil {
322 return nil
323 }
324
325 return Variables(childBlock.Body, s.Nested)
326}
327
328func (s *BlockSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
329 var diags hcl.Diagnostics
330
331 var childBlock *hcl.Block
332 for _, candidate := range content.Blocks {
333 if candidate.Type != s.TypeName {
334 continue
335 }
336
337 if childBlock != nil {
338 diags = append(diags, &hcl.Diagnostic{
339 Severity: hcl.DiagError,
340 Summary: fmt.Sprintf("Duplicate %s block", s.TypeName),
341 Detail: fmt.Sprintf(
342 "Only one block of type %q is allowed. Previous definition was at %s.",
343 s.TypeName, childBlock.DefRange.String(),
344 ),
345 Subject: &candidate.DefRange,
346 })
347 break
348 }
349
350 childBlock = candidate
351 }
352
353 if childBlock == nil {
354 if s.Required {
355 diags = append(diags, &hcl.Diagnostic{
356 Severity: hcl.DiagError,
357 Summary: fmt.Sprintf("Missing %s block", s.TypeName),
358 Detail: fmt.Sprintf(
359 "A block of type %q is required here.", s.TypeName,
360 ),
361 Subject: &content.MissingItemRange,
362 })
363 }
364 return cty.NullVal(s.Nested.impliedType()), diags
365 }
366
367 if s.Nested == nil {
368 panic("BlockSpec with no Nested Spec")
369 }
370 val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
371 diags = append(diags, childDiags...)
372 return val, diags
373}
374
375func (s *BlockSpec) impliedType() cty.Type {
376 return s.Nested.impliedType()
377}
378
379func (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 {
383 continue
384 }
385
386 childBlock = candidate
387 break
388 }
389
390 if childBlock == nil {
391 return content.MissingItemRange
392 }
393
394 return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
395}
396
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.
399type BlockListSpec struct {
400 TypeName string
401 Nested Spec
402 MinItems int
403 MaxItems int
404}
405
406func (s *BlockListSpec) visitSameBodyChildren(cb visitFunc) {
407 // leaf node ("Nested" does not use the same body)
408}
409
410// blockSpec implementation
411func (s *BlockListSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
412 return []hcl.BlockHeaderSchema{
413 {
414 Type: s.TypeName,
415 LabelNames: findLabelSpecs(s.Nested),
416 },
417 }
418}
419
420// blockSpec implementation
421func (s *BlockListSpec) nestedSpec() Spec {
422 return s.Nested
423}
424
425// specNeedingVariables implementation
426func (s *BlockListSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
427 var ret []hcl.Traversal
428
429 for _, childBlock := range content.Blocks {
430 if childBlock.Type != s.TypeName {
431 continue
432 }
433
434 ret = append(ret, Variables(childBlock.Body, s.Nested)...)
435 }
436
437 return ret
438}
439
440func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
441 var diags hcl.Diagnostics
442
443 if s.Nested == nil {
444 panic("BlockListSpec with no Nested Spec")
445 }
446
447 var elems []cty.Value
448 var sourceRanges []hcl.Range
449 for _, childBlock := range content.Blocks {
450 if childBlock.Type != s.TypeName {
451 continue
452 }
453
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))
458 }
459
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,
466 })
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],
473 })
474 }
475
476 var ret cty.Value
477
478 if len(elems) == 0 {
479 ret = cty.ListValEmpty(s.Nested.impliedType())
480 } else {
107c1cdb
ND
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
15c0b25d
AP
519 ret = cty.ListVal(elems)
520 }
521
522 return ret, diags
523}
524
525func (s *BlockListSpec) impliedType() cty.Type {
526 return cty.List(s.Nested.impliedType())
527}
528
529func (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.
532
533 var childBlock *hcl.Block
534 for _, candidate := range content.Blocks {
535 if candidate.Type != s.TypeName {
536 continue
537 }
538
539 childBlock = candidate
540 break
541 }
542
543 if childBlock == nil {
544 return content.MissingItemRange
545 }
546
547 return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
548}
549
107c1cdb
ND
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.
556type BlockTupleSpec struct {
557 TypeName string
558 Nested Spec
559 MinItems int
560 MaxItems int
561}
562
563func (s *BlockTupleSpec) visitSameBodyChildren(cb visitFunc) {
564 // leaf node ("Nested" does not use the same body)
565}
566
567// blockSpec implementation
568func (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
578func (s *BlockTupleSpec) nestedSpec() Spec {
579 return s.Nested
580}
581
582// specNeedingVariables implementation
583func (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
597func (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
644func (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
650func (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
15c0b25d
AP
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.
673type BlockSetSpec struct {
674 TypeName string
675 Nested Spec
676 MinItems int
677 MaxItems int
678}
679
680func (s *BlockSetSpec) visitSameBodyChildren(cb visitFunc) {
681 // leaf node ("Nested" does not use the same body)
682}
683
684// blockSpec implementation
685func (s *BlockSetSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
686 return []hcl.BlockHeaderSchema{
687 {
688 Type: s.TypeName,
689 LabelNames: findLabelSpecs(s.Nested),
690 },
691 }
692}
693
694// blockSpec implementation
695func (s *BlockSetSpec) nestedSpec() Spec {
696 return s.Nested
697}
698
699// specNeedingVariables implementation
700func (s *BlockSetSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
701 var ret []hcl.Traversal
702
703 for _, childBlock := range content.Blocks {
704 if childBlock.Type != s.TypeName {
705 continue
706 }
707
708 ret = append(ret, Variables(childBlock.Body, s.Nested)...)
709 }
710
711 return ret
712}
713
714func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
715 var diags hcl.Diagnostics
716
717 if s.Nested == nil {
718 panic("BlockSetSpec with no Nested Spec")
719 }
720
721 var elems []cty.Value
722 var sourceRanges []hcl.Range
723 for _, childBlock := range content.Blocks {
724 if childBlock.Type != s.TypeName {
725 continue
726 }
727
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))
732 }
733
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,
740 })
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],
747 })
748 }
749
750 var ret cty.Value
751
752 if len(elems) == 0 {
753 ret = cty.SetValEmpty(s.Nested.impliedType())
754 } else {
107c1cdb
ND
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
15c0b25d
AP
793 ret = cty.SetVal(elems)
794 }
795
796 return ret, diags
797}
798
799func (s *BlockSetSpec) impliedType() cty.Type {
800 return cty.Set(s.Nested.impliedType())
801}
802
803func (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.
806
807 var childBlock *hcl.Block
808 for _, candidate := range content.Blocks {
809 if candidate.Type != s.TypeName {
810 continue
811 }
812
813 childBlock = candidate
814 break
815 }
816
817 if childBlock == nil {
818 return content.MissingItemRange
819 }
820
821 return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
822}
823
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.
826//
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.
829type BlockMapSpec struct {
830 TypeName string
831 LabelNames []string
832 Nested Spec
833}
834
835func (s *BlockMapSpec) visitSameBodyChildren(cb visitFunc) {
836 // leaf node ("Nested" does not use the same body)
837}
838
839// blockSpec implementation
840func (s *BlockMapSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
841 return []hcl.BlockHeaderSchema{
842 {
843 Type: s.TypeName,
844 LabelNames: append(s.LabelNames, findLabelSpecs(s.Nested)...),
845 },
846 }
847}
848
849// blockSpec implementation
850func (s *BlockMapSpec) nestedSpec() Spec {
851 return s.Nested
852}
853
854// specNeedingVariables implementation
855func (s *BlockMapSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
856 var ret []hcl.Traversal
857
858 for _, childBlock := range content.Blocks {
859 if childBlock.Type != s.TypeName {
860 continue
861 }
862
863 ret = append(ret, Variables(childBlock.Body, s.Nested)...)
864 }
865
866 return ret
867}
868
869func (s *BlockMapSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
870 var diags hcl.Diagnostics
871
872 if s.Nested == nil {
107c1cdb
ND
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")
15c0b25d
AP
877 }
878
879 elems := map[string]interface{}{}
880 for _, childBlock := range content.Blocks {
881 if childBlock.Type != s.TypeName {
882 continue
883 }
884
885 childLabels := labelsForBlock(childBlock)
886 val, _, childDiags := decode(childBlock.Body, childLabels[len(s.LabelNames):], ctx, s.Nested, false)
887 targetMap := elems
888 for _, key := range childBlock.Labels[:len(s.LabelNames)-1] {
889 if _, exists := targetMap[key]; !exists {
890 targetMap[key] = make(map[string]interface{})
891 }
892 targetMap = targetMap[key].(map[string]interface{})
893 }
894
895 diags = append(diags, childDiags...)
896
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)
902 }
903 diags = append(diags, &hcl.Diagnostic{
904 Severity: hcl.DiagError,
905 Summary: fmt.Sprintf("Duplicate %s block", s.TypeName),
906 Detail: fmt.Sprintf(
907 "A block for %s%s was already defined. The %s labels must be unique.",
908 s.TypeName, labelsBuf.String(), s.TypeName,
909 ),
910 Subject: &childBlock.DefRange,
911 })
912 continue
913 }
914
915 targetMap[key] = val
916 }
917
918 if len(elems) == 0 {
919 return cty.MapValEmpty(s.Nested.impliedType()), diags
920 }
921
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))
925 if depth == 1 {
926 for k, v := range raw {
927 vals[k] = v.(cty.Value)
928 }
929 } else {
930 for k, v := range raw {
931 vals[k] = ctyMap(v.(map[string]interface{}), depth-1)
932 }
933 }
934 return cty.MapVal(vals)
935 }
936
937 return ctyMap(elems, len(s.LabelNames)), diags
938}
939
940func (s *BlockMapSpec) impliedType() cty.Type {
941 ret := s.Nested.impliedType()
942 for _ = range s.LabelNames {
943 ret = cty.Map(ret)
944 }
945 return ret
946}
947
948func (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.
951
952 var childBlock *hcl.Block
953 for _, candidate := range content.Blocks {
954 if candidate.Type != s.TypeName {
955 continue
956 }
957
958 childBlock = candidate
959 break
960 }
961
962 if childBlock == nil {
963 return content.MissingItemRange
964 }
965
966 return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
967}
968
107c1cdb
ND
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.
978type BlockObjectSpec struct {
979 TypeName string
980 LabelNames []string
981 Nested Spec
982}
983
984func (s *BlockObjectSpec) visitSameBodyChildren(cb visitFunc) {
985 // leaf node ("Nested" does not use the same body)
986}
987
988// blockSpec implementation
989func (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
999func (s *BlockObjectSpec) nestedSpec() Spec {
1000 return s.Nested
1001}
1002
1003// specNeedingVariables implementation
1004func (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
1018func (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
1086func (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
1092func (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.
1128type BlockAttrsSpec struct {
1129 TypeName string
1130 ElementType cty.Type
1131 Required bool
1132}
1133
1134func (s *BlockAttrsSpec) visitSameBodyChildren(cb visitFunc) {
1135 // leaf node
1136}
1137
1138// blockSpec implementation
1139func (s *BlockAttrsSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
1140 return []hcl.BlockHeaderSchema{
1141 {
1142 Type: s.TypeName,
1143 LabelNames: nil,
1144 },
1145 }
1146}
1147
1148// blockSpec implementation
1149func (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
1159func (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
1186func (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
1244func (s *BlockAttrsSpec) impliedType() cty.Type {
1245 return cty.Map(s.ElementType)
1246}
1247
1248func (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
1256func (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
15c0b25d
AP
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.
1274//
1275// This spec only works in the nested spec within a BlockSpec, BlockListSpec,
1276// BlockSetSpec or BlockMapSpec.
1277//
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.
1281type BlockLabelSpec struct {
1282 Index int
1283 Name string
1284}
1285
1286func (s *BlockLabelSpec) visitSameBodyChildren(cb visitFunc) {
1287 // leaf node
1288}
1289
1290func (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")
1293 }
1294
1295 return cty.StringVal(blockLabels[s.Index].Value), nil
1296}
1297
1298func (s *BlockLabelSpec) impliedType() cty.Type {
1299 return cty.String // labels are always strings
1300}
1301
1302func (s *BlockLabelSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
1303 if s.Index >= len(blockLabels) {
1304 panic("BlockListSpec used in non-block context")
1305 }
1306
1307 return blockLabels[s.Index].Range
1308}
1309
1310func findLabelSpecs(spec Spec) []string {
1311 maxIdx := -1
1312 var names map[int]string
1313
1314 var visit visitFunc
1315 visit = func(s Spec) {
1316 if ls, ok := s.(*BlockLabelSpec); ok {
1317 if maxIdx < ls.Index {
1318 maxIdx = ls.Index
1319 }
1320 if names == nil {
1321 names = make(map[int]string)
1322 }
1323 names[ls.Index] = ls.Name
1324 }
1325 s.visitSameBodyChildren(visit)
1326 }
1327
1328 visit(spec)
1329
1330 if maxIdx < 0 {
1331 return nil // no labels at all
1332 }
1333
1334 ret := make([]string, maxIdx+1)
1335 for i := range ret {
1336 name := names[i]
1337 if name == "" {
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)
1341 }
1342 ret[i] = name
1343 }
1344
1345 return ret
1346}
1347
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.
1350//
1351// The two specifications must have the same implied result type for correct
1352// operation. If not, the result is undefined.
107c1cdb
ND
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.
15c0b25d
AP
1363type DefaultSpec struct {
1364 Primary Spec
1365 Default Spec
1366}
1367
1368func (s *DefaultSpec) visitSameBodyChildren(cb visitFunc) {
1369 cb(s.Primary)
1370 cb(s.Default)
1371}
1372
1373func (s *DefaultSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
1374 val, diags := s.Primary.decode(content, blockLabels, ctx)
1375 if val.IsNull() {
1376 var moreDiags hcl.Diagnostics
1377 val, moreDiags = s.Default.decode(content, blockLabels, ctx)
1378 diags = append(diags, moreDiags...)
1379 }
1380 return val, diags
1381}
1382
1383func (s *DefaultSpec) impliedType() cty.Type {
1384 return s.Primary.impliedType()
1385}
1386
107c1cdb
ND
1387// attrSpec implementation
1388func (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
1402func (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
1412func (s *DefaultSpec) nestedSpec() Spec {
1413 if bs, ok := s.Primary.(blockSpec); ok {
1414 return bs.nestedSpec()
1415 }
1416 return nil
1417}
1418
15c0b25d
AP
1419func (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)
1425}
1426
1427// TransformExprSpec is a spec that wraps another and then evaluates a given
1428// hcl.Expression on the result.
1429//
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.
1434type TransformExprSpec struct {
1435 Wrapped Spec
1436 Expr hcl.Expression
1437 TransformCtx *hcl.EvalContext
1438 VarName string
1439}
1440
1441func (s *TransformExprSpec) visitSameBodyChildren(cb visitFunc) {
1442 cb(s.Wrapped)
1443}
1444
1445func (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
1450 // root cause.
1451 return cty.UnknownVal(s.impliedType()), diags
1452 }
1453
1454 chiCtx := s.TransformCtx.NewChild()
1455 chiCtx.Variables = map[string]cty.Value{
1456 s.VarName: wrappedVal,
1457 }
1458 resultVal, resultDiags := s.Expr.Value(chiCtx)
1459 diags = append(diags, resultDiags...)
1460 return resultVal, diags
1461}
1462
1463func (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),
1468 }
1469 resultVal, _ := s.Expr.Value(chiCtx)
1470 return resultVal.Type()
1471}
1472
1473func (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)
1477}
1478
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.
1482//
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.
1487//
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.
1492type TransformFuncSpec struct {
1493 Wrapped Spec
1494 Func function.Function
1495}
1496
1497func (s *TransformFuncSpec) visitSameBodyChildren(cb visitFunc) {
1498 cb(s.Wrapped)
1499}
1500
1501func (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
1506 // root cause.
1507 return cty.UnknownVal(s.impliedType()), diags
1508 }
1509
1510 resultVal, err := s.Func.Call([]cty.Value{wrappedVal})
1511 if err != nil {
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(),
1520 })
1521 return cty.UnknownVal(s.impliedType()), diags
1522 }
1523
1524 return resultVal, diags
1525}
1526
1527func (s *TransformFuncSpec) impliedType() cty.Type {
1528 wrappedTy := s.Wrapped.impliedType()
1529 resultTy, err := s.Func.ReturnType([]cty.Type{wrappedTy})
1530 if err != nil {
1531 // Should never happen with a correctly-configured spec
1532 return cty.DynamicPseudoType
1533 }
1534
1535 return resultTy
1536}
1537
1538func (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)
1542}
107c1cdb
ND
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.
1547type noopSpec struct {
1548}
1549
1550func (s noopSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
1551 return cty.NullVal(cty.DynamicPseudoType), nil
1552}
1553
1554func (s noopSpec) impliedType() cty.Type {
1555 return cty.DynamicPseudoType
1556}
1557
1558func (s noopSpec) visitSameBodyChildren(cb visitFunc) {
1559 // nothing to do
1560}
1561
1562func (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}