package engine import "go.maximumdirect.net/configkit/internal/document" // collectionCorrespondence describes matching at one concrete registered path. // A negative index denotes an identity present only at the other endpoint. type collectionCorrespondence struct { path Path eligible bool structural bool entryToFinal []int finalToEntry []int } type resolutionCorrespondence struct { collections map[string]collectionCorrespondence prefix pathPrefixIndex indexedCollections []string } func (c resolutionCorrespondence) clone() resolutionCorrespondence { var result resolutionCorrespondence for _, collection := range c.collections { collection.path = collection.path.clone() collection.entryToFinal = append([]int(nil), collection.entryToFinal...) collection.finalToEntry = append([]int(nil), collection.finalToEntry...) result.indexCollection(collection) } return result } func (c *resolutionCorrespondence) indexCollection(collection collectionCorrespondence) { if c.collections == nil { c.collections = make(map[string]collectionCorrespondence) } pointer := collection.path.String() c.collections[pointer] = collection if collection.eligible { c.prefix.insert(pointer, len(c.indexedCollections)) c.indexedCollections = append(c.indexedCollections, pointer) } } func buildResolutionCorrespondence(entry, final *document.Node, policy sequencePolicy) resolutionCorrespondence { var result resolutionCorrespondence if policy.selectorTrie == nil { return result } var indexes mappingIndex var visit func(*document.Node, *document.Node, Path) visit = func(before, after *document.Node, path Path) { if fields, registered := policy.identityFieldsAt(path); registered && ((before != nil && before.Kind == document.Sequence) || (after != nil && after.Kind == document.Sequence)) { result.indexCollection(matchResolutionCollection(before, after, path, fields)) return } if before != nil && before.Kind == document.Mapping { for _, item := range before.Mapping { var next *document.Node if after != nil && after.Kind == document.Mapping { if position := indexes.find(after, item.Key); position >= 0 { next = after.Mapping[position].Value } } visit(item.Value, next, path.Key(item.Key)) } } if after != nil && after.Kind == document.Mapping { for _, item := range after.Mapping { if before == nil || before.Kind != document.Mapping || indexes.find(before, item.Key) < 0 { visit(nil, item.Value, path.Key(item.Key)) } } } } visit(entry, final, Path{}) return result } func matchResolutionCollection(before, after *document.Node, path Path, fields []string) collectionCorrespondence { result := collectionCorrespondence{path: path.clone()} prior, priorIssues := validateNormalizedSequenceIdentityFrom(before, path, fields, sequenceIdentityDiagnosticCodes{}, 0, sequencePayloadRecord) later, laterIssues := validateNormalizedSequenceIdentityFrom(after, path, fields, sequenceIdentityDiagnosticCodes{}, 0, sequencePayloadRecord) if len(priorIssues) != 0 || len(laterIssues) != 0 { return result } result.eligible = true result.entryToFinal = make([]int, len(before.Sequence)) result.finalToEntry = make([]int, len(after.Sequence)) for i := range result.entryToFinal { result.entryToFinal[i] = -1 } for i := range result.finalToEntry { result.finalToEntry[i] = -1 } result.structural = len(prior) != len(later) for tuple, previous := range prior { if current, found := later[tuple]; found { result.entryToFinal[previous.index] = current.index result.finalToEntry[current.index] = previous.index if previous.index != current.index { result.structural = true } } else { result.structural = true } } return result } // entryPath translates a surviving final record prefix; additions have no entry. // Paths outside eligible collections and wholly absent final positions stay positional. func (c resolutionCorrespondence) entryPath(path Path) (Path, bool) { return c.translate(path, false) } // finalPath translates historical record descendants without matching removals. func (c resolutionCorrespondence) finalPath(path Path) (Path, bool) { return c.translate(path, true) } func (c resolutionCorrespondence) translate(path Path, toFinal bool) (Path, bool) { if !path.valid() { return Path{}, false } translated := path.clone() if len(c.indexedCollections) == 0 { return translated, true } matched, handled := true, false c.prefix.visitAncestors(path.String(), func(id int) { if handled { return } collection := c.collections[c.indexedCollections[id]] depth := len(collection.path.segments) if depth >= len(path.segments) { return } for i, segment := range collection.path.segments { if segment.kind == pathSegmentKey && path.segments[i].kind == pathSegmentIndex { return } } index, ok := schemaIndex(path.segments[depth]) indices := collection.finalToEntry if toFinal { indices = collection.entryToFinal } if !ok || index >= len(indices) { return } handled = true current := indices[index] if current < 0 { matched = false return } translated.segments[depth] = pathSegment{kind: pathSegmentIndex, index: current} }) if !matched { return Path{}, false } return translated, true }