]>
Commit | Line | Data |
---|---|---|
15c0b25d AP |
1 | package hcldec |
2 | ||
3 | import ( | |
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. | |
19 | type 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 | ||
43 | type 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. | |
47 | type ObjectSpec map[string]Spec | |
48 | ||
49 | // attrSpec is implemented by specs that require attributes from the body. | |
50 | type attrSpec interface { | |
51 | attrSchemata() []hcl.AttributeSchema | |
52 | } | |
53 | ||
54 | // blockSpec is implemented by specs that require blocks from the body. | |
55 | type 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. | |
62 | type specNeedingVariables interface { | |
63 | variablesNeeded(content *hcl.BodyContent) []hcl.Traversal | |
64 | } | |
65 | ||
66 | func (s ObjectSpec) visitSameBodyChildren(cb visitFunc) { | |
67 | for _, c := range s { | |
68 | cb(c) | |
69 | } | |
70 | } | |
71 | ||
72 | func (s ObjectSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | |
73 | vals := make(map[string]cty.Value, len(s)) | |
74 | var diags hcl.Diagnostics | |
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 | ||
85 | func (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 | ||
97 | func (s ObjectSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | |
98 | // This is not great, but the best we can do. In practice, it's rather | |
99 | // strange to ask for the source range of an entire top-level body, since | |
100 | // that's already readily available to the caller. | |
101 | return content.MissingItemRange | |
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. | |
106 | type TupleSpec []Spec | |
107 | ||
108 | func (s TupleSpec) visitSameBodyChildren(cb visitFunc) { | |
109 | for _, c := range s { | |
110 | cb(c) | |
111 | } | |
112 | } | |
113 | ||
114 | func (s TupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | |
115 | vals := make([]cty.Value, len(s)) | |
116 | var diags hcl.Diagnostics | |
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 | ||
127 | func (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 | ||
139 | func (s TupleSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | |
140 | // This is not great, but the best we can do. In practice, it's rather | |
141 | // strange to ask for the source range of an entire top-level body, since | |
142 | // that's already readily available to the caller. | |
143 | return content.MissingItemRange | |
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. | |
149 | type AttrSpec struct { | |
150 | Name string | |
151 | Type cty.Type | |
152 | Required bool | |
153 | } | |
154 | ||
155 | func (s *AttrSpec) visitSameBodyChildren(cb visitFunc) { | |
156 | // leaf node | |
157 | } | |
158 | ||
159 | // specNeedingVariables implementation | |
160 | func (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 | |
170 | func (s *AttrSpec) attrSchemata() []hcl.AttributeSchema { | |
171 | return []hcl.AttributeSchema{ | |
172 | { | |
173 | Name: s.Name, | |
174 | Required: s.Required, | |
175 | }, | |
176 | } | |
177 | } | |
178 | ||
179 | func (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 | ||
188 | func (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 | ||
220 | func (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. | |
226 | type LiteralSpec struct { | |
227 | Value cty.Value | |
228 | } | |
229 | ||
230 | func (s *LiteralSpec) visitSameBodyChildren(cb visitFunc) { | |
231 | // leaf node | |
232 | } | |
233 | ||
234 | func (s *LiteralSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | |
235 | return s.Value, nil | |
236 | } | |
237 | ||
238 | func (s *LiteralSpec) impliedType() cty.Type { | |
239 | return s.Value.Type() | |
240 | } | |
241 | ||
242 | func (s *LiteralSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | |
243 | // No sensible range to return for a literal, so the caller had better | |
244 | // ensure it doesn't cause any diagnostics. | |
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. | |
252 | type ExprSpec struct { | |
253 | Expr hcl.Expression | |
254 | } | |
255 | ||
256 | func (s *ExprSpec) visitSameBodyChildren(cb visitFunc) { | |
257 | // leaf node | |
258 | } | |
259 | ||
260 | // specNeedingVariables implementation | |
261 | func (s *ExprSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { | |
262 | return s.Expr.Variables() | |
263 | } | |
264 | ||
265 | func (s *ExprSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | |
266 | return s.Expr.Value(ctx) | |
267 | } | |
268 | ||
269 | func (s *ExprSpec) impliedType() cty.Type { | |
270 | // We can't know the type of our expression until we evaluate it | |
271 | return cty.DynamicPseudoType | |
272 | } | |
273 | ||
274 | func (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. | |
284 | type BlockSpec struct { | |
285 | TypeName string | |
286 | Nested Spec | |
287 | Required bool | |
288 | } | |
289 | ||
290 | func (s *BlockSpec) visitSameBodyChildren(cb visitFunc) { | |
291 | // leaf node ("Nested" does not use the same body) | |
292 | } | |
293 | ||
294 | // blockSpec implementation | |
295 | func (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 | |
305 | func (s *BlockSpec) nestedSpec() Spec { | |
306 | return s.Nested | |
307 | } | |
308 | ||
309 | // specNeedingVariables implementation | |
310 | func (s *BlockSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal { | |
311 | var childBlock *hcl.Block | |
312 | for _, candidate := range content.Blocks { | |
313 | if candidate.Type != s.TypeName { | |
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 | ||
328 | func (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 | ||
375 | func (s *BlockSpec) impliedType() cty.Type { | |
376 | return s.Nested.impliedType() | |
377 | } | |
378 | ||
379 | func (s *BlockSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | |
380 | var childBlock *hcl.Block | |
381 | for _, candidate := range content.Blocks { | |
382 | if candidate.Type != s.TypeName { | |
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. | |
399 | type BlockListSpec struct { | |
400 | TypeName string | |
401 | Nested Spec | |
402 | MinItems int | |
403 | MaxItems int | |
404 | } | |
405 | ||
406 | func (s *BlockListSpec) visitSameBodyChildren(cb visitFunc) { | |
407 | // leaf node ("Nested" does not use the same body) | |
408 | } | |
409 | ||
410 | // blockSpec implementation | |
411 | func (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 | |
421 | func (s *BlockListSpec) nestedSpec() Spec { | |
422 | return s.Nested | |
423 | } | |
424 | ||
425 | // specNeedingVariables implementation | |
426 | func (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 | ||
440 | func (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 | ||
525 | func (s *BlockListSpec) impliedType() cty.Type { | |
526 | return cty.List(s.Nested.impliedType()) | |
527 | } | |
528 | ||
529 | func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | |
530 | // We return the source range of the _first_ block of the given type, | |
531 | // since they are not guaranteed to form a contiguous range. | |
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. | |
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 | ||
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. | |
673 | type BlockSetSpec struct { | |
674 | TypeName string | |
675 | Nested Spec | |
676 | MinItems int | |
677 | MaxItems int | |
678 | } | |
679 | ||
680 | func (s *BlockSetSpec) visitSameBodyChildren(cb visitFunc) { | |
681 | // leaf node ("Nested" does not use the same body) | |
682 | } | |
683 | ||
684 | // blockSpec implementation | |
685 | func (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 | |
695 | func (s *BlockSetSpec) nestedSpec() Spec { | |
696 | return s.Nested | |
697 | } | |
698 | ||
699 | // specNeedingVariables implementation | |
700 | func (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 | ||
714 | func (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 | ||
799 | func (s *BlockSetSpec) impliedType() cty.Type { | |
800 | return cty.Set(s.Nested.impliedType()) | |
801 | } | |
802 | ||
803 | func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | |
804 | // We return the source range of the _first_ block of the given type, | |
805 | // since they are not guaranteed to form a contiguous range. | |
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. | |
829 | type BlockMapSpec struct { | |
830 | TypeName string | |
831 | LabelNames []string | |
832 | Nested Spec | |
833 | } | |
834 | ||
835 | func (s *BlockMapSpec) visitSameBodyChildren(cb visitFunc) { | |
836 | // leaf node ("Nested" does not use the same body) | |
837 | } | |
838 | ||
839 | // blockSpec implementation | |
840 | func (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 | |
850 | func (s *BlockMapSpec) nestedSpec() Spec { | |
851 | return s.Nested | |
852 | } | |
853 | ||
854 | // specNeedingVariables implementation | |
855 | func (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 | ||
869 | func (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 | ||
940 | func (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 | ||
948 | func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | |
949 | // We return the source range of the _first_ block of the given type, | |
950 | // since they are not guaranteed to form a contiguous range. | |
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. | |
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 | ||
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. | |
1281 | type BlockLabelSpec struct { | |
1282 | Index int | |
1283 | Name string | |
1284 | } | |
1285 | ||
1286 | func (s *BlockLabelSpec) visitSameBodyChildren(cb visitFunc) { | |
1287 | // leaf node | |
1288 | } | |
1289 | ||
1290 | func (s *BlockLabelSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | |
1291 | if s.Index >= len(blockLabels) { | |
1292 | panic("BlockListSpec used in non-block context") | |
1293 | } | |
1294 | ||
1295 | return cty.StringVal(blockLabels[s.Index].Value), nil | |
1296 | } | |
1297 | ||
1298 | func (s *BlockLabelSpec) impliedType() cty.Type { | |
1299 | return cty.String // labels are always strings | |
1300 | } | |
1301 | ||
1302 | func (s *BlockLabelSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | |
1303 | if s.Index >= len(blockLabels) { | |
1304 | panic("BlockListSpec used in non-block context") | |
1305 | } | |
1306 | ||
1307 | return blockLabels[s.Index].Range | |
1308 | } | |
1309 | ||
1310 | func 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 |
1363 | type DefaultSpec struct { |
1364 | Primary Spec | |
1365 | Default Spec | |
1366 | } | |
1367 | ||
1368 | func (s *DefaultSpec) visitSameBodyChildren(cb visitFunc) { | |
1369 | cb(s.Primary) | |
1370 | cb(s.Default) | |
1371 | } | |
1372 | ||
1373 | func (s *DefaultSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | |
1374 | val, diags := s.Primary.decode(content, blockLabels, ctx) | |
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 | ||
1383 | func (s *DefaultSpec) impliedType() cty.Type { | |
1384 | return s.Primary.impliedType() | |
1385 | } | |
1386 | ||
107c1cdb ND |
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 | ||
15c0b25d AP |
1419 | func (s *DefaultSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { |
1420 | // We can't tell from here which of the two specs will ultimately be used | |
1421 | // in our result, so we'll just assume the first. This is usually the right | |
1422 | // choice because the default is often a literal spec that doesn't have a | |
1423 | // reasonable source range to return anyway. | |
1424 | return s.Primary.sourceRange(content, blockLabels) | |
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. | |
1434 | type TransformExprSpec struct { | |
1435 | Wrapped Spec | |
1436 | Expr hcl.Expression | |
1437 | TransformCtx *hcl.EvalContext | |
1438 | VarName string | |
1439 | } | |
1440 | ||
1441 | func (s *TransformExprSpec) visitSameBodyChildren(cb visitFunc) { | |
1442 | cb(s.Wrapped) | |
1443 | } | |
1444 | ||
1445 | func (s *TransformExprSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | |
1446 | wrappedVal, diags := s.Wrapped.decode(content, blockLabels, ctx) | |
1447 | if diags.HasErrors() { | |
1448 | // We won't try to run our function in this case, because it'll probably | |
1449 | // generate confusing additional errors that will distract from the | |
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 | ||
1463 | func (s *TransformExprSpec) impliedType() cty.Type { | |
1464 | wrappedTy := s.Wrapped.impliedType() | |
1465 | chiCtx := s.TransformCtx.NewChild() | |
1466 | chiCtx.Variables = map[string]cty.Value{ | |
1467 | s.VarName: cty.UnknownVal(wrappedTy), | |
1468 | } | |
1469 | resultVal, _ := s.Expr.Value(chiCtx) | |
1470 | return resultVal.Type() | |
1471 | } | |
1472 | ||
1473 | func (s *TransformExprSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | |
1474 | // We'll just pass through our wrapped range here, even though that's | |
1475 | // not super-accurate, because there's nothing better to return. | |
1476 | return s.Wrapped.sourceRange(content, blockLabels) | |
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. | |
1492 | type TransformFuncSpec struct { | |
1493 | Wrapped Spec | |
1494 | Func function.Function | |
1495 | } | |
1496 | ||
1497 | func (s *TransformFuncSpec) visitSameBodyChildren(cb visitFunc) { | |
1498 | cb(s.Wrapped) | |
1499 | } | |
1500 | ||
1501 | func (s *TransformFuncSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | |
1502 | wrappedVal, diags := s.Wrapped.decode(content, blockLabels, ctx) | |
1503 | if diags.HasErrors() { | |
1504 | // We won't try to run our function in this case, because it'll probably | |
1505 | // generate confusing additional errors that will distract from the | |
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 | ||
1527 | func (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 | ||
1538 | func (s *TransformFuncSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range { | |
1539 | // We'll just pass through our wrapped range here, even though that's | |
1540 | // not super-accurate, because there's nothing better to return. | |
1541 | return s.Wrapped.sourceRange(content, blockLabels) | |
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. | |
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 | } |