From 38280c6c697fea853214c8586baeb81ef14169ef Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 22 Oct 2020 16:53:37 -0700 Subject: [PATCH] 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. --- gohcl/decode.go | 13 +++++++++++++ gohcl/decode_test.go | 24 ++++++++++++++++++++++++ gohcl/doc.go | 7 +++++++ gohcl/schema.go | 7 +++++++ 4 files changed, 51 insertions(+) diff --git a/gohcl/decode.go b/gohcl/decode.go index f0d589d..3525383 100644 --- a/gohcl/decode.go +++ b/gohcl/decode.go @@ -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) diff --git a/gohcl/decode_test.go b/gohcl/decode_test.go index 8f065c4..0927490 100644 --- a/gohcl/decode_test.go +++ b/gohcl/decode_test.go @@ -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{}{}, diff --git a/gohcl/doc.go b/gohcl/doc.go index c1921cb..419efb0 100644 --- a/gohcl/doc.go +++ b/gohcl/doc.go @@ -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 diff --git a/gohcl/schema.go b/gohcl/schema.go index ecf6ddb..df21cc4 100644 --- a/gohcl/schema.go +++ b/gohcl/schema.go @@ -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