Separate reflection projection handlers

This commit is contained in:
2026-10-25 13:45:27 +00:00
parent fa6e876ab5
commit ac4ce242a2
2 changed files with 117 additions and 99 deletions

View File

@@ -1,6 +1,6 @@
# Consolidation Implementation Plan
Status: in progress. Stages 1 through 16 are complete; follow Stage 17 through Stage 20 in order.
Status: in progress. Stages 1 through 17 are complete; follow Stage 18 through Stage 20 in order.
The target executor is a Luna coding agent working through one end-to-end sprint.
## Authority, Baseline, And Execution
@@ -655,7 +655,7 @@ counter, budget checks, final expansion check, and one policy index remain intac
Both mapping and identified-sequence adapters still use the shared executor.
Validation group I passed.
## Stage 17 — Extract Substantial Projection Handlers
## Stage 17 — Extract Substantial Projection Handlers (Complete)
**Owners:** engine `projection.go`, optionally new `projection_collection.go`.
@@ -678,6 +678,12 @@ Validation group I passed.
original operation state. Do not refactor `clone`, `decodeValue`, or
`valueBuilder.build` as an additional cleanup project.
Progress notes (2026-10-25): delegated struct, map, and sequence projection to
methods on the existing `valueOperation`. The dispatcher retains depth checks,
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
**Owner:** engine `schema.go`, optionally new `schema_fields.go`.

View File

