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 {
|
if tags.Remain != nil {
|
||||||
fieldIdx := *tags.Remain
|
fieldIdx := *tags.Remain
|
||||||
field := val.Type().Field(fieldIdx)
|
field := val.Type().Field(fieldIdx)
|
||||||
|
@ -32,6 +32,7 @@ func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
|
|||||||
|
|
||||||
var attrSchemas []hcl.AttributeSchema
|
var attrSchemas []hcl.AttributeSchema
|
||||||
var blockSchemas []hcl.BlockHeaderSchema
|
var blockSchemas []hcl.BlockHeaderSchema
|
||||||
|
var nestedBlockSchemas []hcl.NestedBlockSchemas
|
||||||
|
|
||||||
tags := getFieldTags(ty)
|
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{
|
schema = &hcl.BodySchema{
|
||||||
Attributes: attrSchemas,
|
Attributes: attrSchemas,
|
||||||
Blocks: blockSchemas,
|
Blocks: blockSchemas,
|
||||||
|
Nested: nestedBlockSchemas,
|
||||||
}
|
}
|
||||||
return schema, partial
|
return schema, partial
|
||||||
}
|
}
|
||||||
@ -115,6 +157,7 @@ type fieldTags struct {
|
|||||||
Remain *int
|
Remain *int
|
||||||
Body *int
|
Body *int
|
||||||
Optional map[string]bool
|
Optional map[string]bool
|
||||||
|
Nested map[string]int
|
||||||
}
|
}
|
||||||
|
|
||||||
type labelField struct {
|
type labelField struct {
|
||||||
@ -127,6 +170,7 @@ func getFieldTags(ty reflect.Type) *fieldTags {
|
|||||||
Attributes: map[string]int{},
|
Attributes: map[string]int{},
|
||||||
Blocks: map[string]int{},
|
Blocks: map[string]int{},
|
||||||
Optional: map[string]bool{},
|
Optional: map[string]bool{},
|
||||||
|
Nested: map[string]int{},
|
||||||
}
|
}
|
||||||
|
|
||||||
ct := ty.NumField()
|
ct := ty.NumField()
|
||||||
@ -172,6 +216,12 @@ func getFieldTags(ty reflect.Type) *fieldTags {
|
|||||||
case "optional":
|
case "optional":
|
||||||
ret.Attributes[name] = i
|
ret.Attributes[name] = i
|
||||||
ret.Optional[name] = true
|
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:
|
default:
|
||||||
panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name))
|
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) {
|
func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
|
||||||
content, remainHCL, diags := b.PartialContent(schema)
|
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.
|
// extraneous items.
|
||||||
remain := remainHCL.(*Body)
|
remain := remainHCL.(*Body)
|
||||||
|
|
||||||
|
@ -14,8 +14,16 @@ type AttributeSchema struct {
|
|||||||
Required bool
|
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.
|
// BodySchema represents the desired shallow structure of a body.
|
||||||
type BodySchema struct {
|
type BodySchema struct {
|
||||||
Attributes []AttributeSchema
|
Attributes []AttributeSchema
|
||||||
Blocks []BlockHeaderSchema
|
Blocks []BlockHeaderSchema
|
||||||
|
Nested []NestedBlockSchemas
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user