feat: Support composite struct with nested keyword

This commit is contained in:
RouxAntoine 2021-03-08 00:56:12 +01:00
parent d7e7bca9a9
commit b54debed5c
Signed by: antoine
GPG Key ID: 098FB66FC0475E70
4 changed files with 93 additions and 2 deletions

View File

@ -78,6 +78,39 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
}
}
for _, fieldIdx := range tags.Nested {
field := val.Type().Field(fieldIdx)
fieldV := val.Field(fieldIdx)
ty := field.Type
isPtr := false
if ty.Kind() == reflect.Ptr {
isPtr = true
ty = ty.Elem()
}
switch {
case bodyType.AssignableTo(field.Type):
fieldV.Set(reflect.ValueOf(leftovers))
case attrsType.AssignableTo(field.Type):
attrs, attrsDiags := leftovers.JustAttributes()
if len(attrsDiags) > 0 {
diags = append(diags, attrsDiags...)
}
fieldV.Set(reflect.ValueOf(attrs))
default:
v := fieldV
if isPtr {
if v.IsNil() {
v = reflect.New(ty)
}
v = v.Elem()
}
diags = append(diags, decodeBodyToValue(leftovers, ctx, v)...)
fieldV.Set(v)
}
}
if tags.Remain != nil {
fieldIdx := *tags.Remain
field := val.Type().Field(fieldIdx)

View File

@ -32,6 +32,7 @@ func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
var attrSchemas []hcl.AttributeSchema
var blockSchemas []hcl.BlockHeaderSchema
var nestedBlockSchemas []hcl.NestedBlockSchemas
tags := getFieldTags(ty)
@ -100,10 +101,51 @@ func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
})
}
partial = tags.Remain != nil
nestedNames := make([]string, 0, len(tags.Nested))
for n := range tags.Nested {
nestedNames = append(nestedNames, n)
}
sort.Strings(nestedNames)
for _, n := range nestedNames {
idx := tags.Nested[n]
optional := tags.Optional[n]
field := ty.Field(idx)
fty := field.Type
// if its a pointer get target element
if fty.Kind() == reflect.Ptr {
fty = fty.Elem()
}
if fty.Kind() != reflect.Struct {
panic(fmt.Sprintf(
"hcl 'nested' tag kind cannot be applied to %s field %s: struct required", field.Type.String(), field.Name,
))
}
var required bool
switch {
case field.Type.AssignableTo(exprType):
// If we're decoding to hcl.Expression then absense can be
// indicated via a null value, so we don't specify that
// the field is required during decoding.
required = false
case field.Type.Kind() != reflect.Ptr && !optional:
required = true
default:
required = false
}
nestedBlockSchemas = append(nestedBlockSchemas, hcl.NestedBlockSchemas{
Name: n,
Required: required,
})
}
partial = tags.Remain != nil || len(tags.Nested) > 0
schema = &hcl.BodySchema{
Attributes: attrSchemas,
Blocks: blockSchemas,
Nested: nestedBlockSchemas,
}
return schema, partial
}
@ -115,6 +157,7 @@ type fieldTags struct {
Remain *int
Body *int
Optional map[string]bool
Nested map[string]int
}
type labelField struct {
@ -127,6 +170,7 @@ func getFieldTags(ty reflect.Type) *fieldTags {
Attributes: map[string]int{},
Blocks: map[string]int{},
Optional: map[string]bool{},
Nested: map[string]int{},
}
ct := ty.NumField()
@ -172,6 +216,12 @@ func getFieldTags(ty reflect.Type) *fieldTags {
case "optional":
ret.Attributes[name] = i
ret.Optional[name] = true
case "nested":
// name anonymous embedded type with anon-<idx>
if name == "" {
name = fmt.Sprintf("anon-%d", i)
}
ret.Nested[name] = i
default:
panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name))
}

View File

@ -58,7 +58,7 @@ func (b *Body) Range() hcl.Range {
func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
content, remainHCL, diags := b.PartialContent(schema)
// No we'll see if anything actually remains, to produce errors about
// Now we'll see if anything actually remains, to produce errors about
// extraneous items.
remain := remainHCL.(*Body)

View File

@ -14,8 +14,16 @@ type AttributeSchema struct {
Required bool
}
// NestedBlockSchemas represent the requirements for a list of nested
// attributes or block
type NestedBlockSchemas struct {
Name string
Required bool
}
// BodySchema represents the desired shallow structure of a body.
type BodySchema struct {
Attributes []AttributeSchema
Blocks []BlockHeaderSchema
Nested []NestedBlockSchemas
}