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. for _, location := range origin.Locations { found := false for _, recorded := range change.Origin.Locations { if location.SourceID == recorded.SourceID { found = true break } } if !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 } for _, location := range candidate.Locations { found := false for _, available := range container.Locations { if location == available { 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)), } 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) { for _, snapshot := range resolved.snapshots { pointer := snapshot.change.Path.String() 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) for _, delta := range resolved.deltas { record := coveringResolutionSnapshot(resolved.snapshots, delta.path) if record == nil { continue } for pointer, info := range metadata.fields { path, err := ParsePath(pointer) if err != nil || !pathContains(delta.path, path) { continue } info.Origin = cloneOrigin(record.change.Origin) metadata.fields[pointer] = info } } refreshResolutionFields(metadata.fields, resolved.final, resolved.snapshots) } func appendResolutionDescendantHistory(metadata *Metadata, resolved resolutionResult) { finalPaths := make(map[string]*document.Node) indexDocument(resolved.final, Path{}, finalPaths) for _, delta := range resolved.deltas { record := coveringResolutionSnapshot(resolved.snapshots, delta.path) if record == nil { continue } affected := make(map[string]bool) for pointer := range metadata.history { if path, err := ParsePath(pointer); err == nil && pathContains(delta.path, path) { affected[pointer] = true } } for pointer := range finalPaths { if path, err := ParsePath(pointer); err == nil && pathContains(delta.path, path) { affected[pointer] = true } } pointers := make([]string, 0, len(affected)) for pointer := range affected { if pointer != record.change.Path.String() { pointers = append(pointers, pointer) } } slices.Sort(pointers) for _, pointer := range pointers { change := cloneChange(record.change) if info, ok := metadata.fields[pointer]; ok { change.Previous = []Origin{cloneOrigin(info.Origin)} } metadata.history[pointer] = append(metadata.history[pointer], change) } } } func coveringResolutionSnapshot(snapshots []resolutionSnapshot, path Path) *resolutionSnapshot { var covering *resolutionSnapshot for index := range snapshots { snapshot := &snapshots[index] if pathContains(snapshot.change.Path, path) && (covering == nil || len(snapshot.change.Path.segments) > len(covering.change.Path.segments)) { covering = snapshot } } return covering } func refreshResolutionFields(fields map[string]FieldInfo, final *document.Node, snapshots []resolutionSnapshot) { finalPaths := make(map[string]*document.Node) indexDocument(final, Path{}, finalPaths) 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 path, err := ParsePath(pointer); err == nil { if covering := coveringResolutionSnapshot(snapshots, path); covering != nil { origin = cloneOrigin(covering.change.Origin) } } fields[pointer] = FieldInfo{Null: node.Kind == document.Null, Origin: origin} } }