Separate reflection projection handlers
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Consolidation Implementation Plan
|
# 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.
|
The target executor is a Luna coding agent working through one end-to-end sprint.
|
||||||
|
|
||||||
## Authority, Baseline, And Execution
|
## 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.
|
Both mapping and identified-sequence adapters still use the shared executor.
|
||||||
Validation group I passed.
|
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`.
|
**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
|
original operation state. Do not refactor `clone`, `decodeValue`, or
|
||||||
`valueBuilder.build` as an additional cleanup project.
|
`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
|
## Stage 18 — Separate Schema Field Discovery And Assembly
|
||||||
|
|
||||||
**Owner:** engine `schema.go`, optionally new `schema_fields.go`.
|
**Owner:** engine `schema.go`, optionally new `schema_fields.go`.
|
||||||
|
|||||||
@@ -39,89 +39,9 @@ func (o *valueOperation) project(value reflect.Value, descriptor *typeDescriptor
|
|||||||
|
|
||||||
switch descriptor.kind {
|
switch descriptor.kind {
|
||||||
case descriptorStruct:
|
case descriptorStruct:
|
||||||
result, issue := o.node(document.Mapping, path, depth)
|
return o.projectStruct(value, descriptor, 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
|
|
||||||
case descriptorMap:
|
case descriptorMap:
|
||||||
if value.IsNil() {
|
return o.projectMap(value, descriptor, path, depth)
|
||||||
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
|
|
||||||
case descriptorSlice:
|
case descriptorSlice:
|
||||||
if value.IsNil() {
|
if value.IsNil() {
|
||||||
return o.node(document.Null, path, depth)
|
return o.node(document.Null, path, depth)
|
||||||
@@ -131,22 +51,9 @@ func (o *valueOperation) project(value reflect.Value, descriptor *typeDescriptor
|
|||||||
return nil, issue
|
return nil, issue
|
||||||
}
|
}
|
||||||
defer leave()
|
defer leave()
|
||||||
fallthrough
|
return o.projectSequence(value, descriptor, path, depth)
|
||||||
case descriptorArray:
|
case descriptorArray:
|
||||||
result, issue := o.node(document.Sequence, path, depth)
|
return o.projectSequence(value, descriptor, 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
|
|
||||||
case descriptorDynamic:
|
case descriptorDynamic:
|
||||||
if value.IsNil() {
|
if value.IsNil() {
|
||||||
return o.node(document.Null, path, depth)
|
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) {
|
func (o *valueOperation) node(kind document.Kind, path Path, depth int) (*document.Node, *projectionIssue) {
|
||||||
if issue := o.checkDepth(path, depth); issue != nil {
|
if issue := o.checkDepth(path, depth); issue != nil {
|
||||||
return nil, issue
|
return nil, issue
|
||||||
|
|||||||
Reference in New Issue
Block a user