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 historyBuilder *historyBuilder 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 { model := m.historyEvidence() if model == nil || !model.hasObservedShape(path) { return nil } selections := model.selectHistory(path) if len(selections) == 0 { return nil } changes := make([]Change, 0, len(selections)) for _, selection := range selections { if !metadataHistoryRole(selection.role) || !selection.hasAttribution { continue } var previous []Origin if len(selection.previous) != 0 { previous = make([]Origin, len(selection.previous)) for index, origin := range selection.previous { previous[index] = cloneOrigin(origin) } } changes = append(changes, Change{ Path: selection.reportedPath.clone(), Origin: cloneOrigin(selection.attribution.origin), Previous: previous, Reasons: append([]string(nil), selection.attribution.reasons...), Dependencies: clonePaths(selection.attribution.dependencies), }) } if len(changes) == 0 { return nil } return changes } // 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.historyEvidence().historicalPaths() } func (m Metadata) historyEvidence() *historyModel { if m.historyModel != nil { return m.historyModel } if m.historyBuilder != nil { return m.historyBuilder.model } return nil } // 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 { switch segment.kind { case pathSegmentIndex: if segment.index != actual.index { return false } case pathSegmentParsed: index, ok := schemaIndex(segment) if !ok || index != actual.index { return false } default: return false } } else if segment.kind == pathSegmentIndex { return false } } return true } func (m Metadata) hasHistoricalPath(path Path) bool { return m.historyEvidence().hasObservedShape(path) } func freezeMetadataHistory(m *Metadata, final *document.Node) { finalizeEffectiveMetadataPaths(m, final) builder := m.historyBuilder if builder == nil || !builder.prefixReady { panic("configkit: metadata history prefix is not ready") } builder.model.final = final m.historyModel = builder.freeze() m.historyBuilder = nil } func finalizeEffectiveMetadataPaths(m *Metadata, final *document.Node) { m.effectivePaths = effectivePathIndex(final) } // 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 }