Separate schema field discovery and assembly

This commit is contained in:
2026-10-25 13:46:51 +00:00
parent ac4ce242a2
commit 0fbce5d141
3 changed files with 113 additions and 70 deletions

View 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
}