Separate schema field discovery and assembly
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Consolidation Implementation Plan
|
||||
|
||||
Status: in progress. Stages 1 through 17 are complete; follow Stage 18 through Stage 20 in order.
|
||||
Status: in progress. Stages 1 through 18 are complete; follow Stage 19 through Stage 20 in order.
|
||||
The target executor is a Luna coding agent working through one end-to-end sprint.
|
||||
|
||||
## Authority, Baseline, And Execution
|
||||
@@ -684,7 +684,7 @@ pointer/dynamic handling, and scalar conversion; struct order, map sorting,
|
||||
flattened-node charging, nil slices, and cycle guards remain in their original
|
||||
paths. Validation group R passed.
|
||||
|
||||
## Stage 18 — Separate Schema Field Discovery And Assembly
|
||||
## Stage 18 — Separate Schema Field Discovery And Assembly (Complete)
|
||||
|
||||
**Owner:** engine `schema.go`, optionally new `schema_fields.go`.
|
||||
|
||||
@@ -701,6 +701,12 @@ paths. Validation group R passed.
|
||||
Do not unify these views or add alternate field/tag policy. Existing schema,
|
||||
projection, decoding, and cloning expectations remain the behavior baseline.
|
||||
|
||||
Progress notes (2026-10-25): kept declaration-order enumeration, exported and
|
||||
ignored-field decisions, and tag parsing in `describeFields`; moved ordinary
|
||||
registration, inline pointer inspection, and inline struct/map assembly into
|
||||
focused helpers in `schema_fields.go`. Physical members remain distinct from
|
||||
flattened fields and inline-map metadata. Validation group R passed.
|
||||
|
||||
**Finish:** field discovery and descriptor assembly have explicit responsibilities
|
||||
without a new registration framework. Run R for both Stages 17–18.
|
||||
|
||||
|
||||
@@ -241,80 +241,15 @@ func (b *schemaBuilder) describeFields(descriptor *typeDescriptor, current refle
|
||||
if name != "" {
|
||||
return &schemaIssue{code: CodeSchemaTag, path: path.clone(), message: "inline field cannot declare a YAML name"}
|
||||
}
|
||||
inlineType := field.Type
|
||||
seenPointers := make(map[reflect.Type]bool)
|
||||
for inlineType.Kind() == reflect.Pointer {
|
||||
if seenPointers[inlineType] {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline pointer chain must terminate in a struct"}
|
||||
}
|
||||
seenPointers[inlineType] = true
|
||||
inlineType = inlineType.Elem()
|
||||
}
|
||||
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:
|
||||
if b.building[inlineType] {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "recursive inline struct is not supported"}
|
||||
}
|
||||
child, childIssue := b.describe(inlineType, path)
|
||||
if childIssue != nil {
|
||||
return childIssue
|
||||
}
|
||||
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(i, nested.index)
|
||||
nested.secret = secret || nested.secret
|
||||
if issue := addField(descriptor, nested, path); issue != nil {
|
||||
return issue
|
||||
}
|
||||
}
|
||||
if child.inlineMap != nil {
|
||||
nested := *child.inlineMap
|
||||
nested.index = prependIndex(i, nested.index)
|
||||
nested.secret = secret || nested.secret
|
||||
if descriptor.inlineMap != nil {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "configuration object has more than one inline map"}
|
||||
}
|
||||
descriptor.inlineMap = &nested
|
||||
}
|
||||
memberType, memberIssue := b.describe(field.Type, path)
|
||||
if memberIssue != nil {
|
||||
return memberIssue
|
||||
}
|
||||
descriptor.members = append(descriptor.members, structMember{index: i, descriptor: memberType, inline: true})
|
||||
case reflect.Map:
|
||||
if inlineType.Key().Kind() != reflect.String {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline map must have string keys"}
|
||||
}
|
||||
if descriptor.inlineMap != nil {
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "configuration object has more than one inline map"}
|
||||
}
|
||||
fieldPath := path.Key("*")
|
||||
fieldType, childIssue := b.describe(inlineType, fieldPath)
|
||||
if childIssue != nil {
|
||||
return childIssue
|
||||
}
|
||||
descriptor.inlineMap = &fieldDescriptor{index: []int{i}, descriptor: fieldType, secret: secret, inlineMap: true}
|
||||
descriptor.members = append(descriptor.members, structMember{index: i, descriptor: fieldType, inline: true})
|
||||
default:
|
||||
return &schemaIssue{code: CodeSchemaInline, path: path.clone(), message: "inline field must be a struct, pointer to struct, or string-keyed map"}
|
||||
if issue := b.describeInlineField(descriptor, field, i, secret, path); issue != nil {
|
||||
return issue
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
fieldPath := path.Key(name)
|
||||
fieldType, childIssue := b.describe(field.Type, fieldPath)
|
||||
if childIssue != nil {
|
||||
return childIssue
|
||||
}
|
||||
if issue := addField(descriptor, fieldDescriptor{name: name, index: []int{i}, descriptor: fieldType, secret: secret}, path); issue != nil {
|
||||
if issue := b.describeOrdinaryField(descriptor, field, i, name, secret, path); issue != nil {
|
||||
return issue
|
||||
}
|
||||
descriptor.members = append(descriptor.members, structMember{index: i, name: name, descriptor: fieldType})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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