gohcl: add "body" struct tag to capture entire block body

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.
This commit is contained in:
Mitchell Hashimoto 2020-10-22 16:53:37 -07:00
parent d56ed51dbc
commit 38280c6c69
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
4 changed files with 51 additions and 0 deletions

View File

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

View File

@ -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{}{},

View File

@ -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

View File

@ -113,6 +113,7 @@ type fieldTags struct {
Blocks map[string]int
Labels []labelField
Remain *int
Body *int
Optional map[string]bool
}
@ -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