@@ -39,89 +39,9 @@ func (o *valueOperation) project(value reflect.Value, descriptor *typeDescriptor
switch descriptor.kind {
case descriptorStruct:
result, issue := o.node(document.Mapping, path, depth)
if issue != nil {
return nil, issue
}
result.Mapping = make([]document.Entry, 0, len(descriptor.fields))
seen := make(map[string]bool, len(descriptor.fields))
for _, member := range descriptor.members {
memberValue := value.Field(member.index)
if member.inline {
if isNilValue(memberValue) {
continue
}
inline, issue := o.project(memberValue, member.descriptor, path, depth)
if issue != nil {
return nil, issue
}
if inline.Kind != document.Mapping {
return nil, &projectionIssue{code: CodeSchemaUnsupportedType, path: path.clone(), message: "inline value did not project to a mapping"}
}
o.nodes-- // The flattened container is not retained in the output tree.
for _, entry := range inline.Mapping {
if seen[entry.Key] {
return nil, &projectionIssue{code: CodeSchemaAmbiguousField, path: path.Key(entry.Key), message: "inline value conflicts with a declared field"}
}
seen[entry.Key] = true
result.Mapping = append(result.Mapping, entry)
}
continue
}
childPath := path.Key(member.name)
if seen[member.name] {
return nil, &projectionIssue{code: CodeSchemaAmbiguousField, path: childPath, message: "inline value conflicts with a declared field"}
}
if issue := o.charge(childPath); issue != nil { // mapping key
return nil, issue
}
child, issue := o.project(memberValue, member.descriptor, childPath, depth+1)
if issue != nil {
return nil, issue
}
seen[member.name] = true
result.Mapping = append(result.Mapping, document.Entry{Key: member.name, Value: child})
}
for _, entry := range result.Mapping {
field := descriptor.inlineMap
if index, ok := descriptor.fieldByName[entry.Key]; ok {
field = &descriptor.fields[index]
}
if field != nil && field.secret {
entry.Value.Sensitive = true
}
}
return result, nil
return o.projectStruct(value, descriptor, path, depth)
case descriptorMap:
if value.IsNil() {
return o.node(document.Null, path, depth)
}
leave, issue := o.enter(value, path)
if issue != nil {
return nil, issue
}
defer leave()
result, issue := o.node(document.Mapping, path, depth)
if issue != nil {
return nil, issue
}
result.Mapping = make([]document.Entry, 0, value.Len())
keys := value.MapKeys()
slices.SortFunc(keys, func(left, right reflect.Value) int {
return cmp.Compare(left.String(), right.String())
})
for _, key := range keys {
childPath := path.Key(key.String())
if issue := o.charge(childPath); issue != nil {
return nil, issue
}
child, issue := o.project(value.MapIndex(key), descriptor.element, childPath, depth+1)
if issue != nil {
return nil, issue
}
result.Mapping = append(result.Mapping, document.Entry{Key: key.String(), Value: child})
}
return result, nil
return o.projectMap(value, descriptor, path, depth)
case descriptorSlice:
if value.IsNil() {
return o.node(document.Null, path, depth)
@@ -131,22 +51,9 @@ func (o *valueOperation) project(value reflect.Value, descriptor *typeDescriptor
return nil, issue
}
defer leave()
fallthrough
return o.projectSequence(value, descriptor, path, depth)
case descriptorArray:
result, issue := o.node(document.Sequence, path, depth)
if issue != nil {
return nil, issue
}
result.Sequence = make([]*document.Node, 0, value.Len())
for i := 0; i < value.Len(); i++ {
childPath := path.Index(i)
child, issue := o.project(value.Index(i), descriptor.element, childPath, depth+1)
if issue != nil {
return nil, issue
}
result.Sequence = append(result.Sequence, child)
}
return result, nil
return o.projectSequence(value, descriptor, path, depth)
case descriptorDynamic:
if value.IsNil() {
return o.node(document.Null, path, depth)
@@ -217,6 +124,111 @@ func (o *valueOperation) project(value reflect.Value, descriptor *typeDescriptor
}
}
func (o *valueOperation) projectStruct(value reflect.Value, descriptor *typeDescriptor, path Path, depth int) (*document.Node, *projectionIssue) {
result, issue := o.node(document.Mapping, path, depth)
if issue != nil {
return nil, issue
}
result.Mapping = make([]document.Entry, 0, len(descriptor.fields))
seen := make(map[string]bool, len(descriptor.fields))
for _, member := range descriptor.members {
memberValue := value.Field(member.index)
if member.inline {
if isNilValue(memberValue) {
continue
}
inline, issue := o.project(memberValue, member.descriptor, path, depth)
if issue != nil {
return nil, issue
}
if inline.Kind != document.Mapping {
return nil, &projectionIssue{code: CodeSchemaUnsupportedType, path: path.clone(), message: "inline value did not project to a mapping"}
}
o.nodes-- // The flattened container is not retained in the output tree.
for _, entry := range inline.Mapping {
if seen[entry.Key] {
return nil, &projectionIssue{code: CodeSchemaAmbiguousField, path: path.Key(entry.Key), message: "inline value conflicts with a declared field"}
}
seen[entry.Key] = true
result.Mapping = append(result.Mapping, entry)
}
continue
}
childPath := path.Key(member.name)
if seen[member.name] {
return nil, &projectionIssue{code: CodeSchemaAmbiguousField, path: childPath, message: "inline value conflicts with a declared field"}
}
if issue := o.charge(childPath); issue != nil { // mapping key
return nil, issue
}
child, issue := o.project(memberValue, member.descriptor, childPath, depth+1)
if issue != nil {
return nil, issue
}
seen[member.name] = true
result.Mapping = append(result.Mapping, document.Entry{Key: member.name, Value: child})
}
for _, entry := range result.Mapping {
field := descriptor.inlineMap
if index, ok := descriptor.fieldByName[entry.Key]; ok {
field = &descriptor.fields[index]
}
if field != nil && field.secret {
entry.Value.Sensitive = true
}
}
return result, nil
}
func (o *valueOperation) projectMap(value reflect.Value, descriptor *typeDescriptor, path Path, depth int) (*document.Node, *projectionIssue) {
if value.IsNil() {
return o.node(document.Null, path, depth)
}
leave, issue := o.enter(value, path)
if issue != nil {
return nil, issue
}
defer leave()
result, issue := o.node(document.Mapping, path, depth)
if issue != nil {
return nil, issue
}
result.Mapping = make([]document.Entry, 0, value.Len())
keys := value.MapKeys()
slices.SortFunc(keys, func(left, right reflect.Value) int {
return cmp.Compare(left.String(), right.String())
})
for _, key := range keys {
childPath := path.Key(key.String())
if issue := o.charge(childPath); issue != nil {
return nil, issue
}
child, issue := o.project(value.MapIndex(key), descriptor.element, childPath, depth+1)
if issue != nil {
return nil, issue
}
result.Mapping = append(result.Mapping, document.Entry{Key: key.String(), Value: child})
}
return result, nil
}
func (o *valueOperation) projectSequence(value reflect.Value, descriptor *typeDescriptor, path Path, depth int) (*document.Node, *projectionIssue) {
result, issue := o.node(document.Sequence, path, depth)
if issue != nil {
return nil, issue
}
result.Sequence = make([]*document.Node, 0, value.Len())
for index := 0; index < value.Len(); index++ {
childPath := path.Index(index)
child, issue := o.project(value.Index(index), descriptor.element, childPath, depth+1)
if issue != nil {
return nil, issue
}
result.Sequence = append(result.Sequence, child)
}
return result, nil
}
func (o *valueOperation) node(kind document.Kind, path Path, depth int) (*document.Node, *projectionIssue) {
if issue := o.checkDepth(path, depth); issue != nil {
return nil, issue