184 lines
6.3 KiB
Go
184 lines
6.3 KiB
Go
package engine
|
|
|
|
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
|
|
// Metadata provides detached effective presence/origin and chronological history
|
|
// queries for a load. Its zero value contains no paths.
|
|
type Metadata struct {
|
|
effectivePaths map[string]Path
|
|
fields map[string]FieldInfo
|
|
history map[string][]Change
|
|
inheritance []inheritanceOperation
|
|
inheritanceIndex *inheritanceHistoryIndex
|
|
historyModel *historyModel
|
|
}
|
|
|
|
// Lookup returns information for an effective path. Unknown and invalid paths
|
|
// return false.
|
|
func (m Metadata) Lookup(path Path) (FieldInfo, bool) {
|
|
info, ok := m.effectiveField(path)
|
|
if !ok {
|
|
return FieldInfo{}, false
|
|
}
|
|
detached := cloneFieldInfo(info)
|
|
detached.Origin.Inheritance = projectInheritanceSteps(detached.Origin)
|
|
return detached, true
|
|
}
|
|
|
|
// IsExplicit reports whether a surviving input contribution, including one
|
|
// inherited from a sibling, explicitly defined path.
|
|
func (m Metadata) IsExplicit(path Path) bool {
|
|
info, ok := m.effectiveField(path)
|
|
return ok && info.Explicit
|
|
}
|
|
|
|
func (m Metadata) effectiveField(path Path) (FieldInfo, bool) {
|
|
if !path.valid() {
|
|
return FieldInfo{}, false
|
|
}
|
|
pointer := path.String()
|
|
candidate, ok := m.effectivePaths[pointer]
|
|
if !ok || !matchesPathShape(path, candidate) {
|
|
return FieldInfo{}, false
|
|
}
|
|
info, ok := m.fields[pointer]
|
|
return info, ok
|
|
}
|
|
|
|
// History returns detached definition, inheritance, and resolution records for path. A record's
|
|
// Path can be an ancestor when that container operation replaced or removed path.
|
|
// Removed historical paths can have history even though Lookup returns false.
|
|
// Unknown and invalid paths return nil.
|
|
func (m Metadata) History(path Path) []Change {
|
|
if !m.hasHistoricalPath(path) {
|
|
return nil
|
|
}
|
|
history := m.history[path.String()]
|
|
operations := m.inheritance
|
|
if m.inheritanceIndex != nil {
|
|
operations = m.inheritanceIndex.matchingOperations(path, m.inheritance)
|
|
}
|
|
return historyWithInheritance(path, history, operations)
|
|
}
|
|
|
|
// HistoricalPaths returns the paths observed during loading, including effective
|
|
// paths and paths removed by overlays, inheritance, or recorded resolution. It includes default
|
|
// paths and the root, but not paths that never existed. The zero Metadata returns
|
|
// an empty slice.
|
|
//
|
|
// Paths are detached, unique by canonical JSON Pointer, and sorted by that
|
|
// spelling. Like ParsePath results, numeric tokens are resolved contextually;
|
|
// one pointer can identify a mapping key in an earlier layer and a sequence index
|
|
// in a later one. Use History to inspect definitions and Lookup to check whether
|
|
// a path remains effective. Enumeration exposes no configuration values and does
|
|
// not perform another load or resolve secrets.
|
|
func (m Metadata) HistoricalPaths() []Path {
|
|
return m.historyModel.historicalPaths()
|
|
}
|
|
|
|
// Textual pointers are lookup keys, but explicit key/index kinds remain part of
|
|
// the query. Candidate paths are constructed from actual document containers.
|
|
func matchesPathShape(query, candidate Path) bool {
|
|
if !query.valid() || len(query.segments) != len(candidate.segments) {
|
|
return false
|
|
}
|
|
for i, segment := range query.segments {
|
|
actual := candidate.segments[i]
|
|
if !pathTokenSpellingEqual(segment, actual) {
|
|
return false
|
|
}
|
|
if actual.kind == pathSegmentIndex {
|
|
index, ok := schemaIndex(segment)
|
|
if !ok || index != actual.index {
|
|
return false
|
|
}
|
|
} else if segment.kind == pathSegmentIndex {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (m Metadata) hasHistoricalPath(path Path) bool {
|
|
return m.historyModel.hasObservedShape(path)
|
|
}
|
|
|
|
func finalizeMetadataPaths(m *Metadata, final *document.Node, applications []historyApplication, earlier ...*document.Node) {
|
|
finalizeMetadataPathsWithResolution(m, final, applications, nil, earlier...)
|
|
}
|
|
|
|
func finalizeMetadataPathsWithResolution(m *Metadata, final *document.Node, applications []historyApplication, snapshots []resolutionSnapshot, earlier ...*document.Node) {
|
|
finalizeEffectiveMetadataPaths(m, final)
|
|
builder := newHistoryBuilder()
|
|
if len(earlier) != 0 {
|
|
builder.model.baseline = earlier[0]
|
|
}
|
|
builder.model.final = final
|
|
for index, tree := range earlier {
|
|
phase := historyPhaseSources
|
|
switch index {
|
|
case 0:
|
|
phase = historyPhaseBaseline
|
|
case 2:
|
|
phase = historyPhasePreCLI
|
|
case 3:
|
|
phase = historyPhasePostCLI
|
|
}
|
|
builder.observeDocument(tree, phase)
|
|
}
|
|
if final != nil {
|
|
builder.observeDocument(final, historyPhasePostCLI)
|
|
}
|
|
for _, application := range applications {
|
|
builder.registerDocumentRoot(application.value)
|
|
phase := historyPhaseSources
|
|
if application.layer == LayerCLI {
|
|
phase = historyPhaseCLI
|
|
}
|
|
payload := builder.nextPayload(phase)
|
|
walkHistoryApplication(application, func(_, effective Path, node *document.Node) {
|
|
builder.addInventoryObservation(effective, effective, payload, phase, node.Sensitive)
|
|
})
|
|
}
|
|
for _, operation := range m.inheritance {
|
|
payload := builder.nextPayload(historyPhaseInheritance)
|
|
if operation.control != nil {
|
|
builder.addInventoryObservation(operation.control.path, operation.control.path, payload, historyPhaseInheritance, false)
|
|
continue
|
|
}
|
|
if operation.transfer == nil {
|
|
continue
|
|
}
|
|
builder.registerDocumentRoot(operation.parent)
|
|
builder.registerDocumentRoot(operation.effective)
|
|
for _, root := range []*document.Node{operation.parent, operation.effective} {
|
|
walkDocument(root, operation.transfer.to, func(path Path, node *document.Node) {
|
|
builder.addInventoryObservation(path, path, payload, historyPhaseInheritance, node.Sensitive)
|
|
})
|
|
}
|
|
}
|
|
if len(snapshots) != 0 {
|
|
if len(earlier) != 0 {
|
|
builder.setScratch(earlier[len(earlier)-1])
|
|
}
|
|
builder.observeResolutionEvidence(snapshots)
|
|
}
|
|
m.historyModel = builder.freeze()
|
|
m.inheritanceIndex = newInheritanceHistoryIndex(m.inheritance)
|
|
}
|
|
|
|
func finalizeEffectiveMetadataPaths(m *Metadata, final *document.Node) {
|
|
m.effectivePaths = effectivePathIndex(final)
|
|
m.inheritanceIndex = newInheritanceHistoryIndex(m.inheritance)
|
|
}
|
|
|
|
// effectivePathIndex records document-derived paths with their contextual segment
|
|
// kinds. Callers retain no aliases to the walked tree or its paths.
|
|
func effectivePathIndex(final *document.Node) map[string]Path {
|
|
paths := make(map[string]Path)
|
|
walkDocument(final, Path{}, func(path Path, _ *document.Node) {
|
|
paths[path.String()] = path.clone()
|
|
})
|
|
return paths
|
|
}
|