diff options
| author | Martin Atkins <mart@degeneration.co.uk> | 2018-08-09 16:53:16 -0700 |
|---|---|---|
| committer | Martin Atkins <mart@degeneration.co.uk> | 2018-08-09 16:53:16 -0700 |
| commit | bb724af7fd64ce766b39cad5f147d09d93221546 (patch) | |
| tree | 6b95224280481773ff921c1214fafdac1c7548c8 /cmd/hcldec | |
| parent | 59bb5c26709127dde2b5dceeb12ce2d656fe58c0 (diff) | |
hcldec: BlockAttrsSpec spec type
This is the hcldec interface to Body.JustAttributes, producing a map whose
keys are the child attribute names and whose values are the results of
evaluating those expressions.
We can't just expose a JustAttributes-style spec directly here because
it's not really compatible with how hcldec thinks about things, but we
can expose a spec that decodes a specific child block because that can
then compose properly with other specs at the same level without
interfering with their operation.
The primary use for this is to allow the use of the block syntax to define
a map:
dynamic_stuff {
foo = "bar"
}
JustAttributes is normally used in static analysis situations such as
enumerating the contents of a block to decide what to include in the
final EvalContext. That's not really possible with the hcldec model
because both structural decoding and expression evaluation happen
together. Therefore the use of this is pretty limited: it's useful if you
want to be compatible with an existing format based on legacy HCL where a
map was conventionally defined using block syntax, relying on the fact
that HCL did not make a strong distinction between attribute and block
syntax.
Diffstat (limited to 'cmd/hcldec')
| -rw-r--r-- | cmd/hcldec/spec-format.md | 34 | ||||
| -rw-r--r-- | cmd/hcldec/spec.go | 44 |
2 files changed, 78 insertions, 0 deletions
diff --git a/cmd/hcldec/spec-format.md b/cmd/hcldec/spec-format.md index 9f4d7a9..e26e10f 100644 --- a/cmd/hcldec/spec-format.md +++ b/cmd/hcldec/spec-format.md @@ -258,6 +258,40 @@ of the given type must have. `block` expects a single nested spec block, which is applied to the body of each matching block to produce the resulting map items. +## `block_attrs` spec blocks + +The `block_attrs` spec type is similar to an `attr` spec block of a map type, +but it produces a map from the attributes of a block rather than from an +attribute's expression. + +```hcl +block_attrs { + block_type = "variables" + element_type = string + required = false +} +``` + +This allows a map with user-defined keys to be produced within block syntax, +but due to the constraints of that syntax it also means that the user will +be unable to dynamically-generate either individual key names using key +expressions or the entire map value using a `for` expression. + +`block_attrs` spec blocks accept the following arguments: + +* `block_type` (required) - The block type name to expect within the HCL + input file. This may be omitted when a default name selector is created + by a parent `object` spec, if the input block type name should match the + output JSON object property name. + +* `element_type` (required) - The value type to require for each of the + attributes within a matched block. The resulting value will be a JSON + object whose property values are of this type. + +* `required` (optional) - If `true`, an error will be produced if a block + of the given type is not present. If `false` -- the default -- an absent + block will be indicated by producing `null`. + ## `literal` spec blocks The `literal` spec type returns a given literal value, and creates no diff --git a/cmd/hcldec/spec.go b/cmd/hcldec/spec.go index 31effd3..b62f05b 100644 --- a/cmd/hcldec/spec.go +++ b/cmd/hcldec/spec.go @@ -135,6 +135,9 @@ func decodeSpecBlock(block *hcl.Block) (hcldec.Spec, hcl.Diagnostics) { case "block_map": return decodeBlockMapSpec(block.Body, impliedName) + case "block_attrs": + return decodeBlockAttrsSpec(block.Body, impliedName) + case "default": return decodeDefaultSpec(block.Body) @@ -429,6 +432,47 @@ func decodeBlockNestedSpec(body hcl.Body) (hcldec.Spec, hcl.Diagnostics) { return spec, diags } +func decodeBlockAttrsSpec(body hcl.Body, impliedName string) (hcldec.Spec, hcl.Diagnostics) { + type content struct { + TypeName *string `hcl:"block_type"` + ElementType hcl.Expression `hcl:"element_type"` + Required *bool `hcl:"required"` + } + + var args content + diags := gohcl.DecodeBody(body, nil, &args) + if diags.HasErrors() { + return errSpec, diags + } + + spec := &hcldec.BlockAttrsSpec{ + TypeName: impliedName, + } + + if args.Required != nil { + spec.Required = *args.Required + } + if args.TypeName != nil { + spec.TypeName = *args.TypeName + } + + var typeDiags hcl.Diagnostics + spec.ElementType, typeDiags = evalTypeExpr(args.ElementType) + diags = append(diags, typeDiags...) + + if spec.TypeName == "" { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Missing block_type in block_attrs spec", + Detail: "The block_type attribute is required, to specify the block type name that is expected in an input HCL file.", + Subject: body.MissingItemRange().Ptr(), + }) + return errSpec, diags + } + + return spec, diags +} + func decodeLiteralSpec(body hcl.Body) (hcldec.Spec, hcl.Diagnostics) { type content struct { Value cty.Value `hcl:"value"` |
