This is essentially a CLI wrapper around the hcldec package, accepting a decoding specification via a HCL-based language and using it to translate input HCL files into JSON values while performing basic structural and type validation of the input files.
11 KiB
hcldec
spec format
The hcldec
spec format instructs hcldec
on how to validate
one or more configuration files given in the HCL syntax and how to translate
the result into JSON format.
The spec format is itself built from HCL syntax, with each HCL block serving as a spec whose block type and contents together describe a single mapping action and, in most cases, a validation constraint. Each spec block produces one JSON value.
A spec file must have a single top-level spec block that describes the
top-level JSON value hcldec
will return, and that spec block may have other
nested spec blocks (depending on its type) that produce nested structures and
additional validation constraints.
The most common usage of hcldec
is to produce a JSON object whose properties
are derived from the top-level content of the input file. In this case, the
root of the given spec file will have an object
spec block whose contents
describe how each of the object's properties are to be populated using
nested spec blocks.
Each spec is evaluated in the context of an HCL body, which is the HCL terminology for one level of nesting in a configuration file. The top-level objects in a file all belong to the root body of that file, and then each nested block has its own body containing the elements within that block. Some spec types select a new body as the context for their nested specs, allowing nested HCL structures to be decoded.
Spec Block Types
The following sections describe the different block types that can be used to define specs within a spec file.
object
spec blocks
The object
spec type is the most commonly used at the root of a spec file.
Its result is a JSON object whose properties are set based on any nested
spec blocks:
object {
attr "name" {
type = "string"
}
block "address" {
object {
attr "street" {
type = "string"
}
# ...
}
}
}
Nested spec blocks inside object
must always have an extra block label
"name"
, "address"
and "street"
in the above example) that specifies
the name of the property that should be created in the JSON object result.
This label also acts as a default name selector for the nested spec, allowing
the attr
blocks in the above example to omit the usually-required name
argument in cases where the HCL input name and JSON output name are the same.
An object
spec block creates no validation constraints, but it passes on
any validation constraints created by the nested specs.
array
spec blocks
The array
spec type produces a JSON array whose elements are set based on
any nested spec blocks:
array {
attr {
name = "first_element"
type = "string"
}
attr {
name = "second_element"
type = "string"
}
}
An array
spec block creates no validation constraints, but it passes on
any validation constraints created by the nested specs.
attr
spec blocks
The attr
spec type reads the value of an attribute in the current body
and returns that value as its result. It also creates validation constraints
for the given attribute name and its value.
attr {
name = "document_root"
type = string
required = true
}
attr
spec blocks accept the following arguments:
-
name
(required) - The attribute name to expect within the HCL input file. This may be omitted when a default name selector is created by a parentobject
spec, if the input attribute name should match the output JSON object property name. -
type
(optional) - A type expression that the given attribute value must conform to. If this argument is set,hcldec
will automatically convert the given input value to this type or produce an error if that is not possible. -
required
(optional) - If set totrue
,hcldec
will produce an error if a value is not provided for the source attribute.
attr
is a leaf spec type, so no nested spec blocks are permitted.
block
spec blocks
The block
spec type applies one nested spec block to the contents of a
block within the current body and returns the result of that spec. It also
creates validation constraints for the given block type name.
block {
block_type = "logging"
object {
attr "level" {
type = string
}
attr "file" {
type = string
}
}
}
block
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 parentobject
spec, if the input block type name should match the output JSON object property name. -
required
(optional) - If set totrue
,hcldec
will produce an error if a block of the specified type is not present in the current body.
block
creates a validation constraint that there must be zero or one blocks
of the given type name, or exactly one if required
is set.
block
expects a single nested spec block, which is applied to the body of
the block of the given type when it is present.
block_list
spec blocks
The block_list
spec type is similar to block
, but it accepts zero or
more blocks of a specified type rather than requiring zero or one. The
result is a JSON array with one entry per block of the given type.
block_list {
block_type = "log_file"
object {
attr "level" {
type = string
}
attr "filename" {
type = string
required = true
}
}
}
block_list
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 parentobject
spec, if the input block type name should match the output JSON object property name. -
min_items
(optional) - If set to a number greater than zero,hcldec
will produce an error if fewer than the given number of blocks are present. -
max_items
(optional) - If set to a number greater than zero,hcldec
will produce an error if more than the given number of blocks are present. This attribute must be greater than or equal tomin_items
if both are set.
block
creates a validation constraint on the number of blocks of the given
type that must be present.
block
expects a single nested spec block, which is applied to the body of
each matching block to produce the resulting list items.
block_set
spec blocks
The block_set
spec type behaves the same as block_list
except that
the result is in no specific order and any duplicate items are removed.
block_set {
block_type = "log_file"
object {
attr "level" {
type = string
}
attr "filename" {
type = string
required = true
}
}
}
The contents of block_set
are the same as for block_list
.
block_map
spec blocks
The block_map
spec type is similar to block
, but it accepts zero or
more blocks of a specified type rather than requiring zero or one. The
result is a JSON object, or possibly multiple nested JSON objects, whose
properties are derived from the labels set on each matching block.
block_map {
block_type = "log_file"
labels = ["filename"]
object {
attr "level" {
type = string
required = true
}
}
}
block_map
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 parentobject
spec, if the input block type name should match the output JSON object property name. -
labels
(required) - A list of user-oriented block label names. Each entry in this list creates one level of object within the output value, and requires one additional block header label on any child block of this type. Block header labels are the quoted strings that appear after the block type name but before the opening{
.
block
creates a validation constraint on the number of labels that blocks
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.
literal
spec blocks
The literal
spec type returns a given literal value, and creates no
validation constraints. It is most commonly used with the default
spec
type to create a fallback value, but can also be used e.g. to fill out
required properties in an object
spec that do not correspond to any
construct in the input configuration.
literal {
value = "hello world"
}
literal
spec blocks accept the following argument:
value
(required) - The value to return.
literal
is a leaf spec type, so no nested spec blocks are permitted.
default
spec blocks
The default
spec type evaluates a sequence of nested specs in turn and
returns the result of the first one that produces a non-null value.
It creates no validation constraints of its own, but passes on the validation
constraints from its first nested block.
default {
attr {
name = "private"
type = bool
}
literal {
value = false
}
}
A default
spec block must have at least one nested spec block, and should
generally have at least two since otherwise the default
wrapper is a no-op.
The second and any subsequent spec blocks are fallback specs. These exhibit
their usual behavior but are not able to impose validation constraints on the
current body since they are not evaluated unless all prior specs produce
null
as their result.
Type Expressions
Type expressions are used to describe the expected type of an attribute, as an additional validation constraint.
A type expression uses primitive type names and compound type constructors. A type constructor builds a new type based on one or more type expression arguments.
The following type names and type constructors are supported:
any
is a wildcard that accepts a value of any type. (In HCL terms, this is the dynamic pseudo-type.)string
is a Unicode string.number
is an arbitrary-precision floating point number.bool
is a boolean value (true
orfalse
)list(element_type)
constructs a list type with the given element typeset(element_type)
constructs a set type with the given element typemap(element_type)
constructs a map type with the given element typeobject({name1 = element_type, name2 = element_type, ...})
constructs an object type with the given attribute types.tuple([element_type, element_type, ...])
constructs a tuple type with the given element types. This can be used, for example, to require an array with a particular number of elements, or with elements of different types.
The above types are as defined by the HCL syntax-agnostic information model. After validation, values are lowered to JSON's type system, which is a subset of the HCL type system.
null
is a valid value of any type, and not a type itself.