feat: Support composite struct with nested keyword
This commit is contained in:
parent
d7e7bca9a9
commit
b54debed5c
@ -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)
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user