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...)