Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gohcl: Struct Embedding #667

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Partial support for gohcl struct embedding
Still needs encoder changes and tests
  • Loading branch information
cam72cam committed Mar 12, 2024
commit 8a475eb26387358d32cf403a6f991edb0d2d2315
9 changes: 9 additions & 0 deletions gohcl/decode.go
Original file line number Diff line number Diff line change
@@ -66,6 +66,11 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
return diags
}

return append(diags, decodeBodyToStructInner(body, content, leftovers, ctx, val)...)
}
func decodeBodyToStructInner(body hcl.Body, content *hcl.BodyContent, leftovers hcl.Body, ctx *hcl.EvalContext, val reflect.Value) hcl.Diagnostics {
var diags hcl.Diagnostics

tags := getFieldTags(val.Type())

if tags.Body != nil {
@@ -98,6 +103,10 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
diags = append(diags, decodeBodyToValue(leftovers, ctx, fieldV)...)
}
}
for _, embedded := range tags.Embedded {
fieldV := val.Field(embedded.FieldIndex)
diags = append(diags, decodeBodyToStructInner(body, content, leftovers, ctx, fieldV)...)
}

for name, fieldIdx := range tags.Attributes {
attr := content.Attributes[name]
49 changes: 38 additions & 11 deletions gohcl/schema.go
Original file line number Diff line number Diff line change
@@ -4,9 +4,10 @@
package gohcl

import (
"cmp"
"fmt"
"reflect"
"sort"
"slices"
"strings"

"github.com/hashicorp/hcl/v2"
@@ -24,6 +25,10 @@ import (
// mapping is attempted, this function will panic.
func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
ty := reflect.TypeOf(val)
if nty, ok := val.(reflect.Value); ok {
// Recursion through embedded structs
ty = nty.Type()
}

if ty.Kind() == reflect.Ptr {
ty = ty.Elem()
@@ -38,12 +43,7 @@ func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {

tags := getFieldTags(ty)

attrNames := make([]string, 0, len(tags.Attributes))
for n := range tags.Attributes {
attrNames = append(attrNames, n)
}
sort.Strings(attrNames)
for _, n := range attrNames {
idx := tags.Attributes[n]
optional := tags.Optional[n]
field := ty.Field(idx)
@@ -68,12 +68,7 @@ func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
})
}

blockNames := make([]string, 0, len(tags.Blocks))
for n := range tags.Blocks {
blockNames = append(blockNames, n)
}
sort.Strings(blockNames)
for _, n := range blockNames {
idx := tags.Blocks[n]
field := ty.Field(idx)
fty := field.Type
@@ -104,6 +99,25 @@ func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
}

partial = tags.Remain != nil

for _, embedded := range tags.Embedded {
nested, npartial := ImpliedBodySchema(reflect.New(embedded.Type))
if npartial && partial {
panic("only one 'remain' tag is permitted (nested)")
}

attrSchemas = append(attrSchemas, nested.Attributes...)
blockSchemas = append(blockSchemas, nested.Blocks...)
println(fmt.Sprintf("%v: %#v\n", embedded.Type, nested))
}

slices.SortStableFunc(attrSchemas, func(a, b hcl.AttributeSchema) int {
return cmp.Compare(a.Name, b.Name)
})
slices.SortStableFunc(blockSchemas, func(a, b hcl.BlockHeaderSchema) int {
return cmp.Compare(a.Type, b.Type)
})

schema = &hcl.BodySchema{
Attributes: attrSchemas,
Blocks: blockSchemas,
@@ -118,6 +132,12 @@ type fieldTags struct {
Remain *int
Body *int
Optional map[string]bool
Embedded []embeddedField
}

type embeddedField struct {
FieldIndex int
Type reflect.Type
}

type labelField struct {
@@ -135,8 +155,15 @@ func getFieldTags(ty reflect.Type) *fieldTags {
ct := ty.NumField()
for i := 0; i < ct; i++ {
field := ty.Field(i)

tag := field.Tag.Get("hcl")
if tag == "" {
if field.Type.Kind() == reflect.Struct && field.Anonymous {
ret.Embedded = append(ret.Embedded, embeddedField{
FieldIndex: i,
Type: field.Type,
})
}
continue
}