Files
configkit/internal/engine/override_context.go

273 lines
10 KiB
Go

package engine
import "go.maximumdirect.net/configkit/internal/document"
// postDecodeOverrideContext carries the typed post-CLI state and detached
// resolution views after the history builder has taken ownership of evidence.
type postDecodeOverrideContext struct {
decoded decodedConfiguration
metadata Metadata
resolution resolutionInput
referenceInput *document.Node
}
func preparePostDecodeOverrideContext(input assembledInput, before decodedConfiguration, assigned decodedOverrideResult) postDecodeOverrideContext {
after := before
after.value = assigned.value
after.presence = cloneInputPresence(assigned.presence)
after.origins = cloneOrigins(assigned.origins)
after.final = document.Clone(assigned.final)
metadata := metadataFromDecoded(before)
cliRecords := overrideMetadataRecords(before, assigned.overrides)
applications := structuralHistoryApplications(input)
var structuralOverlays []overlayRecord
if input.inheritance != nil && input.composition != nil {
structuralOverlays = input.composition.overlays
metadata.historyBuilder.setScratch(input.composition.root)
metadata.historyBuilder.observeSourceEvidence(input.composition.root, input.composition.origins, applications, structuralOverlays)
metadata.historyBuilder.setScratch(input.composition.root)
metadata.historyBuilder.observeInheritanceEvidence(input.inheritance.operations)
} else if input.composition != nil {
structuralOverlays = input.composition.overlays
metadata.historyBuilder.setScratch(input.composition.root)
metadata.historyBuilder.observeSourceEvidence(input.composition.root, input.composition.origins, applications, structuralOverlays)
} else if len(applications) != 0 {
metadata.historyBuilder.observeSourceEvidence(input.document, nil, applications, nil)
}
metadata.historyBuilder.observePreCLICheckpoint(before.final, before.origins)
var postDecodeApps []historyApplication
for _, override := range assigned.overrides {
postDecodeApps = append(postDecodeApps, historyApplication{
layer: LayerCLI,
path: override.path.clone(),
value: document.Clone(override.value),
origin: overlayOrigin(LayerCLI, document.Location{SourceID: override.source.ID}),
})
}
if len(cliRecords) != 0 {
metadata.fields = make(map[string]FieldInfo)
collectFieldInfo(after.final, Path{}, after, metadata.fields)
}
if len(cliRecords) != 0 || len(postDecodeApps) != 0 {
metadata.historyBuilder.setScratch(before.final)
metadata.historyBuilder.observeCLIEvidence(postDecodeApps, cliRecords)
}
metadata.historyBuilder.observePostCLICheckpoint(after.final, after.origins)
metadata.historyBuilder.prefixReady = true
sources := cloneSourceInfos(input.sources)
knownSources := make(map[string]bool, len(sources)+len(assigned.overrides))
for _, source := range sources {
knownSources[source.ID] = true
}
for _, override := range assigned.overrides {
if !knownSources[override.source.ID] {
sources = append(sources, cloneSourceInfo(override.source))
knownSources[override.source.ID] = true
}
}
if input.versionState != nil {
sources = sourceInfosWithVersionEvidence(sources, input.versionState.evidence, nil, true)
}
resolution := newResolutionInput(metadata, assigned.final, sources, input.contexts)
finalizeEffectiveMetadataPaths(&metadata, assigned.final)
return postDecodeOverrideContext{
decoded: after,
metadata: metadata,
resolution: resolution,
referenceInput: documentWithMetadataLocations(assigned.final, metadata, input.document),
}
}
func overrideMetadataRecords(before decodedConfiguration, overrides []preparedOverride) []overlayRecord {
if len(overrides) == 0 {
return nil
}
lookup := buildDocumentLookup(before.final)
records := make([]overlayRecord, 0, len(overrides))
for _, override := range overrides {
location := document.Location{SourceID: override.source.ID}
previous := cloneOrigin(before.origins[override.path.String()])
var displaced []displacedDefinition
var introduced []historyPathOrigin
if existing, ok := documentAtPathWithLookup(before.final, override.path, lookup); ok {
displaced = affectedOverrideOrigins(existing, override.value, before.origins, override.path, new(mappingIndex))
}
application := historyApplication{
layer: LayerCLI, path: override.path.clone(), value: override.value,
origin: overlayOrigin(LayerCLI, location),
}
walkHistoryApplication(application, func(_, effective Path, node *document.Node) {
introduced = append(introduced, historyPathOrigin{
path: effective, origin: sourceApplicationOrigin(application, node), sensitive: node.Sensitive,
})
})
records = append(records, overlayRecord{change: Change{
Path: override.path.clone(), Origin: overlayOrigin(LayerCLI, location), Previous: previousOrigins(previous),
}, displaced: displaced, introduced: introduced})
}
return records
}
func affectedOverrideOrigins(existing, incoming *document.Node, origins map[string]Origin, path Path, index *mappingIndex) []displacedDefinition {
var displaced []displacedDefinition
collectAffectedOverrideOrigins(&displaced, existing, incoming, origins, path, index)
return sortDisplacedDefinitions(displaced)
}
func collectAffectedOverrideOrigins(displaced *[]displacedDefinition, existing, incoming *document.Node, origins map[string]Origin, path Path, index *mappingIndex) {
if existing.Kind != document.Mapping || incoming.Kind != document.Mapping {
collectDisplacedOrigins(displaced, existing, origins, path, Origin{})
return
}
*displaced = append(*displaced, displacedDefinition{path: path.clone(), origin: cloneOrigin(origins[path.String()])})
for _, entry := range incoming.Mapping {
position := index.find(existing, entry.Key)
if position < 0 {
continue
}
childPath := path.Key(entry.Key)
childExisting := existing.Mapping[position].Value
if childExisting.Kind == document.Mapping && entry.Value.Kind == document.Mapping {
collectAffectedOverrideOrigins(displaced, childExisting, entry.Value, origins, childPath, index)
} else {
collectDisplacedOrigins(displaced, childExisting, origins, childPath, Origin{})
}
}
}
func structuralHistoryApplications(input assembledInput) []historyApplication {
if input.composition != nil {
return input.composition.applications
}
if input.document == nil {
return nil
}
return []historyApplication{{layer: LayerMain, value: input.document}}
}
// documentWithMetadataLocations attaches effective origin coordinates to a
// canonical typed projection for secret and validation diagnostics.
func documentWithMetadataLocations(root *document.Node, metadata Metadata, fallback ...*document.Node) *document.Node {
located := document.Clone(root)
if located == nil {
return nil
}
var fallbackRoot *document.Node
if len(fallback) != 0 {
fallbackRoot = fallback[0]
}
var lookup *documentLookup
fallbackLocation := func(path Path) (document.Location, bool) {
if lookup == nil {
lookup = buildDocumentLookup(fallbackRoot)
}
return nearestConcreteDocumentLocationWithLookup(fallbackRoot, path, lookup)
}
var visit func(*document.Node, Path)
visit = func(node *document.Node, path Path) {
if info, exists := metadata.effectiveField(path); exists {
if location, ok := metadataFieldLocation(metadata, path, info.Origin); ok {
node.Location = location
} else if location, ok := fallbackLocation(path); ok {
node.Location = location
}
} else if location, ok := fallbackLocation(path); ok {
node.Location = location
}
switch node.Kind {
case document.Mapping:
for index := range node.Mapping {
entry := &node.Mapping[index]
childPath := path.Key(entry.Key)
if info, exists := metadata.effectiveField(childPath); exists {
if location, ok := metadataFieldLocation(metadata, childPath, info.Origin); ok {
entry.KeyLocation = location
entry.ValueLocation = location
} else if location, ok := fallbackLocation(childPath); ok {
entry.KeyLocation = location
entry.ValueLocation = location
}
} else if location, ok := fallbackLocation(childPath); ok {
entry.KeyLocation = location
entry.ValueLocation = location
}
visit(entry.Value, childPath)
}
case document.Sequence:
for index, child := range node.Sequence {
visit(child, path.Index(index))
}
}
}
visit(located, Path{})
return located
}
func nearestConcreteDocumentLocationWithLookup(root *document.Node, path Path, lookup *documentLookup) (document.Location, bool) {
if root == nil || !path.valid() {
return document.Location{}, false
}
current := root
nearest := current.Location
for _, segment := range path.segments {
switch current.Kind {
case document.Mapping:
if segment.kind != pathSegmentKey {
return nearest, locationKnown(nearest)
}
position := -1
if lookup != nil {
position = lookup.find(current, segment.key)
} else {
for index, entry := range current.Mapping {
if entry.Key == segment.key {
position = index
break
}
}
}
if position < 0 || position >= len(current.Mapping) {
return nearest, locationKnown(nearest)
}
current = current.Mapping[position].Value
case document.Sequence:
if segment.kind != pathSegmentIndex || segment.index < 0 || segment.index >= len(current.Sequence) {
return nearest, locationKnown(nearest)
}
current = current.Sequence[segment.index]
default:
return nearest, locationKnown(nearest)
}
if current == nil {
return nearest, locationKnown(nearest)
}
nearest = current.Location
}
return nearest, locationKnown(nearest)
}
func metadataFieldLocation(metadata Metadata, path Path, origin Origin) (document.Location, bool) {
if location, ok := latestOriginDocumentLocation(origin); ok {
return location, true
}
if model := metadata.historyEvidence(); model != nil {
location, ok := model.latestHistoricalLocation(path)
if ok {
return document.Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true
}
}
return document.Location{}, false
}
func latestOriginDocumentLocation(origin Origin) (document.Location, bool) {
if len(origin.Locations) == 0 {
return document.Location{}, false
}
location := origin.Locations[len(origin.Locations)-1]
return document.Location{SourceID: location.SourceID, Line: location.Line, Column: location.Column}, true
}