| .. _go-decoding-lowlevel: |
| |
| 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/hcl/v2/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...) |