diff options
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/hcldec/spec.go')
-rw-r--r-- | vendor/github.com/hashicorp/hcl2/hcldec/spec.go | 998 |
1 files changed, 998 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/hcl2/hcldec/spec.go b/vendor/github.com/hashicorp/hcl2/hcldec/spec.go new file mode 100644 index 0000000..25cafcd --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hcldec/spec.go | |||
@@ -0,0 +1,998 @@ | |||
1 | package hcldec | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "fmt" | ||
6 | |||
7 | "github.com/hashicorp/hcl2/hcl" | ||
8 | "github.com/zclconf/go-cty/cty" | ||
9 | "github.com/zclconf/go-cty/cty/convert" | ||
10 | "github.com/zclconf/go-cty/cty/function" | ||
11 | ) | ||
12 | |||
13 | // A Spec is a description of how to decode a hcl.Body to a cty.Value. | ||
14 | // | ||
15 | // The various other types in this package whose names end in "Spec" are | ||
16 | // the spec implementations. The most common top-level spec is ObjectSpec, | ||
17 | // which decodes body content into a cty.Value of an object type. | ||
18 | type Spec interface { | ||
19 | // Perform the decode operation on the given body, in the context of | ||
20 | // the given block (which might be null), using the given eval context. | ||
21 | // | ||
22 | // "block" is provided only by the nested calls performed by the spec | ||
23 | // types that work on block bodies. | ||
24 | decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) | ||
25 | |||
26 | // Return the cty.Type that should be returned when decoding a body with | ||
27 | // this spec. | ||
28 | impliedType() cty.Type | ||
29 | |||
30 | // Call the given callback once for each of the nested specs that would | ||
31 | // get decoded with the same body and block as the receiver. This should | ||
32 | // not descend into the nested specs used when decoding blocks. | ||
33 | visitSameBodyChildren(cb visitFunc) | ||
34 | |||
35 | // Determine the source range of the value that would be returned for the | ||
36 | // spec in the given content, in the context of the given block | ||
37 | // (which might be null). If the corresponding item is missing, return | ||
38 | // a place where it might be inserted. | ||
39 | sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range | ||
40 | } | ||
41 | |||
42 | type visitFunc func(spec Spec) | ||
43 | |||
44 | // An ObjectSpec is a Spec that produces a cty.Value of an object type whose | ||
45 | // attributes correspond to the keys of the spec map. | ||
46 | type ObjectSpec map[string]Spec | ||
47 | |||
48 | // attrSpec is implemented by specs that require attributes from the body. | ||
49 | type attrSpec interface { | ||
50 | attrSchemata() []hcl.AttributeSchema | ||
51 | } | ||
52 | |||
53 | // blockSpec is implemented by specs that require blocks from the body. | ||
54 | type blockSpec interface { | ||
55 | blockHeaderSchemata() []hcl.BlockHeaderSchema | ||
56 | nestedSpec() Spec | ||
57 | } | ||
58 | |||
59 | // specNeedingVariables is implemented by specs that can use variables | ||
60 | // from the EvalContext, to declare which variables they need. | ||
61 | type specNeedingVariables interface { | ||
62 | variablesNeeded(content *hcl.BodyContent) []hcl.Traversal | ||
63 | } | ||
64 | |||
65 | func (s ObjectSpec) visitSameBodyChildren(cb visitFunc) { | ||
66 | for _, c := range s { | ||
67 | cb(c) | ||
68 | } | ||
69 | } | ||
70 | |||
71 | func (s ObjectSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
72 | vals := make(map[string]cty.Value, len(s)) | ||
73 | var diags hcl.Diagnostics | ||
74 | |||
75 | for k, spec := range s { | ||
76 | var kd hcl.Diagnostics | ||
77 | vals[k], kd = spec.decode(content, blockLabels, ctx) | ||
78 | diags = append(diags, kd...) | ||
79 | } | ||
80 | |||
81 | return cty.ObjectVal(vals), diags | ||
82 | } | ||
83 | |||
84 | func (s ObjectSpec) impliedType() cty.Type { | ||
85 | if len(s) == 0 { | ||
86 | return cty.EmptyObject | ||
87 | } | ||
88 | |||
89 | attrTypes := make(map[string]cty.Type) | ||
90 | for k, childSpec := range s { | ||
91 | attrTypes[k] = childSpec.impliedType() | ||
92 | } | ||
93 | return cty.Object(attrTypes) | ||
94 | } | ||
95 | |||
96 | func (s ObjectSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
97 | // This is not great, but the best we can do. In practice, it's rather | ||
98 | // strange to ask for the source range of an entire top-level body, since | ||
99 | // that's already readily available to the caller. | ||
100 | return content.MissingItemRange | ||
101 | } | ||
102 | |||
103 | // A TupleSpec is a Spec that produces a cty.Value of a tuple type whose | ||
104 | // elements correspond to the elements of the spec slice. | ||
105 | type TupleSpec []Spec | ||
106 | |||
107 | func (s TupleSpec) visitSameBodyChildren(cb visitFunc) { | ||
108 | for _, c := range s { | ||
109 | cb(c) | ||
110 | } | ||
111 | } | ||
112 | |||
113 | func (s TupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
114 | vals := make([]cty.Value, len(s)) | ||
115 | var diags hcl.Diagnostics | ||
116 | |||
117 | for i, spec := range s { | ||
118 | var ed hcl.Diagnostics | ||
119 | vals[i], ed = spec.decode(content, blockLabels, ctx) | ||
120 | diags = append(diags, ed...) | ||
121 | } | ||
122 | |||
123 | return cty.TupleVal(vals), diags | ||
124 | } | ||
125 | |||
126 | func (s TupleSpec) impliedType() cty.Type { | ||
127 | if len(s) == 0 { | ||
128 | return cty.EmptyTuple | ||
129 | } | ||
130 | |||
131 | attrTypes := make([]cty.Type, len(s)) | ||
132 | for i, childSpec := range s { | ||
133 | attrTypes[i] = childSpec.impliedType() | ||
134 | } | ||
135 | return cty.Tuple(attrTypes) | ||
136 | } | ||
137 | |||
138 | func (s TupleSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
139 | // This is not great, but the best we can do. In practice, it's rather | ||
140 | // strange to ask for the source range of an entire top-level body, since | ||
141 | // that's already readily available to the caller. | ||
142 | return content.MissingItemRange | ||
143 | } | ||
144 | |||
145 | // An AttrSpec is a Spec that evaluates a particular attribute expression in | ||
146 | // the body and returns its resulting value converted to the requested type, | ||
147 | // or produces a diagnostic if the type is incorrect. | ||
148 | type AttrSpec struct { | ||
149 | Name string | ||
150 | Type cty.Type | ||
151 | Required bool | ||
152 | } | ||
153 | |||
154 | func (s *AttrSpec) visitSameBodyChildren(cb visitFunc) { | ||
155 | // leaf node | ||
156 | } | ||
157 | |||
158 | // specNeedingVariables implementation | ||
159 | func (s *AttrSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { | ||
160 | attr, exists := content.Attributes[s.Name] | ||
161 | if !exists { | ||
162 | return nil | ||
163 | } | ||
164 | |||
165 | return attr.Expr.Variables() | ||
166 | } | ||
167 | |||
168 | // attrSpec implementation | ||
169 | func (s *AttrSpec) attrSchemata() []hcl.AttributeSchema { | ||
170 | return []hcl.AttributeSchema{ | ||
171 | { | ||
172 | Name: s.Name, | ||
173 | Required: s.Required, | ||
174 | }, | ||
175 | } | ||
176 | } | ||
177 | |||
178 | func (s *AttrSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
179 | attr, exists := content.Attributes[s.Name] | ||
180 | if !exists { | ||
181 | return content.MissingItemRange | ||
182 | } | ||
183 | |||
184 | return attr.Expr.Range() | ||
185 | } | ||
186 | |||
187 | func (s *AttrSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
188 | attr, exists := content.Attributes[s.Name] | ||
189 | if !exists { | ||
190 | // We don't need to check required and emit a diagnostic here, because | ||
191 | // that would already have happened when building "content". | ||
192 | return cty.NullVal(s.Type), nil | ||
193 | } | ||
194 | |||
195 | val, diags := attr.Expr.Value(ctx) | ||
196 | |||
197 | convVal, err := convert.Convert(val, s.Type) | ||
198 | if err != nil { | ||
199 | diags = append(diags, &hcl.Diagnostic{ | ||
200 | Severity: hcl.DiagError, | ||
201 | Summary: "Incorrect attribute value type", | ||
202 | Detail: fmt.Sprintf( | ||
203 | "Inappropriate value for attribute %q: %s.", | ||
204 | s.Name, err.Error(), | ||
205 | ), | ||
206 | Subject: attr.Expr.StartRange().Ptr(), | ||
207 | Context: hcl.RangeBetween(attr.NameRange, attr.Expr.StartRange()).Ptr(), | ||
208 | }) | ||
209 | // We'll return an unknown value of the _correct_ type so that the | ||
210 | // incomplete result can still be used for some analysis use-cases. | ||
211 | val = cty.UnknownVal(s.Type) | ||
212 | } else { | ||
213 | val = convVal | ||
214 | } | ||
215 | |||
216 | return val, diags | ||
217 | } | ||
218 | |||
219 | func (s *AttrSpec) impliedType() cty.Type { | ||
220 | return s.Type | ||
221 | } | ||
222 | |||
223 | // A LiteralSpec is a Spec that produces the given literal value, ignoring | ||
224 | // the given body. | ||
225 | type LiteralSpec struct { | ||
226 | Value cty.Value | ||
227 | } | ||
228 | |||
229 | func (s *LiteralSpec) visitSameBodyChildren(cb visitFunc) { | ||
230 | // leaf node | ||
231 | } | ||
232 | |||
233 | func (s *LiteralSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
234 | return s.Value, nil | ||
235 | } | ||
236 | |||
237 | func (s *LiteralSpec) impliedType() cty.Type { | ||
238 | return s.Value.Type() | ||
239 | } | ||
240 | |||
241 | func (s *LiteralSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
242 | // No sensible range to return for a literal, so the caller had better | ||
243 | // ensure it doesn't cause any diagnostics. | ||
244 | return hcl.Range{ | ||
245 | Filename: "<unknown>", | ||
246 | } | ||
247 | } | ||
248 | |||
249 | // An ExprSpec is a Spec that evaluates the given expression, ignoring the | ||
250 | // given body. | ||
251 | type ExprSpec struct { | ||
252 | Expr hcl.Expression | ||
253 | } | ||
254 | |||
255 | func (s *ExprSpec) visitSameBodyChildren(cb visitFunc) { | ||
256 | // leaf node | ||
257 | } | ||
258 | |||
259 | // specNeedingVariables implementation | ||
260 | func (s *ExprSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { | ||
261 | return s.Expr.Variables() | ||
262 | } | ||
263 | |||
264 | func (s *ExprSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
265 | return s.Expr.Value(ctx) | ||
266 | } | ||
267 | |||
268 | func (s *ExprSpec) impliedType() cty.Type { | ||
269 | // We can't know the type of our expression until we evaluate it | ||
270 | return cty.DynamicPseudoType | ||
271 | } | ||
272 | |||
273 | func (s *ExprSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
274 | return s.Expr.Range() | ||
275 | } | ||
276 | |||
277 | // A BlockSpec is a Spec that produces a cty.Value by decoding the contents | ||
278 | // of a single nested block of a given type, using a nested spec. | ||
279 | // | ||
280 | // If the Required flag is not set, the nested block may be omitted, in which | ||
281 | // case a null value is produced. If it _is_ set, an error diagnostic is | ||
282 | // produced if there are no nested blocks of the given type. | ||
283 | type BlockSpec struct { | ||
284 | TypeName string | ||
285 | Nested Spec | ||
286 | Required bool | ||
287 | } | ||
288 | |||
289 | func (s *BlockSpec) visitSameBodyChildren(cb visitFunc) { | ||
290 | // leaf node ("Nested" does not use the same body) | ||
291 | } | ||
292 | |||
293 | // blockSpec implementation | ||
294 | func (s *BlockSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { | ||
295 | return []hcl.BlockHeaderSchema{ | ||
296 | { | ||
297 | Type: s.TypeName, | ||
298 | LabelNames: findLabelSpecs(s.Nested), | ||
299 | }, | ||
300 | } | ||
301 | } | ||
302 | |||
303 | // blockSpec implementation | ||
304 | func (s *BlockSpec) nestedSpec() Spec { | ||
305 | return s.Nested | ||
306 | } | ||
307 | |||
308 | // specNeedingVariables implementation | ||
309 | func (s *BlockSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { | ||
310 | var childBlock *hcl.Block | ||
311 | for _, candidate := range content.Blocks { | ||
312 | if candidate.Type != s.TypeName { | ||
313 | continue | ||
314 | } | ||
315 | |||
316 | childBlock = candidate | ||
317 | break | ||
318 | } | ||
319 | |||
320 | if childBlock == nil { | ||
321 | return nil | ||
322 | } | ||
323 | |||
324 | return Variables(childBlock.Body, s.Nested) | ||
325 | } | ||
326 | |||
327 | func (s *BlockSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
328 | var diags hcl.Diagnostics | ||
329 | |||
330 | var childBlock *hcl.Block | ||
331 | for _, candidate := range content.Blocks { | ||
332 | if candidate.Type != s.TypeName { | ||
333 | continue | ||
334 | } | ||
335 | |||
336 | if childBlock != nil { | ||
337 | diags = append(diags, &hcl.Diagnostic{ | ||
338 | Severity: hcl.DiagError, | ||
339 | Summary: fmt.Sprintf("Duplicate %s block", s.TypeName), | ||
340 | Detail: fmt.Sprintf( | ||
341 | "Only one block of type %q is allowed. Previous definition was at %s.", | ||
342 | s.TypeName, childBlock.DefRange.String(), | ||
343 | ), | ||
344 | Subject: &candidate.DefRange, | ||
345 | }) | ||
346 | break | ||
347 | } | ||
348 | |||
349 | childBlock = candidate | ||
350 | } | ||
351 | |||
352 | if childBlock == nil { | ||
353 | if s.Required { | ||
354 | diags = append(diags, &hcl.Diagnostic{ | ||
355 | Severity: hcl.DiagError, | ||
356 | Summary: fmt.Sprintf("Missing %s block", s.TypeName), | ||
357 | Detail: fmt.Sprintf( | ||
358 | "A block of type %q is required here.", s.TypeName, | ||
359 | ), | ||
360 | Subject: &content.MissingItemRange, | ||
361 | }) | ||
362 | } | ||
363 | return cty.NullVal(s.Nested.impliedType()), diags | ||
364 | } | ||
365 | |||
366 | if s.Nested == nil { | ||
367 | panic("BlockSpec with no Nested Spec") | ||
368 | } | ||
369 | val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false) | ||
370 | diags = append(diags, childDiags...) | ||
371 | return val, diags | ||
372 | } | ||
373 | |||
374 | func (s *BlockSpec) impliedType() cty.Type { | ||
375 | return s.Nested.impliedType() | ||
376 | } | ||
377 | |||
378 | func (s *BlockSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
379 | var childBlock *hcl.Block | ||
380 | for _, candidate := range content.Blocks { | ||
381 | if candidate.Type != s.TypeName { | ||
382 | continue | ||
383 | } | ||
384 | |||
385 | childBlock = candidate | ||
386 | break | ||
387 | } | ||
388 | |||
389 | if childBlock == nil { | ||
390 | return content.MissingItemRange | ||
391 | } | ||
392 | |||
393 | return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) | ||
394 | } | ||
395 | |||
396 | // A BlockListSpec is a Spec that produces a cty list of the results of | ||
397 | // decoding all of the nested blocks of a given type, using a nested spec. | ||
398 | type BlockListSpec struct { | ||
399 | TypeName string | ||
400 | Nested Spec | ||
401 | MinItems int | ||
402 | MaxItems int | ||
403 | } | ||
404 | |||
405 | func (s *BlockListSpec) visitSameBodyChildren(cb visitFunc) { | ||
406 | // leaf node ("Nested" does not use the same body) | ||
407 | } | ||
408 | |||
409 | // blockSpec implementation | ||
410 | func (s *BlockListSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { | ||
411 | return []hcl.BlockHeaderSchema{ | ||
412 | { | ||
413 | Type: s.TypeName, | ||
414 | LabelNames: findLabelSpecs(s.Nested), | ||
415 | }, | ||
416 | } | ||
417 | } | ||
418 | |||
419 | // blockSpec implementation | ||
420 | func (s *BlockListSpec) nestedSpec() Spec { | ||
421 | return s.Nested | ||
422 | } | ||
423 | |||
424 | // specNeedingVariables implementation | ||
425 | func (s *BlockListSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { | ||
426 | var ret []hcl.Traversal | ||
427 | |||
428 | for _, childBlock := range content.Blocks { | ||
429 | if childBlock.Type != s.TypeName { | ||
430 | continue | ||
431 | } | ||
432 | |||
433 | ret = append(ret, Variables(childBlock.Body, s.Nested)...) | ||
434 | } | ||
435 | |||
436 | return ret | ||
437 | } | ||
438 | |||
439 | func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
440 | var diags hcl.Diagnostics | ||
441 | |||
442 | if s.Nested == nil { | ||
443 | panic("BlockListSpec with no Nested Spec") | ||
444 | } | ||
445 | |||
446 | var elems []cty.Value | ||
447 | var sourceRanges []hcl.Range | ||
448 | for _, childBlock := range content.Blocks { | ||
449 | if childBlock.Type != s.TypeName { | ||
450 | continue | ||
451 | } | ||
452 | |||
453 | val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false) | ||
454 | diags = append(diags, childDiags...) | ||
455 | elems = append(elems, val) | ||
456 | sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)) | ||
457 | } | ||
458 | |||
459 | if len(elems) < s.MinItems { | ||
460 | diags = append(diags, &hcl.Diagnostic{ | ||
461 | Severity: hcl.DiagError, | ||
462 | Summary: fmt.Sprintf("Insufficient %s blocks", s.TypeName), | ||
463 | Detail: fmt.Sprintf("At least %d %q blocks are required.", s.MinItems, s.TypeName), | ||
464 | Subject: &content.MissingItemRange, | ||
465 | }) | ||
466 | } else if s.MaxItems > 0 && len(elems) > s.MaxItems { | ||
467 | diags = append(diags, &hcl.Diagnostic{ | ||
468 | Severity: hcl.DiagError, | ||
469 | Summary: fmt.Sprintf("Too many %s blocks", s.TypeName), | ||
470 | Detail: fmt.Sprintf("No more than %d %q blocks are allowed", s.MaxItems, s.TypeName), | ||
471 | Subject: &sourceRanges[s.MaxItems], | ||
472 | }) | ||
473 | } | ||
474 | |||
475 | var ret cty.Value | ||
476 | |||
477 | if len(elems) == 0 { | ||
478 | ret = cty.ListValEmpty(s.Nested.impliedType()) | ||
479 | } else { | ||
480 | ret = cty.ListVal(elems) | ||
481 | } | ||
482 | |||
483 | return ret, diags | ||
484 | } | ||
485 | |||
486 | func (s *BlockListSpec) impliedType() cty.Type { | ||
487 | return cty.List(s.Nested.impliedType()) | ||
488 | } | ||
489 | |||
490 | func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
491 | // We return the source range of the _first_ block of the given type, | ||
492 | // since they are not guaranteed to form a contiguous range. | ||
493 | |||
494 | var childBlock *hcl.Block | ||
495 | for _, candidate := range content.Blocks { | ||
496 | if candidate.Type != s.TypeName { | ||
497 | continue | ||
498 | } | ||
499 | |||
500 | childBlock = candidate | ||
501 | break | ||
502 | } | ||
503 | |||
504 | if childBlock == nil { | ||
505 | return content.MissingItemRange | ||
506 | } | ||
507 | |||
508 | return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) | ||
509 | } | ||
510 | |||
511 | // 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. | ||
513 | type BlockSetSpec struct { | ||
514 | TypeName string | ||
515 | Nested Spec | ||
516 | MinItems int | ||
517 | MaxItems int | ||
518 | } | ||
519 | |||
520 | func (s *BlockSetSpec) visitSameBodyChildren(cb visitFunc) { | ||
521 | // leaf node ("Nested" does not use the same body) | ||
522 | } | ||
523 | |||
524 | // blockSpec implementation | ||
525 | func (s *BlockSetSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { | ||
526 | return []hcl.BlockHeaderSchema{ | ||
527 | { | ||
528 | Type: s.TypeName, | ||
529 | LabelNames: findLabelSpecs(s.Nested), | ||
530 | }, | ||
531 | } | ||
532 | } | ||
533 | |||
534 | // blockSpec implementation | ||
535 | func (s *BlockSetSpec) nestedSpec() Spec { | ||
536 | return s.Nested | ||
537 | } | ||
538 | |||
539 | // specNeedingVariables implementation | ||
540 | func (s *BlockSetSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { | ||
541 | var ret []hcl.Traversal | ||
542 | |||
543 | for _, childBlock := range content.Blocks { | ||
544 | if childBlock.Type != s.TypeName { | ||
545 | continue | ||
546 | } | ||
547 | |||
548 | ret = append(ret, Variables(childBlock.Body, s.Nested)...) | ||
549 | } | ||
550 | |||
551 | return ret | ||
552 | } | ||
553 | |||
554 | func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
555 | var diags hcl.Diagnostics | ||
556 | |||
557 | if s.Nested == nil { | ||
558 | panic("BlockSetSpec with no Nested Spec") | ||
559 | } | ||
560 | |||
561 | var elems []cty.Value | ||
562 | var sourceRanges []hcl.Range | ||
563 | for _, childBlock := range content.Blocks { | ||
564 | if childBlock.Type != s.TypeName { | ||
565 | continue | ||
566 | } | ||
567 | |||
568 | val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false) | ||
569 | diags = append(diags, childDiags...) | ||
570 | elems = append(elems, val) | ||
571 | sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)) | ||
572 | } | ||
573 | |||
574 | if len(elems) < s.MinItems { | ||
575 | diags = append(diags, &hcl.Diagnostic{ | ||
576 | Severity: hcl.DiagError, | ||
577 | Summary: fmt.Sprintf("Insufficient %s blocks", s.TypeName), | ||
578 | Detail: fmt.Sprintf("At least %d %q blocks are required.", s.MinItems, s.TypeName), | ||
579 | Subject: &content.MissingItemRange, | ||
580 | }) | ||
581 | } else if s.MaxItems > 0 && len(elems) > s.MaxItems { | ||
582 | diags = append(diags, &hcl.Diagnostic{ | ||
583 | Severity: hcl.DiagError, | ||
584 | Summary: fmt.Sprintf("Too many %s blocks", s.TypeName), | ||
585 | Detail: fmt.Sprintf("No more than %d %q blocks are allowed", s.MaxItems, s.TypeName), | ||
586 | Subject: &sourceRanges[s.MaxItems], | ||
587 | }) | ||
588 | } | ||
589 | |||
590 | var ret cty.Value | ||
591 | |||
592 | if len(elems) == 0 { | ||
593 | ret = cty.SetValEmpty(s.Nested.impliedType()) | ||
594 | } else { | ||
595 | ret = cty.SetVal(elems) | ||
596 | } | ||
597 | |||
598 | return ret, diags | ||
599 | } | ||
600 | |||
601 | func (s *BlockSetSpec) impliedType() cty.Type { | ||
602 | return cty.Set(s.Nested.impliedType()) | ||
603 | } | ||
604 | |||
605 | func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
606 | // We return the source range of the _first_ block of the given type, | ||
607 | // since they are not guaranteed to form a contiguous range. | ||
608 | |||
609 | var childBlock *hcl.Block | ||
610 | for _, candidate := range content.Blocks { | ||
611 | if candidate.Type != s.TypeName { | ||
612 | continue | ||
613 | } | ||
614 | |||
615 | childBlock = candidate | ||
616 | break | ||
617 | } | ||
618 | |||
619 | if childBlock == nil { | ||
620 | return content.MissingItemRange | ||
621 | } | ||
622 | |||
623 | return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) | ||
624 | } | ||
625 | |||
626 | // A BlockMapSpec is a Spec that produces a cty map of the results of | ||
627 | // decoding all of the nested blocks of a given type, using a nested spec. | ||
628 | // | ||
629 | // One level of map structure is created for each of the given label names. | ||
630 | // There must be at least one given label name. | ||
631 | type BlockMapSpec struct { | ||
632 | TypeName string | ||
633 | LabelNames []string | ||
634 | Nested Spec | ||
635 | } | ||
636 | |||
637 | func (s *BlockMapSpec) visitSameBodyChildren(cb visitFunc) { | ||
638 | // leaf node ("Nested" does not use the same body) | ||
639 | } | ||
640 | |||
641 | // blockSpec implementation | ||
642 | func (s *BlockMapSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema { | ||
643 | return []hcl.BlockHeaderSchema{ | ||
644 | { | ||
645 | Type: s.TypeName, | ||
646 | LabelNames: append(s.LabelNames, findLabelSpecs(s.Nested)...), | ||
647 | }, | ||
648 | } | ||
649 | } | ||
650 | |||
651 | // blockSpec implementation | ||
652 | func (s *BlockMapSpec) nestedSpec() Spec { | ||
653 | return s.Nested | ||
654 | } | ||
655 | |||
656 | // specNeedingVariables implementation | ||
657 | func (s *BlockMapSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { | ||
658 | var ret []hcl.Traversal | ||
659 | |||
660 | for _, childBlock := range content.Blocks { | ||
661 | if childBlock.Type != s.TypeName { | ||
662 | continue | ||
663 | } | ||
664 | |||
665 | ret = append(ret, Variables(childBlock.Body, s.Nested)...) | ||
666 | } | ||
667 | |||
668 | return ret | ||
669 | } | ||
670 | |||
671 | func (s *BlockMapSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
672 | var diags hcl.Diagnostics | ||
673 | |||
674 | if s.Nested == nil { | ||
675 | panic("BlockSetSpec with no Nested Spec") | ||
676 | } | ||
677 | |||
678 | elems := map[string]interface{}{} | ||
679 | for _, childBlock := range content.Blocks { | ||
680 | if childBlock.Type != s.TypeName { | ||
681 | continue | ||
682 | } | ||
683 | |||
684 | childLabels := labelsForBlock(childBlock) | ||
685 | val, _, childDiags := decode(childBlock.Body, childLabels[len(s.LabelNames):], ctx, s.Nested, false) | ||
686 | targetMap := elems | ||
687 | for _, key := range childBlock.Labels[:len(s.LabelNames)-1] { | ||
688 | if _, exists := targetMap[key]; !exists { | ||
689 | targetMap[key] = make(map[string]interface{}) | ||
690 | } | ||
691 | targetMap = targetMap[key].(map[string]interface{}) | ||
692 | } | ||
693 | |||
694 | diags = append(diags, childDiags...) | ||
695 | |||
696 | key := childBlock.Labels[len(s.LabelNames)-1] | ||
697 | if _, exists := targetMap[key]; exists { | ||
698 | labelsBuf := bytes.Buffer{} | ||
699 | for _, label := range childBlock.Labels { | ||
700 | fmt.Fprintf(&labelsBuf, " %q", label) | ||
701 | } | ||
702 | diags = append(diags, &hcl.Diagnostic{ | ||
703 | Severity: hcl.DiagError, | ||
704 | Summary: fmt.Sprintf("Duplicate %s block", s.TypeName), | ||
705 | Detail: fmt.Sprintf( | ||
706 | "A block for %s%s was already defined. The %s labels must be unique.", | ||
707 | s.TypeName, labelsBuf.String(), s.TypeName, | ||
708 | ), | ||
709 | Subject: &childBlock.DefRange, | ||
710 | }) | ||
711 | continue | ||
712 | } | ||
713 | |||
714 | targetMap[key] = val | ||
715 | } | ||
716 | |||
717 | if len(elems) == 0 { | ||
718 | return cty.MapValEmpty(s.Nested.impliedType()), diags | ||
719 | } | ||
720 | |||
721 | var ctyMap func(map[string]interface{}, int) cty.Value | ||
722 | ctyMap = func(raw map[string]interface{}, depth int) cty.Value { | ||
723 | vals := make(map[string]cty.Value, len(raw)) | ||
724 | if depth == 1 { | ||
725 | for k, v := range raw { | ||
726 | vals[k] = v.(cty.Value) | ||
727 | } | ||
728 | } else { | ||
729 | for k, v := range raw { | ||
730 | vals[k] = ctyMap(v.(map[string]interface{}), depth-1) | ||
731 | } | ||
732 | } | ||
733 | return cty.MapVal(vals) | ||
734 | } | ||
735 | |||
736 | return ctyMap(elems, len(s.LabelNames)), diags | ||
737 | } | ||
738 | |||
739 | func (s *BlockMapSpec) impliedType() cty.Type { | ||
740 | ret := s.Nested.impliedType() | ||
741 | for _ = range s.LabelNames { | ||
742 | ret = cty.Map(ret) | ||
743 | } | ||
744 | return ret | ||
745 | } | ||
746 | |||
747 | func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
748 | // We return the source range of the _first_ block of the given type, | ||
749 | // since they are not guaranteed to form a contiguous range. | ||
750 | |||
751 | var childBlock *hcl.Block | ||
752 | for _, candidate := range content.Blocks { | ||
753 | if candidate.Type != s.TypeName { | ||
754 | continue | ||
755 | } | ||
756 | |||
757 | childBlock = candidate | ||
758 | break | ||
759 | } | ||
760 | |||
761 | if childBlock == nil { | ||
762 | return content.MissingItemRange | ||
763 | } | ||
764 | |||
765 | return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested) | ||
766 | } | ||
767 | |||
768 | // 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 | ||
770 | // belongs to a block. It is a programming error to use this in a non-block | ||
771 | // context, so this spec will panic in that case. | ||
772 | // | ||
773 | // This spec only works in the nested spec within a BlockSpec, BlockListSpec, | ||
774 | // BlockSetSpec or BlockMapSpec. | ||
775 | // | ||
776 | // The full set of label specs used against a particular block must have a | ||
777 | // consecutive set of indices starting at zero. The maximum index found | ||
778 | // defines how many labels the corresponding blocks must have in cty source. | ||
779 | type BlockLabelSpec struct { | ||
780 | Index int | ||
781 | Name string | ||
782 | } | ||
783 | |||
784 | func (s *BlockLabelSpec) visitSameBodyChildren(cb visitFunc) { | ||
785 | // leaf node | ||
786 | } | ||
787 | |||
788 | func (s *BlockLabelSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
789 | if s.Index >= len(blockLabels) { | ||
790 | panic("BlockListSpec used in non-block context") | ||
791 | } | ||
792 | |||
793 | return cty.StringVal(blockLabels[s.Index].Value), nil | ||
794 | } | ||
795 | |||
796 | func (s *BlockLabelSpec) impliedType() cty.Type { | ||
797 | return cty.String // labels are always strings | ||
798 | } | ||
799 | |||
800 | func (s *BlockLabelSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
801 | if s.Index >= len(blockLabels) { | ||
802 | panic("BlockListSpec used in non-block context") | ||
803 | } | ||
804 | |||
805 | return blockLabels[s.Index].Range | ||
806 | } | ||
807 | |||
808 | func findLabelSpecs(spec Spec) []string { | ||
809 | maxIdx := -1 | ||
810 | var names map[int]string | ||
811 | |||
812 | var visit visitFunc | ||
813 | visit = func(s Spec) { | ||
814 | if ls, ok := s.(*BlockLabelSpec); ok { | ||
815 | if maxIdx < ls.Index { | ||
816 | maxIdx = ls.Index | ||
817 | } | ||
818 | if names == nil { | ||
819 | names = make(map[int]string) | ||
820 | } | ||
821 | names[ls.Index] = ls.Name | ||
822 | } | ||
823 | s.visitSameBodyChildren(visit) | ||
824 | } | ||
825 | |||
826 | visit(spec) | ||
827 | |||
828 | if maxIdx < 0 { | ||
829 | return nil // no labels at all | ||
830 | } | ||
831 | |||
832 | ret := make([]string, maxIdx+1) | ||
833 | for i := range ret { | ||
834 | name := names[i] | ||
835 | if name == "" { | ||
836 | // Should never happen if the spec is conformant, since we require | ||
837 | // consecutive indices starting at zero. | ||
838 | name = fmt.Sprintf("missing%02d", i) | ||
839 | } | ||
840 | ret[i] = name | ||
841 | } | ||
842 | |||
843 | return ret | ||
844 | } | ||
845 | |||
846 | // DefaultSpec is a spec that wraps two specs, evaluating the primary first | ||
847 | // and then evaluating the default if the primary returns a null value. | ||
848 | // | ||
849 | // The two specifications must have the same implied result type for correct | ||
850 | // operation. If not, the result is undefined. | ||
851 | type DefaultSpec struct { | ||
852 | Primary Spec | ||
853 | Default Spec | ||
854 | } | ||
855 | |||
856 | func (s *DefaultSpec) visitSameBodyChildren(cb visitFunc) { | ||
857 | cb(s.Primary) | ||
858 | cb(s.Default) | ||
859 | } | ||
860 | |||
861 | func (s *DefaultSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
862 | val, diags := s.Primary.decode(content, blockLabels, ctx) | ||
863 | if val.IsNull() { | ||
864 | var moreDiags hcl.Diagnostics | ||
865 | val, moreDiags = s.Default.decode(content, blockLabels, ctx) | ||
866 | diags = append(diags, moreDiags...) | ||
867 | } | ||
868 | return val, diags | ||
869 | } | ||
870 | |||
871 | func (s *DefaultSpec) impliedType() cty.Type { | ||
872 | return s.Primary.impliedType() | ||
873 | } | ||
874 | |||
875 | 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 | ||
877 | // in our result, so we'll just assume the first. This is usually the right | ||
878 | // choice because the default is often a literal spec that doesn't have a | ||
879 | // reasonable source range to return anyway. | ||
880 | return s.Primary.sourceRange(content, blockLabels) | ||
881 | } | ||
882 | |||
883 | // TransformExprSpec is a spec that wraps another and then evaluates a given | ||
884 | // hcl.Expression on the result. | ||
885 | // | ||
886 | // The implied type of this spec is determined by evaluating the expression | ||
887 | // with an unknown value of the nested spec's implied type, which may cause | ||
888 | // the result to be imprecise. This spec should not be used in situations where | ||
889 | // precise result type information is needed. | ||
890 | type TransformExprSpec struct { | ||
891 | Wrapped Spec | ||
892 | Expr hcl.Expression | ||
893 | TransformCtx *hcl.EvalContext | ||
894 | VarName string | ||
895 | } | ||
896 | |||
897 | func (s *TransformExprSpec) visitSameBodyChildren(cb visitFunc) { | ||
898 | cb(s.Wrapped) | ||
899 | } | ||
900 | |||
901 | func (s *TransformExprSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
902 | wrappedVal, diags := s.Wrapped.decode(content, blockLabels, ctx) | ||
903 | if diags.HasErrors() { | ||
904 | // We won't try to run our function in this case, because it'll probably | ||
905 | // generate confusing additional errors that will distract from the | ||
906 | // root cause. | ||
907 | return cty.UnknownVal(s.impliedType()), diags | ||
908 | } | ||
909 | |||
910 | chiCtx := s.TransformCtx.NewChild() | ||
911 | chiCtx.Variables = map[string]cty.Value{ | ||
912 | s.VarName: wrappedVal, | ||
913 | } | ||
914 | resultVal, resultDiags := s.Expr.Value(chiCtx) | ||
915 | diags = append(diags, resultDiags...) | ||
916 | return resultVal, diags | ||
917 | } | ||
918 | |||
919 | func (s *TransformExprSpec) impliedType() cty.Type { | ||
920 | wrappedTy := s.Wrapped.impliedType() | ||
921 | chiCtx := s.TransformCtx.NewChild() | ||
922 | chiCtx.Variables = map[string]cty.Value{ | ||
923 | s.VarName: cty.UnknownVal(wrappedTy), | ||
924 | } | ||
925 | resultVal, _ := s.Expr.Value(chiCtx) | ||
926 | return resultVal.Type() | ||
927 | } | ||
928 | |||
929 | func (s *TransformExprSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
930 | // We'll just pass through our wrapped range here, even though that's | ||
931 | // not super-accurate, because there's nothing better to return. | ||
932 | return s.Wrapped.sourceRange(content, blockLabels) | ||
933 | } | ||
934 | |||
935 | // TransformFuncSpec is a spec that wraps another and then evaluates a given | ||
936 | // cty function with the result. The given function must expect exactly one | ||
937 | // argument, where the result of the wrapped spec will be passed. | ||
938 | // | ||
939 | // The implied type of this spec is determined by type-checking the function | ||
940 | // with an unknown value of the nested spec's implied type, which may cause | ||
941 | // the result to be imprecise. This spec should not be used in situations where | ||
942 | // precise result type information is needed. | ||
943 | // | ||
944 | // If the given function produces an error when run, this spec will produce | ||
945 | // a non-user-actionable diagnostic message. It's the caller's responsibility | ||
946 | // to ensure that the given function cannot fail for any non-error result | ||
947 | // of the wrapped spec. | ||
948 | type TransformFuncSpec struct { | ||
949 | Wrapped Spec | ||
950 | Func function.Function | ||
951 | } | ||
952 | |||
953 | func (s *TransformFuncSpec) visitSameBodyChildren(cb visitFunc) { | ||
954 | cb(s.Wrapped) | ||
955 | } | ||
956 | |||
957 | func (s *TransformFuncSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
958 | wrappedVal, diags := s.Wrapped.decode(content, blockLabels, ctx) | ||
959 | if diags.HasErrors() { | ||
960 | // We won't try to run our function in this case, because it'll probably | ||
961 | // generate confusing additional errors that will distract from the | ||
962 | // root cause. | ||
963 | return cty.UnknownVal(s.impliedType()), diags | ||
964 | } | ||
965 | |||
966 | resultVal, err := s.Func.Call([]cty.Value{wrappedVal}) | ||
967 | if err != nil { | ||
968 | // This is not a good example of a diagnostic because it is reporting | ||
969 | // a programming error in the calling application, rather than something | ||
970 | // an end-user could act on. | ||
971 | diags = append(diags, &hcl.Diagnostic{ | ||
972 | Severity: hcl.DiagError, | ||
973 | Summary: "Transform function failed", | ||
974 | Detail: fmt.Sprintf("Decoder transform returned an error: %s", err), | ||
975 | Subject: s.sourceRange(content, blockLabels).Ptr(), | ||
976 | }) | ||
977 | return cty.UnknownVal(s.impliedType()), diags | ||
978 | } | ||
979 | |||
980 | return resultVal, diags | ||
981 | } | ||
982 | |||
983 | func (s *TransformFuncSpec) impliedType() cty.Type { | ||
984 | wrappedTy := s.Wrapped.impliedType() | ||
985 | resultTy, err := s.Func.ReturnType([]cty.Type{wrappedTy}) | ||
986 | if err != nil { | ||
987 | // Should never happen with a correctly-configured spec | ||
988 | return cty.DynamicPseudoType | ||
989 | } | ||
990 | |||
991 | return resultTy | ||
992 | } | ||
993 | |||
994 | func (s *TransformFuncSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | ||
995 | // We'll just pass through our wrapped range here, even though that's | ||
996 | // not super-accurate, because there's nothing better to return. | ||
997 | return s.Wrapped.sourceRange(content, blockLabels) | ||
998 | } | ||