Separate schema field discovery and assembly
This commit is contained in:
102
internal/engine/schema_fields.go
Normal file
102
internal/engine/schema_fields.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package engine
|
||||
|
||||
import "reflect"
|
||||
|
||||
func (b *schemaBuilder) describeOrdinaryField(owner *typeDescriptor, field reflect.StructField, index int, name string, secret bool, path Path) *schemaIssue {
|
||||
fieldPath := path.Key(name)
|
||||
fieldType, issue := b.describe(field.Type, fieldPath)
|
||||
if issue != nil {
|
||||
return issue
|
||||
}
|
||||
if issue := addField(owner, fieldDescriptor{name: name, index: []int{index}, descriptor: fieldType, secret: secret}, path); issue != nil {
|
||||
return issue
|
||||
}
|
||||
owner.members = append(owner.members, structMember{index: index, name: name, descriptor: fieldType})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *schemaBuilder) describeInlineField(owner *typeDescriptor, field reflect.StructField, index int, secret bool, path Path) *schemaIssue {
|
||||
inlineType, issue := inlineFieldType(field.Type, path)
|
||||
if issue != nil {
|
||||
return issue
|
||||
}
|
||||
if inlineType.Kind() == reflect.Map && field.Type.Kind() == reflect.Pointer {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline maps cannot be pointers"}
|
||||
}
|
||||
|
||||
switch inlineType.Kind() {
|
||||
case reflect.Struct:
|
||||
return b.describeInlineStruct(owner, field, inlineType, index, secret, path)
|
||||
case reflect.Map:
|
||||
return b.describeInlineMap(owner, inlineType, index, secret, path)
|
||||
default:
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline field must be a struct, pointer to struct, or string-keyed map"}
|
||||
}
|
||||
}
|
||||
|
||||
func inlineFieldType(fieldType reflect.Type, path Path) (reflect.Type, *schemaIssue) {
|
||||
current := fieldType
|
||||
seenPointers := make(map[reflect.Type]bool)
|
||||
for current.Kind() == reflect.Pointer {
|
||||
if seenPointers[current] {
|
||||
return nil, &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline pointer chain must terminate in a struct"}
|
||||
}
|
||||
seenPointers[current] = true
|
||||
current = current.Elem()
|
||||
}
|
||||
return current, nil
|
||||
}
|
||||
|
||||
func (b *schemaBuilder) describeInlineStruct(owner *typeDescriptor, field reflect.StructField, inlineType reflect.Type, index int, secret bool, path Path) *schemaIssue {
|
||||
if b.building[inlineType] {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "recursive inline struct is not supported"}
|
||||
}
|
||||
child, issue := b.describe(inlineType, path)
|
||||
if issue != nil {
|
||||
return issue
|
||||
}
|
||||
if child.kind != descriptorStruct {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline field must be a structural struct or string-keyed map"}
|
||||
}
|
||||
for _, nested := range child.fields {
|
||||
nested.index = prependIndex(index, nested.index)
|
||||
nested.secret = secret || nested.secret
|
||||
if issue := addField(owner, nested, path); issue != nil {
|
||||
return issue
|
||||
}
|
||||
}
|
||||
if child.inlineMap != nil {
|
||||
nested := *child.inlineMap
|
||||
nested.index = prependIndex(index, nested.index)
|
||||
nested.secret = secret || nested.secret
|
||||
if owner.inlineMap != nil {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "configuration object has more than one inline map"}
|
||||
}
|
||||
owner.inlineMap = &nested
|
||||
}
|
||||
|
||||
memberType, issue := b.describe(field.Type, path)
|
||||
if issue != nil {
|
||||
return issue
|
||||
}
|
||||
owner.members = append(owner.members, structMember{index: index, descriptor: memberType, inline: true})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *schemaBuilder) describeInlineMap(owner *typeDescriptor, inlineType reflect.Type, index int, secret bool, path Path) *schemaIssue {
|
||||
if inlineType.Key().Kind() != reflect.String {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline map must have string keys"}
|
||||
}
|
||||
if owner.inlineMap != nil {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "configuration object has more than one inline map"}
|
||||
}
|
||||
|
||||
fieldPath := path.Key("*")
|
||||
fieldType, issue := b.describe(inlineType, fieldPath)
|
||||
if issue != nil {
|
||||
return issue
|
||||
}
|
||||
owner.inlineMap = &fieldDescriptor{index: []int{index}, descriptor: fieldType, secret: secret, inlineMap: true}
|
||||
owner.members = append(owner.members, structMember{index: index, descriptor: fieldType, inline: true})
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user