guide: Low-level API section
This commit is contained in:
parent
d2e7762e9a
commit
495dfc9487
@ -3,3 +3,197 @@
|
||||
Advanced Decoding With The Low-level API
|
||||
========================================
|
||||
|
||||
In previous sections we've discussed :go:pkg:`gohcl` and :go:pkg:`hcldec`,
|
||||
which both deal with decoding of HCL bodies and the expressions within them
|
||||
using a high-level description of the expected configuration schema.
|
||||
Both of these packages are implemented in terms of HCL's low-level decoding
|
||||
interfaces, which we will explore in this section.
|
||||
|
||||
HCL decoding in the low-level API has two distinct phases:
|
||||
|
||||
* Structural decoding: analyzing the arguments and nested blocks present in a
|
||||
particular body.
|
||||
|
||||
* Expression evaluation: obtaining final values for each argument expression
|
||||
found during structural decoding.
|
||||
|
||||
The low-level API gives the calling application full control over when each
|
||||
body is decoded and when each expression is evaluated, allowing for more
|
||||
complex configuration formats where e.g. different variables are available in
|
||||
different contexts, or perhaps expressions within one block can refer to
|
||||
values defined in another block.
|
||||
|
||||
The low-level API also gives more detailed access to source location
|
||||
information for decoded elements, and so may be desirable for applications that
|
||||
do a lot of additional validation of decoded data where more specific source
|
||||
locations lead to better diagnostic messages.
|
||||
|
||||
Since all of the decoding mechanisms work with the same :go:type:`hcl.Body`
|
||||
type, it is fine and expected to mix them within an application to get access
|
||||
to the more detailed information where needed while using the higher-level APIs
|
||||
for the more straightforward portions of a configuration language.
|
||||
|
||||
The following subsections will give an overview of the low-level API. For full
|
||||
details, see `the godoc reference <https://godoc.org/github.com/hashicorp/hcl2/hcl>`_.
|
||||
|
||||
Structural Decoding
|
||||
-------------------
|
||||
|
||||
As seen in prior sections, :go:type:`hcl.Body` is an opaque representation of
|
||||
the arguments and child blocks at a particular nesting level. An HCL file has
|
||||
a root body containing the top-level elements, and then each nested block has
|
||||
its own body presenting its own content.
|
||||
|
||||
:go:type:`hcl.Body` is a Go interface whose methods serve as the structural
|
||||
decoding API:
|
||||
|
||||
.. go:currentpackage:: hcl
|
||||
|
||||
.. go:type:: Body
|
||||
|
||||
Represents the structural elements at a particular nesting level.
|
||||
|
||||
.. go:function:: func (b Body) Content(schema *BodySchema) (*BodyContent, Diagnostics)
|
||||
|
||||
Decode the content from the receiving body using the given schema. The
|
||||
schema is considered exhaustive of all content within the body, and so
|
||||
any elements not covered by the schema will generate error diagnostics.
|
||||
|
||||
.. go:function:: func (b Body) PartialContent(schema *BodySchema) (*BodyContent, Body, Diagnostics)
|
||||
|
||||
Similar to `Content`, but allows for additional arguments and block types
|
||||
that are not described in the given schema. The additional body return
|
||||
value is a special body that contains only the *remaining* elements, after
|
||||
extraction of the ones covered by the schema. This returned body can be
|
||||
used to decode the remaining content elsewhere in the calling program.
|
||||
|
||||
.. go:function:: func (b Body) JustAttributes() (Attributes, Diagnostics)
|
||||
|
||||
Decode the content from the receving body in a special *attributes-only*
|
||||
mode, allowing the calling application to enumerate the arguments given
|
||||
inside the body without needing to predict them in schema.
|
||||
|
||||
When this method is used, a body can be treated somewhat like a map
|
||||
expression, but it still has a rigid structure where the arguments must
|
||||
be given directly with no expression evaluation. This is an advantage for
|
||||
declarations that must themselves be resolved before expression
|
||||
evaluation is possible.
|
||||
|
||||
If the body contains any blocks, error diagnostics are returned. JSON
|
||||
syntax relies on schema to distinguish arguments from nested blocks, and
|
||||
so a JSON body in attributes-only mode will treat all JSON object
|
||||
properties as arguments.
|
||||
|
||||
.. go:function:: func (b Body) MissingItemRange() Range
|
||||
|
||||
Returns a source range that points to where an absent required item in
|
||||
the body might be placed. This is a "best effort" sort of thing, required
|
||||
only to be somewhere inside the receving body, as a way to give source
|
||||
location information for a "missing required argument" sort of error.
|
||||
|
||||
The main content-decoding methods each require a :go:type:`hcl.BodySchema`
|
||||
object describing the expected content. The fields of this type describe the
|
||||
expected arguments and nested block types respectively:
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
schema := &hcl.BodySchema{
|
||||
Attributes: []hcl.AttributeSchema{
|
||||
{
|
||||
Name: "io_mode",
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
Blocks: []hcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "service",
|
||||
LabelNames: []string{"type", "name"},
|
||||
},
|
||||
},
|
||||
}
|
||||
content, moreDiags := body.Content(schema)
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
:go:type:`hcl.BodyContent` is the result of both ``Content`` and
|
||||
``PartialContent``, giving the actual attributes and nested blocks that were
|
||||
found. Since arguments are uniquely named within a body and unordered, they
|
||||
are returned as a map. Nested blocks are ordered and may have many instances
|
||||
of a given type, so they are returned all together in a single slice for
|
||||
further interpretation by the caller.
|
||||
|
||||
Unlike the two higher-level approaches, the low-level API *always* works only
|
||||
with one nesting level at a time. Decoding a nested block returns the "header"
|
||||
for that block, giving its type and label values, but its body remains an
|
||||
:go:type:`hcl.Body` for later decoding.
|
||||
|
||||
Each returned attribute corresponds to one of the arguments in the body, and
|
||||
it has an :go:type:`hcl.Expression` object that can be used to obtain a value
|
||||
for the argument during expression evaluation, as described in the next
|
||||
section.
|
||||
|
||||
Expression Evaluation
|
||||
---------------------
|
||||
|
||||
Expression evaluation *in general* has its own section, imaginitively titled
|
||||
:ref:`go-expression-eval`, so this section will focus only on how it is
|
||||
achieved in the low-level API.
|
||||
|
||||
All expression evaluation in the low-level API starts with an
|
||||
:go:type:`hcl.Expression` object. This is another interface type, with various
|
||||
implementations depending on the expression type and the syntax it was parsed
|
||||
from.
|
||||
|
||||
.. go:currentpackage:: hcl
|
||||
|
||||
.. go:type:: Expression
|
||||
|
||||
Represents a unevaluated single expression.
|
||||
|
||||
.. go:function:: func (e Expression) Value(ctx *EvalContext) (cty.Value, Diagnostics)
|
||||
|
||||
Evaluates the receiving expression in the given evaluation context. The
|
||||
result is a :go:type:`cty.Value` representing the result value, along
|
||||
with any diagnostics that were raised during evaluation.
|
||||
|
||||
If the diagnostics contains errors, the value may be incomplete or
|
||||
invalid and should either be discarded altogether or used with care for
|
||||
analysis.
|
||||
|
||||
.. go:function:: func (e Expression) Variables() []Traversal
|
||||
|
||||
Returns information about any nested expressions that access variables
|
||||
from the *global* evaluation context. Does not include references to
|
||||
temporary local variables, such as those generated by a
|
||||
"``for`` expression".
|
||||
|
||||
.. go:function:: func (e Expression) Range() Range
|
||||
|
||||
Returns the source range for the entire expression. This can be useful
|
||||
when generating application-specific diagnostic messages, such as
|
||||
value validation errors.
|
||||
|
||||
.. go:function:: func (e Expression) StartRange() Range
|
||||
|
||||
Similar to ``Range``, but if the expression is complex, such as a tuple
|
||||
or object constructor, may indicate only the opening tokens for the
|
||||
construct to avoid creating an overwhelming source code snippet.
|
||||
|
||||
This should be used in diagnostic messages only in situations where the
|
||||
error is clearly with the construct itself and not with the overall
|
||||
expression. For example, a type error indicating that a tuple was not
|
||||
expected might use ``StartRange`` to draw attention to the beginning
|
||||
of a tuple constructor, without highlighting the entire expression.
|
||||
|
||||
Method ``Value`` is the primary API for expressions, and takes the same kind
|
||||
of evaluation context object described in :ref:`go-expression-eval`.
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
ctx := &hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"name": cty.StringVal("Ermintrude"),
|
||||
"age": cty.NumberIntVal(32),
|
||||
},
|
||||
}
|
||||
val, moreDiags := expr.Value(ctx)
|
||||
diags = append(diags, moreDiags...)
|
||||
|
Loading…
Reference in New Issue
Block a user