github.com/hashicorp/hcl/v2@v2.20.0/guide/go_decoding_lowlevel.rst (about)

     1  .. _go-decoding-lowlevel:
     2  
     3  Advanced Decoding With The Low-level API
     4  ========================================
     5  
     6  In previous sections we've discussed :go:pkg:`gohcl` and :go:pkg:`hcldec`,
     7  which both deal with decoding of HCL bodies and the expressions within them
     8  using a high-level description of the expected configuration schema.
     9  Both of these packages are implemented in terms of HCL's low-level decoding
    10  interfaces, which we will explore in this section.
    11  
    12  HCL decoding in the low-level API has two distinct phases:
    13  
    14  * Structural decoding: analyzing the arguments and nested blocks present in a
    15    particular body.
    16  
    17  * Expression evaluation: obtaining final values for each argument expression
    18    found during structural decoding.
    19  
    20  The low-level API gives the calling application full control over when each
    21  body is decoded and when each expression is evaluated, allowing for more
    22  complex configuration formats where e.g. different variables are available in
    23  different contexts, or perhaps expressions within one block can refer to
    24  values defined in another block.
    25  
    26  The low-level API also gives more detailed access to source location
    27  information for decoded elements, and so may be desirable for applications that
    28  do a lot of additional validation of decoded data where more specific source
    29  locations lead to better diagnostic messages.
    30  
    31  Since all of the decoding mechanisms work with the same :go:type:`hcl.Body`
    32  type, it is fine and expected to mix them within an application to get access
    33  to the more detailed information where needed while using the higher-level APIs
    34  for the more straightforward portions of a configuration language.
    35  
    36  The following subsections will give an overview of the low-level API. For full
    37  details, see `the godoc reference <https://godoc.org/github.com/hashicorp/hcl/v2/hcl>`_.
    38  
    39  Structural Decoding
    40  -------------------
    41  
    42  As seen in prior sections, :go:type:`hcl.Body` is an opaque representation of
    43  the arguments and child blocks at a particular nesting level. An HCL file has
    44  a root body containing the top-level elements, and then each nested block has
    45  its own body presenting its own content.
    46  
    47  :go:type:`hcl.Body` is a Go interface whose methods serve as the structural
    48  decoding API:
    49  
    50  .. go:currentpackage:: hcl
    51  
    52  .. go:type:: Body
    53  
    54     Represents the structural elements at a particular nesting level.
    55  
    56     .. go:function:: func (b Body) Content(schema *BodySchema) (*BodyContent, Diagnostics)
    57  
    58        Decode the content from the receiving body using the given schema. The
    59        schema is considered exhaustive of all content within the body, and so
    60        any elements not covered by the schema will generate error diagnostics.
    61  
    62     .. go:function:: func (b Body) PartialContent(schema *BodySchema) (*BodyContent, Body, Diagnostics)
    63  
    64        Similar to `Content`, but allows for additional arguments and block types
    65        that are not described in the given schema. The additional body return
    66        value is a special body that contains only the *remaining* elements, after
    67        extraction of the ones covered by the schema. This returned body can be
    68        used to decode the remaining content elsewhere in the calling program.
    69  
    70     .. go:function:: func (b Body) JustAttributes() (Attributes, Diagnostics)
    71  
    72        Decode the content from the receving body in a special *attributes-only*
    73        mode, allowing the calling application to enumerate the arguments given
    74        inside the body without needing to predict them in schema.
    75  
    76        When this method is used, a body can be treated somewhat like a map
    77        expression, but it still has a rigid structure where the arguments must
    78        be given directly with no expression evaluation. This is an advantage for
    79        declarations that must themselves be resolved before expression
    80        evaluation is possible.
    81  
    82        If the body contains any blocks, error diagnostics are returned. JSON
    83        syntax relies on schema to distinguish arguments from nested blocks, and
    84        so a JSON body in attributes-only mode will treat all JSON object
    85        properties as arguments.
    86  
    87     .. go:function:: func (b Body) MissingItemRange() Range
    88  
    89        Returns a source range that points to where an absent required item in
    90        the body might be placed. This is a "best effort" sort of thing, required
    91        only to be somewhere inside the receving body, as a way to give source
    92        location information for a "missing required argument" sort of error.
    93  
    94  The main content-decoding methods each require a :go:type:`hcl.BodySchema`
    95  object describing the expected content. The fields of this type describe the
    96  expected arguments and nested block types respectively:
    97  
    98  .. code-block:: go
    99  
   100     schema := &hcl.BodySchema{
   101         Attributes: []hcl.AttributeSchema{
   102             {
   103                 Name:     "io_mode",
   104                 Required: false,
   105             },
   106         },
   107         Blocks: []hcl.BlockHeaderSchema{
   108             {
   109                 Type:       "service",
   110                 LabelNames: []string{"type", "name"},
   111             },
   112         },
   113     }
   114     content, moreDiags := body.Content(schema)
   115     diags = append(diags, moreDiags...)
   116  
   117  :go:type:`hcl.BodyContent` is the result of both ``Content`` and
   118  ``PartialContent``, giving the actual attributes and nested blocks that were
   119  found. Since arguments are uniquely named within a body and unordered, they
   120  are returned as a map. Nested blocks are ordered and may have many instances
   121  of a given type, so they are returned all together in a single slice for
   122  further interpretation by the caller.
   123  
   124  Unlike the two higher-level approaches, the low-level API *always* works only
   125  with one nesting level at a time. Decoding a nested block returns the "header"
   126  for that block, giving its type and label values, but its body remains an
   127  :go:type:`hcl.Body` for later decoding.
   128  
   129  Each returned attribute corresponds to one of the arguments in the body, and
   130  it has an :go:type:`hcl.Expression` object that can be used to obtain a value
   131  for the argument during expression evaluation, as described in the next
   132  section.
   133  
   134  Expression Evaluation
   135  ---------------------
   136  
   137  Expression evaluation *in general* has its own section, imaginitively titled
   138  :ref:`go-expression-eval`, so this section will focus only on how it is
   139  achieved in the low-level API.
   140  
   141  All expression evaluation in the low-level API starts with an
   142  :go:type:`hcl.Expression` object. This is another interface type, with various
   143  implementations depending on the expression type and the syntax it was parsed
   144  from.
   145  
   146  .. go:currentpackage:: hcl
   147  
   148  .. go:type:: Expression
   149  
   150     Represents a unevaluated single expression.
   151  
   152     .. go:function:: func (e Expression) Value(ctx *EvalContext) (cty.Value, Diagnostics)
   153  
   154        Evaluates the receiving expression in the given evaluation context. The
   155        result is a :go:type:`cty.Value` representing the result value, along
   156        with any diagnostics that were raised during evaluation.
   157  
   158        If the diagnostics contains errors, the value may be incomplete or
   159        invalid and should either be discarded altogether or used with care for
   160        analysis.
   161  
   162     .. go:function:: func (e Expression) Variables() []Traversal
   163  
   164        Returns information about any nested expressions that access variables
   165        from the *global* evaluation context. Does not include references to
   166        temporary local variables, such as those generated by a
   167        "``for`` expression".
   168  
   169     .. go:function:: func (e Expression) Range() Range
   170  
   171        Returns the source range for the entire expression. This can be useful
   172        when generating application-specific diagnostic messages, such as
   173        value validation errors.
   174  
   175     .. go:function:: func (e Expression) StartRange() Range
   176  
   177        Similar to ``Range``, but if the expression is complex, such as a tuple
   178        or object constructor, may indicate only the opening tokens for the
   179        construct to avoid creating an overwhelming source code snippet.
   180  
   181        This should be used in diagnostic messages only in situations where the
   182        error is clearly with the construct itself and not with the overall
   183        expression. For example, a type error indicating that a tuple was not
   184        expected might use ``StartRange`` to draw attention to the beginning
   185        of a tuple constructor, without highlighting the entire expression.
   186  
   187  Method ``Value`` is the primary API for expressions, and takes the same kind
   188  of evaluation context object described in :ref:`go-expression-eval`.
   189  
   190  .. code-block:: go
   191  
   192     ctx := &hcl.EvalContext{
   193          Variables: map[string]cty.Value{
   194              "name": cty.StringVal("Ermintrude"),
   195              "age":  cty.NumberIntVal(32),
   196          },
   197     }
   198     val, moreDiags := expr.Value(ctx)
   199     diags = append(diags, moreDiags...)