390 lines
13 KiB
Go
390 lines
13 KiB
Go
package engine
|
|
|
|
import (
|
|
"slices"
|
|
|
|
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
)
|
|
|
|
// resolutionInput is the immutable entry-state provenance available to a
|
|
// resolution callback. It owns detached metadata and source records so later
|
|
// resolution updates cannot change the entry view.
|
|
type resolutionInput struct {
|
|
metadata Metadata
|
|
sources map[string]SourceInfo
|
|
contexts map[string]ResourceContext
|
|
}
|
|
|
|
func newResolutionInput(metadata Metadata, final *document.Node, sources []SourceInfo, contexts ...map[string]ResourceContext) resolutionInput {
|
|
frozen := Metadata{
|
|
fields: make(map[string]FieldInfo, len(metadata.fields)),
|
|
effectivePaths: effectivePathIndex(final),
|
|
}
|
|
for pointer, info := range metadata.fields {
|
|
frozen.fields[pointer] = cloneFieldInfo(info)
|
|
}
|
|
indexed := make(map[string]SourceInfo, len(sources))
|
|
for _, source := range sources {
|
|
if source.ID == "" {
|
|
panic("configkit: source identity is missing")
|
|
}
|
|
if _, exists := indexed[source.ID]; exists {
|
|
panic("configkit: source identity is duplicated")
|
|
}
|
|
indexed[source.ID] = cloneSourceInfo(source)
|
|
}
|
|
result := resolutionInput{metadata: frozen, sources: indexed}
|
|
if len(contexts) != 0 {
|
|
result.contexts = cloneResourceContexts(contexts[0])
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (input resolutionInput) lookup(path Path) (FieldInfo, bool) {
|
|
return input.metadata.Lookup(path)
|
|
}
|
|
|
|
func (input resolutionInput) source(id string) (SourceInfo, bool) {
|
|
source, ok := input.sources[id]
|
|
if !ok {
|
|
return SourceInfo{}, false
|
|
}
|
|
return cloneSourceInfo(source), true
|
|
}
|
|
|
|
func (input resolutionInput) sourceContext(id string) (ResourceContext, bool) {
|
|
context, ok := input.contexts[id]
|
|
return context, ok
|
|
}
|
|
|
|
func metadataFromDecoded(decoded decodedConfiguration) Metadata {
|
|
metadata := Metadata{
|
|
fields: make(map[string]FieldInfo),
|
|
history: make(map[string][]Change),
|
|
}
|
|
baseline := make(map[string]*document.Node)
|
|
indexDocument(decoded.baseline, Path{}, baseline)
|
|
for pointer := range baseline {
|
|
path, err := ParsePath(pointer)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
metadata.history[pointer] = append(metadata.history[pointer], Change{
|
|
Path: path, Origin: Origin{Layer: LayerDefault},
|
|
})
|
|
}
|
|
for pointer := range decoded.presence {
|
|
path, err := ParsePath(pointer)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
origin := cloneOrigin(decoded.origins[pointer])
|
|
change := Change{Path: path, Origin: origin}
|
|
if _, ok := baseline[pointer]; ok {
|
|
change.Previous = []Origin{{Layer: LayerDefault}}
|
|
}
|
|
metadata.history[pointer] = append(metadata.history[pointer], change)
|
|
}
|
|
collectFieldInfo(decoded.final, Path{}, decoded, metadata.fields)
|
|
return metadata
|
|
}
|
|
|
|
func applyOverlayMetadata(metadata *Metadata, records []overlayRecord) {
|
|
baseline := make(map[string][]Change, len(metadata.history))
|
|
for pointer, history := range metadata.history {
|
|
for _, change := range history {
|
|
if change.Origin.Layer == LayerDefault {
|
|
baseline[pointer] = append(baseline[pointer], cloneChange(change))
|
|
}
|
|
}
|
|
}
|
|
events := make(map[string][]Change)
|
|
for _, record := range records {
|
|
exact := false
|
|
for _, displaced := range record.displaced {
|
|
pointer := displaced.path.String()
|
|
appendInputOriginEvent(events, baseline, pointer, displaced.path, displaced.origin)
|
|
appendInputChangeEvent(events, baseline, pointer, record.change)
|
|
if pointer == record.change.Path.String() {
|
|
exact = true
|
|
}
|
|
}
|
|
if !exact {
|
|
pointer := record.change.Path.String()
|
|
for _, previous := range record.change.Previous {
|
|
appendInputOriginEvent(events, baseline, pointer, record.change.Path, previous)
|
|
}
|
|
appendInputChangeEvent(events, baseline, pointer, record.change)
|
|
}
|
|
}
|
|
for pointer, info := range metadata.fields {
|
|
if !info.Explicit {
|
|
continue
|
|
}
|
|
path, err := ParsePath(pointer)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
appendInputOriginEvent(events, baseline, pointer, path, info.Origin)
|
|
}
|
|
metadata.history = make(map[string][]Change, len(baseline)+len(events))
|
|
for pointer, history := range baseline {
|
|
metadata.history[pointer] = cloneChanges(history)
|
|
}
|
|
for pointer, history := range events {
|
|
metadata.history[pointer] = append(metadata.history[pointer], cloneChanges(history)...)
|
|
}
|
|
}
|
|
|
|
func appendInputOriginEvent(events map[string][]Change, baseline map[string][]Change, pointer string, path Path, origin Origin) {
|
|
if origin.Layer == "" {
|
|
return
|
|
}
|
|
history := events[pointer]
|
|
if len(history) != 0 && originRepresentedByChange(path, origin, history[len(history)-1]) {
|
|
return
|
|
}
|
|
appendInputChangeEvent(events, baseline, pointer, Change{Path: path.clone(), Origin: cloneOrigin(origin)})
|
|
}
|
|
|
|
func originRepresentedByChange(path Path, origin Origin, change Change) bool {
|
|
if change.Path.String() == path.String() {
|
|
return originContainedBy(origin, change.Origin)
|
|
}
|
|
if !pathContains(change.Path, path) || origin.Layer != change.Origin.Layer || origin.Operation != change.Origin.Operation {
|
|
return false
|
|
}
|
|
// A subtree replacement already records its descendants' contributions.
|
|
// Their coordinates differ from the container's, but their sources must be
|
|
// covered by the operation; matching only the precedence role is insufficient.
|
|
recordedSources := make(map[string]struct{}, len(change.Origin.Locations))
|
|
for _, recorded := range change.Origin.Locations {
|
|
recordedSources[recorded.SourceID] = struct{}{}
|
|
}
|
|
for _, location := range origin.Locations {
|
|
if _, found := recordedSources[location.SourceID]; !found {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func appendInputChangeEvent(events map[string][]Change, baseline map[string][]Change, pointer string, value Change) {
|
|
change := cloneChange(value)
|
|
history := events[pointer]
|
|
if len(history) != 0 {
|
|
change.Previous = []Origin{cloneOrigin(history[len(history)-1].Origin)}
|
|
} else if defaults := baseline[pointer]; len(defaults) != 0 {
|
|
change.Previous = []Origin{cloneOrigin(defaults[len(defaults)-1].Origin)}
|
|
} else {
|
|
change.Previous = nil
|
|
}
|
|
events[pointer] = append(history, change)
|
|
}
|
|
|
|
func originContainedBy(candidate, container Origin) bool {
|
|
if candidate.Layer != container.Layer || candidate.Operation != container.Operation {
|
|
return false
|
|
}
|
|
availableLocations := make(map[Location]struct{}, len(container.Locations))
|
|
for _, location := range container.Locations {
|
|
availableLocations[location] = struct{}{}
|
|
}
|
|
for _, location := range candidate.Locations {
|
|
if _, found := availableLocations[location]; !found {
|
|
return false
|
|
}
|
|
}
|
|
if (len(candidate.contributors) == 0) != (len(container.contributors) == 0) {
|
|
return false
|
|
}
|
|
for _, contributor := range candidate.contributors {
|
|
found := false
|
|
for _, available := range container.contributors {
|
|
if contributor.location == available.location &&
|
|
slices.Equal(contributor.source.segments, available.source.segments) &&
|
|
sameInheritanceTrace(contributor.trace, available.trace) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func cloneChanges(changes []Change) []Change {
|
|
cloned := make([]Change, len(changes))
|
|
for index, change := range changes {
|
|
cloned[index] = cloneChange(change)
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
func collectFieldInfo(node *document.Node, path Path, decoded decodedConfiguration, fields map[string]FieldInfo) {
|
|
pointer := path.String()
|
|
_, explicit := decoded.presence[pointer]
|
|
fields[pointer] = FieldInfo{
|
|
Explicit: explicit,
|
|
Null: node.Kind == document.Null,
|
|
Origin: cloneOrigin(decoded.origins[pointer]),
|
|
}
|
|
switch node.Kind {
|
|
case document.Mapping:
|
|
for _, entry := range node.Mapping {
|
|
collectFieldInfo(entry.Value, path.Key(entry.Key), decoded, fields)
|
|
}
|
|
case document.Sequence:
|
|
for index, child := range node.Sequence {
|
|
collectFieldInfo(child, path.Index(index), decoded, fields)
|
|
}
|
|
}
|
|
}
|
|
|
|
func cloneMetadata(metadata Metadata) Metadata {
|
|
if metadata.fields == nil && metadata.history == nil {
|
|
return Metadata{}
|
|
}
|
|
cloned := Metadata{
|
|
fields: make(map[string]FieldInfo, len(metadata.fields)),
|
|
history: make(map[string][]Change, len(metadata.history)),
|
|
inheritance: append([]inheritanceOperation(nil), metadata.inheritance...),
|
|
inheritanceIndex: metadata.inheritanceIndex,
|
|
}
|
|
cloned.effectivePaths = make(map[string]Path, len(metadata.effectivePaths))
|
|
for pointer, path := range metadata.effectivePaths {
|
|
cloned.effectivePaths[pointer] = path.clone()
|
|
}
|
|
cloned.historicalPaths = make(map[string][]Path, len(metadata.historicalPaths))
|
|
for pointer, paths := range metadata.historicalPaths {
|
|
cloned.historicalPaths[pointer] = clonePaths(paths)
|
|
}
|
|
for path, info := range metadata.fields {
|
|
cloned.fields[path] = cloneFieldInfo(info)
|
|
}
|
|
for path, changes := range metadata.history {
|
|
clonedChanges := make([]Change, len(changes))
|
|
for index, change := range changes {
|
|
clonedChanges[index] = cloneChange(change)
|
|
}
|
|
cloned.history[path] = clonedChanges
|
|
}
|
|
return cloned
|
|
}
|
|
|
|
func applyResolutionMetadata(metadata *Metadata, resolved resolutionResult) {
|
|
finalPaths := make(map[string]*document.Node)
|
|
indexDocument(resolved.final, Path{}, finalPaths)
|
|
snapshotCoverage, snapshotPointers := newResolutionCoverageIndexFromSnapshots(resolved.snapshots)
|
|
var deltaCoverage pathPrefixIndex
|
|
snapshotIDs := make([]int, len(resolved.deltas))
|
|
for id, delta := range resolved.deltas {
|
|
if delta.path.valid() {
|
|
deltaCoverage.insert(delta.path.String(), id)
|
|
}
|
|
snapshotIDs[id] = -1
|
|
if snapshotID, ok := snapshotCoverage.deepestCoveringID(delta.path); ok {
|
|
snapshotIDs[id] = snapshotID
|
|
}
|
|
}
|
|
|
|
for snapshotID, snapshot := range resolved.snapshots {
|
|
pointer := snapshotPointers[snapshotID]
|
|
change := cloneChange(snapshot.change)
|
|
if info, ok := metadata.fields[pointer]; ok {
|
|
change.Previous = []Origin{cloneOrigin(info.Origin)}
|
|
info.Origin = cloneOrigin(change.Origin)
|
|
metadata.fields[pointer] = info
|
|
}
|
|
metadata.history[pointer] = append(metadata.history[pointer], change)
|
|
}
|
|
|
|
appendResolutionDescendantHistory(metadata, resolved.snapshots, snapshotPointers, finalPaths, &deltaCoverage, snapshotIDs)
|
|
applyResolutionFieldOrigins(metadata.fields, resolved.snapshots, &deltaCoverage, snapshotIDs)
|
|
refreshResolutionFields(metadata.fields, finalPaths, resolved.snapshots, snapshotCoverage)
|
|
}
|
|
|
|
func appendResolutionDescendantHistory(metadata *Metadata, snapshots []resolutionSnapshot, snapshotPointers []string, finalPaths map[string]*document.Node, deltaIndex *pathPrefixIndex, snapshotIDs []int) {
|
|
allPointers := make(map[string]struct{}, len(metadata.history)+len(finalPaths))
|
|
for pointer := range metadata.history {
|
|
if _, err := ParsePath(pointer); err == nil {
|
|
allPointers[pointer] = struct{}{}
|
|
}
|
|
}
|
|
for pointer := range finalPaths {
|
|
allPointers[pointer] = struct{}{}
|
|
}
|
|
buckets := make([][]string, len(snapshotIDs))
|
|
for pointer := range allPointers {
|
|
deltaIndex.visitAncestors(pointer, func(deltaID int) {
|
|
snapshotID := snapshotIDs[deltaID]
|
|
if snapshotID < 0 || pointer == snapshotPointers[snapshotID] {
|
|
return
|
|
}
|
|
buckets[deltaID] = append(buckets[deltaID], pointer)
|
|
})
|
|
}
|
|
for deltaID, pointers := range buckets {
|
|
snapshotID := snapshotIDs[deltaID]
|
|
if snapshotID < 0 {
|
|
continue
|
|
}
|
|
slices.Sort(pointers)
|
|
for _, pointer := range pointers {
|
|
change := cloneChange(snapshots[snapshotID].change)
|
|
if info, ok := metadata.fields[pointer]; ok {
|
|
change.Previous = []Origin{cloneOrigin(info.Origin)}
|
|
}
|
|
metadata.history[pointer] = append(metadata.history[pointer], change)
|
|
}
|
|
}
|
|
}
|
|
|
|
func applyResolutionFieldOrigins(fields map[string]FieldInfo, snapshots []resolutionSnapshot, deltaIndex *pathPrefixIndex, snapshotIDs []int) {
|
|
buckets := make([][]string, len(snapshotIDs))
|
|
for pointer := range fields {
|
|
if _, err := ParsePath(pointer); err != nil {
|
|
continue
|
|
}
|
|
deltaIndex.visitAncestors(pointer, func(deltaID int) {
|
|
if snapshotIDs[deltaID] >= 0 {
|
|
buckets[deltaID] = append(buckets[deltaID], pointer)
|
|
}
|
|
})
|
|
}
|
|
for deltaID, pointers := range buckets {
|
|
snapshotID := snapshotIDs[deltaID]
|
|
if snapshotID < 0 {
|
|
continue
|
|
}
|
|
origin := snapshots[snapshotID].change.Origin
|
|
for _, pointer := range pointers {
|
|
info := fields[pointer]
|
|
info.Origin = cloneOrigin(origin)
|
|
fields[pointer] = info
|
|
}
|
|
}
|
|
}
|
|
|
|
func refreshResolutionFields(fields map[string]FieldInfo, finalPaths map[string]*document.Node, snapshots []resolutionSnapshot, snapshotCoverage *resolutionCoverageIndex) {
|
|
for pointer := range fields {
|
|
if _, ok := finalPaths[pointer]; !ok {
|
|
delete(fields, pointer)
|
|
}
|
|
}
|
|
for pointer, node := range finalPaths {
|
|
if info, ok := fields[pointer]; ok {
|
|
info.Null = node.Kind == document.Null
|
|
fields[pointer] = info
|
|
continue
|
|
}
|
|
origin := Origin{Layer: LayerResolution}
|
|
if snapshotID, ok := snapshotCoverage.deepestCoveringPointerID(pointer); ok {
|
|
origin = cloneOrigin(snapshots[snapshotID].change.Origin)
|
|
}
|
|
fields[pointer] = FieldInfo{Null: node.Kind == document.Null, Origin: origin}
|
|
}
|
|
}
|