package engine import "gitea.maximumdirect.net/eric/ConfigKit/internal/document" func prependCompositionDiagnosticPaths(diagnostics []Diagnostic, prefix Path) []Diagnostic { cloned := append([]Diagnostic(nil), diagnostics...) for index := range cloned { if !isCompositionDiagnostic(cloned[index]) { continue } cloned[index].Path = appendDestination(prefix, cloned[index].Path) cloned[index].Primary = cloneLocation(cloned[index].Primary) cloned[index].Related = cloneLocations(cloned[index].Related) } return cloned } func isCompositionDiagnostic(diagnostic Diagnostic) bool { return diagnostic.Code == CodeComposeConflict || diagnostic.Code == CodeComposeInvalid } // On a composition failure only the earlier contributions are needed to locate // this item. Project one enclosing scope at a time as traversal unwinds; syntax // and access diagnostics keep their original source-local coordinates. func projectItemDiagnosticPaths(diagnostics []Diagnostic, parent acquisitionScope, destination Path, beforeContribution int) []Diagnostic { for _, diagnostic := range diagnostics { if isCompositionDiagnostic(diagnostic) { prefix := parent prefix.contributions = parent.contributions[:beforeContribution] index := priorSequenceLength([]acquisitionScope{prefix}, destination) return prependCompositionDiagnosticPaths(diagnostics, destination.Index(index)) } } return diagnostics } // This scan is confined to failed loads, not the successful construction path. func priorSequenceLength(scopes []acquisitionScope, path Path) int { length := 0 for _, scope := range scopes { for _, contribution := range scope.contributions { if node, present := documentAtPath(contribution.document, path); present && node.Kind == document.Sequence { length += len(node.Sequence) } } } return length } func projectPriorInputDiagnosticPaths(diagnostics []Diagnostic, scopes []acquisitionScope) []Diagnostic { cloned := cloneDiagnostics(diagnostics) for index := range cloned { if !isCompositionDiagnostic(cloned[index]) { continue } for position, segment := range cloned[index].Path.segments { if segment.kind != pathSegmentIndex { continue } container := Path{segments: cloned[index].Path.segments[:position]} cloned[index].Path.segments[position].index += priorSequenceLength(scopes, container) // Registration does not address paths through item indexes. A prior // input shifts the outer collection, not its item-local sequences. break } } return cloned } func projectNestedEvidence(composed *composedDocument, scope acquisitionScope) { if composed == nil { return } applications := composed.applications projectedApplications := make([]historyApplication, 0, len(applications)) ranges := indexSequenceRanges(composed.sequenceRanges) for sourceIndex, contribution := range scope.contributions { if contribution.nested == nil { projectedApplications = append(projectedApplications, applications[sourceIndex]) continue } itemIndex := 0 if assigned, found := ranges[sequenceRangeKey{sourceIndex: sourceIndex, path: contribution.destination.String()}]; found { if projected, ok := assigned.outputForInput(0); ok { itemIndex = projected } } prefix := contribution.destination.Index(itemIndex) if sourceIndex < len(applications) { outer := applications[sourceIndex] structural := document.Clone(outer.value) if node, found := documentAtPath(structural, contribution.destination); found && node.Kind == document.Sequence && len(node.Sequence) > 0 { // The retained fragment is still a local singleton. Its assigned // outer index belongs to the projection, not this source tree. node.Sequence[0] = &document.Node{Kind: document.Mapping, Location: node.Sequence[0].Location} } outer.value = structural projectedApplications = append(projectedApplications, outer) for _, nested := range contribution.nested.applications { projected := nested projected.path = appendDestination(prefix, nested.path) projected.sequences = cloneSequenceHistoryContributions(nested.sequences) projected.sequencesByPath = indexSequenceHistoryContributions(projected.sequences) projectedApplications = append(projectedApplications, projected) } } projectNestedOrigins(composed.origins, contribution.nested.root, contribution.nested.origins, prefix) } composed.applications = projectedApplications projectNestedCollectionPolicies(composed, scope, ranges) } func projectNestedCollectionPolicies(composed *composedDocument, scope acquisitionScope, ranges map[sequenceRangeKey]sequenceRange) { if composed == nil { return } type itemPolicyProjection struct { destination Path policy collectionPolicy base int } projected := make(map[string]*itemPolicyProjection) var prefixLengths map[string]int for sourceIndex, contribution := range scope.contributions { if prefixLengths != nil && sourceIndex > 0 { addSequencePrefixLengths(scope.contributions[sourceIndex-1].document, Path{}, prefixLengths) } if contribution.nested == nil || len(contribution.nested.collectionPolicies.collections) == 0 { continue } key := contribution.destination.String() assigned := ranges[sequenceRangeKey{sourceIndex: sourceIndex, path: key}] itemIndex, found := assigned.outputForInput(0) if !found { // Normal composition supplies the assignment directly. For a // handoff without ranges, scan each preceding tree only once. if prefixLengths == nil { prefixLengths = make(map[string]int) for _, previous := range scope.contributions[:sourceIndex] { addSequencePrefixLengths(previous.document, Path{}, prefixLengths) } } itemIndex = prefixLengths[key] } working, initialized := projected[key] if !initialized { // Read one owned copy per destination, then add each incoming // record without recopying earlier records and their scopes. policy, _ := composed.collectionPolicies.at(contribution.destination) working = &itemPolicyProjection{destination: contribution.destination, policy: policy, base: len(policy.recordOrder)} projected[key] = working } working.policy.setRecordPolicyAt(working.base+itemIndex, collectionRecordPolicy{ owner: newCollectionRecordPolicy().owner, scope: contribution.nested.collectionPolicies, }) } for _, working := range projected { composed.collectionPolicies.set(working.destination, working.policy) } } func addSequencePrefixLengths(node *document.Node, path Path, lengths map[string]int) { if node == nil { return } if node.Kind == document.Sequence { lengths[path.String()] += len(node.Sequence) return } // Contribution destinations select mapping paths, not sequence members. for _, entry := range node.Mapping { addSequencePrefixLengths(entry.Value, path.Key(entry.Key), lengths) } } func projectNestedOrigins(target map[string]Origin, root *document.Node, origins map[string]Origin, prefix Path) { var visit func(*document.Node, Path) visit = func(node *document.Node, path Path) { if node == nil { return } if origin, ok := origins[path.String()]; ok { target[appendDestination(prefix, path).String()] = cloneOrigin(origin) } for _, entry := range node.Mapping { visit(entry.Value, path.Key(entry.Key)) } for index, child := range node.Sequence { visit(child, path.Index(index)) } } visit(root, Path{}) } func projectAcquiredDestinations(composed *composedDocument, scope acquisitionScope, sources []SourceInfo) { byID := make(map[string]int, len(sources)) for index := range sources { byID[sources[index].ID] = index } var projectScope func(*composedDocument, acquisitionScope, Path, bool, string) projectScope = func(currentComposition *composedDocument, current acquisitionScope, prefix Path, insideItem bool, parentSourceID string) { ranges := indexSequenceRanges(currentComposition.sequenceRanges) for sourceIndex, contribution := range current.contributions { if contribution.source.ID == parentSourceID { continue } contributionPrefix := prefix contributionInsideItem := insideItem itemIndex := 0 if assigned, found := ranges[sequenceRangeKey{sourceIndex: sourceIndex, path: contribution.destination.String()}]; found { if projected, ok := assigned.outputForInput(0); ok { itemIndex = projected } } if contribution.source.placementMode == PlacementModeSequenceItem { container := appendDestination(contributionPrefix, contribution.destination) contribution.source.Destination = container.clone() contribution.source.destinationUnresolved = false current.contributions[sourceIndex].source = cloneSourceInfo(contribution.source) if position, ok := byID[contribution.source.ID]; ok { sources[position].Destination = container sources[position].destinationUnresolved = false } contributionPrefix = appendDestination(container, Path{}.Index(itemIndex)) contributionInsideItem = true projectSourceVersionEvidence(contribution.processing, contributionPrefix, nil, sourceIndex) } else if contributionInsideItem { exactDestination := appendDestination(contributionPrefix, contribution.destination) contribution.source.Destination = exactDestination.clone() contribution.source.destinationUnresolved = false current.contributions[sourceIndex].source = cloneSourceInfo(contribution.source) if position, ok := byID[contribution.source.ID]; ok { sources[position].Destination = exactDestination sources[position].destinationUnresolved = false } projectSourceVersionEvidence(contribution.processing, exactDestination, nil, sourceIndex) } else { projectSourceVersionEvidence(contribution.processing, contribution.destination, ranges, sourceIndex) } if contribution.nested != nil && contribution.nested.acquisition != nil { for _, nestedScope := range contribution.nested.acquisition { projectScope(contribution.nested, nestedScope, contributionPrefix, contributionInsideItem, contribution.source.ID) } } } } projectScope(composed, scope, Path{}, false, "") } func projectSourceVersionEvidence(processing *sourceVersionProcessingResult, destination Path, ranges sequenceRangeIndex, sourceIndex int) { if processing != nil && processing.evidence != nil { evidence := processing.evidence evidence.destination = destination.clone() // Rebuild from source-local paths on each handoff: an input can be // composed first on its own and then at a different offset in its tier. evidence.projectedPaths = nil paths := evidence.sensitivityPaths() for index, path := range paths { for position := range path.segments { prefix := Path{segments: path.segments[:position]} assigned, found := ranges[sequenceRangeKey{sourceIndex: sourceIndex, path: prefix.String()}] if !found { continue } item, ok := schemaIndex(path.segments[position]) if ok && path.segments[position].kind != pathSegmentKey { if outputIndex, projected := assigned.outputForInput(item); projected { path.segments[position] = pathSegment{kind: pathSegmentIndex, index: outputIndex} } } } paths[index] = path } evidence.projectedPaths = paths } }