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 } func (c resolutionCorrespondence) clone() resolutionCorrespondence { result := resolutionCorrespondence{collections: make(map[string]collectionCorrespondence, len(c.collections))} for key, collection := range c.collections { collection.path = collection.path.clone() collection.entryToFinal = append([]int(nil), collection.entryToFinal...) collection.finalToEntry = append([]int(nil), collection.finalToEntry...) result.collections[key] = collection } return result } func buildResolutionCorrespondence(entry, final *document.Node, policy sequencePolicy) resolutionCorrespondence { result := resolutionCorrespondence{collections: make(map[string]collectionCorrespondence)} 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.collections[path.String()] = 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) { if !path.valid() { return Path{}, false } for depth := 0; depth < len(path.segments); depth++ { prefix := Path{segments: path.segments[:depth]} collection, found := c.collections[prefix.String()] if !found || !collection.eligible { continue } compatible := true for i, segment := range collection.path.segments { if segment.kind == pathSegmentKey && path.segments[i].kind == pathSegmentIndex { compatible = false break } } index, ok := schemaIndex(path.segments[depth]) if !compatible || !ok || index >= len(collection.finalToEntry) { continue } previous := collection.finalToEntry[index] if previous < 0 { return Path{}, false } translated := path.clone() translated.segments[depth] = pathSegment{kind: pathSegmentIndex, index: previous} return translated, true } return path.clone(), true }