Skip to content

Commit

Permalink
gohcl: add "body" struct tag to capture entire block body
Browse files Browse the repository at this point in the history
Structs can specify the "body" struct tag to capture the entire block
body. This does NOT act as "remain" where leftover fields are
non-erroneous. If you want leftover fields to be allowed, a "remain"
field must ALSO be present.

This is used to capture the full block body that was decoded for the
block. This is useful if you want to ever redecode something differently
(maybe with a different EvalContext) or partially decode something but
redecode the entire value.
  • Loading branch information
mitchellh committed Oct 22, 2020
1 parent d56ed51 commit 38280c6
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 0 deletions.
13 changes: 13 additions & 0 deletions gohcl/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)

tags := getFieldTags(val.Type())

if tags.Body != nil {
fieldIdx := *tags.Body
field := val.Type().Field(fieldIdx)
fieldV := val.Field(fieldIdx)
switch {
case bodyType.AssignableTo(field.Type):
fieldV.Set(reflect.ValueOf(body))

default:
diags = append(diags, decodeBodyToValue(body, ctx, fieldV)...)
}
}

if tags.Remain != nil {
fieldIdx := *tags.Remain
field := val.Type().Field(fieldIdx)
Expand Down
24 changes: 24 additions & 0 deletions gohcl/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,30 @@ func TestDecodeBody(t *testing.T) {
}),
0,
},
{
map[string]interface{}{
"name": "Ermintrude",
"age": 50,
},
makeInstantiateType(struct {
Name string `hcl:"name"`
Body hcl.Body `hcl:",body"`
Remain hcl.Body `hcl:",remain"`
}{}),
func(gotI interface{}) bool {
got := gotI.(struct {
Name string `hcl:"name"`
Body hcl.Body `hcl:",body"`
Remain hcl.Body `hcl:",remain"`
})

attrs, _ := got.Body.JustAttributes()

return got.Name == "Ermintrude" && len(attrs) == 2 &&
attrs["name"] != nil && attrs["age"] != nil
},
0,
},
{
map[string]interface{}{
"noodle": map[string]interface{}{},
Expand Down
7 changes: 7 additions & 0 deletions gohcl/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
// in which case multiple blocks of the corresponding type are decoded into
// the slice.
//
// "body" can be placed on a single field of type hcl.Body to capture
// the full hcl.Body that was decoded for a block. This does not allow leftover
// values like "remain", so a decoding error will still be returned if leftover
// fields are given. If you want to capture the decoding body PLUS leftover
// fields, you must specify a "remain" field as well to prevent errors. The
// body field and the remain field will both contain the leftover fields.
//
// "label" fields are considered only in a struct used as the type of a field
// marked as "block", and are used sequentially to capture the labels of
// the blocks being decoded. In this case, the name token is used only as
Expand Down
7 changes: 7 additions & 0 deletions gohcl/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ type fieldTags struct {
Blocks map[string]int
Labels []labelField
Remain *int
Body *int
Optional map[string]bool
}

Expand Down Expand Up @@ -162,6 +163,12 @@ func getFieldTags(ty reflect.Type) *fieldTags {
}
idx := i // copy, because this loop will continue assigning to i
ret.Remain = &idx
case "body":
if ret.Body != nil {
panic("only one 'body' tag is permitted")
}
idx := i // copy, because this loop will continue assigning to i
ret.Body = &idx
case "optional":
ret.Attributes[name] = i
ret.Optional[name] = true
Expand Down

0 comments on commit 38280c6

Please sign in to comment.