240 lines
9.2 KiB
Go
240 lines
9.2 KiB
Go
package engine
|
|
|
|
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
|
|
// postDecodeOverrideContext keeps the structural, typed pre-CLI, and effective
|
|
// post-CLI views distinct while preparing the existing inspection machinery.
|
|
type postDecodeOverrideContext struct {
|
|
structuralInput *document.Node
|
|
preCLI *document.Node
|
|
preCLIOrigins map[string]Origin
|
|
decoded decodedConfiguration
|
|
metadata Metadata
|
|
resolution resolutionInput
|
|
sources []SourceInfo
|
|
applications []historyApplication
|
|
postDecodeApps []historyApplication
|
|
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)
|
|
var structuralOverlays []overlayRecord
|
|
if input.inheritance != nil && input.composition != nil {
|
|
structuralOverlays = input.composition.overlays
|
|
applyInheritanceMetadata(&metadata, input.composition.root, input.composition.origins, structuralOverlays, input.inheritance)
|
|
} else if input.composition != nil {
|
|
structuralOverlays = input.composition.overlays
|
|
applyOverlayMetadata(&metadata, structuralOverlays)
|
|
}
|
|
if len(cliRecords) != 0 {
|
|
metadata.fields = make(map[string]FieldInfo)
|
|
collectFieldInfo(after.final, Path{}, after, metadata.fields)
|
|
appendPostDecodeCLIHistory(&metadata, cliRecords)
|
|
}
|
|
|
|
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)
|
|
|
|
applications := structuralHistoryApplications(input)
|
|
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}),
|
|
})
|
|
}
|
|
allApplications := append(cloneHistoryApplications(applications), cloneHistoryApplications(postDecodeApps)...)
|
|
finalizeMetadataPaths(&metadata, assigned.final, allApplications, input.document, before.final)
|
|
|
|
return postDecodeOverrideContext{
|
|
structuralInput: document.Clone(input.document),
|
|
preCLI: document.Clone(before.final),
|
|
preCLIOrigins: cloneOrigins(before.origins),
|
|
decoded: after,
|
|
metadata: metadata,
|
|
resolution: resolution,
|
|
sources: sources,
|
|
applications: applications,
|
|
postDecodeApps: postDecodeApps,
|
|
referenceInput: documentWithMetadataLocations(assigned.final, metadata, input.document),
|
|
}
|
|
}
|
|
|
|
// Source history is already assembled, with inheritance projected at query time.
|
|
// Reuse overlay event construction for CLI changes without reapplying displaced
|
|
// source definitions after those later assignments.
|
|
func appendPostDecodeCLIHistory(metadata *Metadata, records []overlayRecord) {
|
|
cli := Metadata{fields: metadata.fields}
|
|
applyOverlayMetadata(&cli, records)
|
|
for pointer, history := range cli.history {
|
|
for _, change := range history {
|
|
if change.Origin.Layer == LayerCLI {
|
|
appendInputChangeEvent(metadata.history, nil, pointer, change)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
if existing, ok := documentAtPathWithLookup(before.final, override.path, lookup); ok {
|
|
displaced = affectedOverrideOrigins(existing, override.value, before.origins, override.path, new(mappingIndex))
|
|
}
|
|
records = append(records, overlayRecord{change: Change{
|
|
Path: override.path.clone(), Origin: overlayOrigin(LayerCLI, location), Previous: previousOrigins(previous),
|
|
}, displaced: displaced})
|
|
}
|
|
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 (context postDecodeOverrideContext) historyApplications() []historyApplication {
|
|
applications := cloneHistoryApplications(context.applications)
|
|
return append(applications, cloneHistoryApplications(context.postDecodeApps)...)
|
|
}
|
|
|
|
func structuralHistoryApplications(input assembledInput) []historyApplication {
|
|
if input.composition != nil {
|
|
return cloneHistoryApplications(input.composition.applications)
|
|
}
|
|
if input.document == nil {
|
|
return nil
|
|
}
|
|
return []historyApplication{{layer: LayerBase, value: document.Clone(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 visit func(*document.Node, Path)
|
|
visit = func(node *document.Node, path Path) {
|
|
pointer := path.String()
|
|
if info, exists := metadata.fields[pointer]; exists {
|
|
if location, ok := metadataFieldLocation(metadata, pointer, info.Origin); ok {
|
|
node.Location = location
|
|
} else if location, ok := nearestDocumentLocation(fallbackRoot, path); ok {
|
|
node.Location = location
|
|
}
|
|
} else if location, ok := nearestDocumentLocation(fallbackRoot, 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.fields[childPath.String()]; exists {
|
|
if location, ok := metadataFieldLocation(metadata, childPath.String(), info.Origin); ok {
|
|
entry.KeyLocation = location
|
|
entry.ValueLocation = location
|
|
} else if location, ok := nearestDocumentLocation(fallbackRoot, childPath); ok {
|
|
entry.KeyLocation = location
|
|
entry.ValueLocation = location
|
|
}
|
|
} else if location, ok := nearestDocumentLocation(fallbackRoot, 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 metadataFieldLocation(metadata Metadata, pointer string, origin Origin) (document.Location, bool) {
|
|
if location, ok := latestOriginDocumentLocation(origin); ok {
|
|
return location, true
|
|
}
|
|
for index := len(metadata.history[pointer]) - 1; index >= 0; index-- {
|
|
change := metadata.history[pointer][index]
|
|
if location, ok := latestOriginDocumentLocation(change.Origin); ok {
|
|
return location, true
|
|
}
|
|
for prior := len(change.Previous) - 1; prior >= 0; prior-- {
|
|
if location, ok := latestOriginDocumentLocation(change.Previous[prior]); ok {
|
|
return location, 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
|
|
}
|