Files
configkit/internal/engine/resolution_correspondence.go

166 lines
5.2 KiB
Go

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
}