package configkit import ( "sort" "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 historicalPaths map[string][]Path fields map[string]FieldInfo history map[string][]Change } // Lookup returns information for an effective path. Unknown and invalid paths // return false. func (m Metadata) Lookup(path Path) (FieldInfo, bool) { if !path.valid() || !matchesPathShape(path, m.effectivePaths[path.String()]) { return FieldInfo{}, false } info, ok := m.fields[path.String()] if !ok { return FieldInfo{}, false } return cloneFieldInfo(info), true } // IsExplicit reports whether a surviving input contribution explicitly defined // path. func (m Metadata) IsExplicit(path Path) bool { info, ok := m.Lookup(path) return ok && info.Explicit } // History returns detached definition 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, ok := m.history[path.String()] if !ok { return nil } cloned := make([]Change, len(history)) for i, change := range history { cloned[i] = cloneChange(change) } return cloned } // HistoricalPaths returns the paths observed during loading, including effective // paths and paths removed by overlays 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 { pointers := make([]string, 0, len(m.historicalPaths)) for pointer := range m.historicalPaths { pointers = append(pointers, pointer) } sort.Strings(pointers) paths := make([]Path, 0, len(pointers)) for _, pointer := range pointers { // The index contains only canonical pointers produced by Path.String. path, _ := ParsePath(pointer) paths = append(paths, path) } return paths } // 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() || query.String() != candidate.String() || len(query.segments) != len(candidate.segments) { return false } for i, segment := range query.segments { actual := candidate.segments[i] 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 { if !path.valid() { return false } for _, candidate := range m.historicalPaths[path.String()] { if matchesPathShape(path, candidate) { return true } } return false } func finalizeMetadataPaths(m *Metadata, final *document.Node, applications []historyApplication, earlier ...*document.Node) { m.effectivePaths = make(map[string]Path) m.historicalPaths = make(map[string][]Path) walkDocument(final, Path{}, func(path Path, _ *document.Node) { m.effectivePaths[path.String()] = path.clone() }) add := func(path Path, _ *document.Node) { pointer := path.String() for _, prior := range m.historicalPaths[pointer] { if matchesPathShape(path, prior) { return } } m.historicalPaths[pointer] = append(m.historicalPaths[pointer], path.clone()) } walkDocument(final, Path{}, add) for _, tree := range earlier { walkDocument(tree, Path{}, add) } for _, application := range applications { // Overrides have mapping-only prefixes; parsed tokens are keys in this layer. prefix := Path{} for _, segment := range application.path.segments { prefix = prefix.Key(segment.key) } walkDocument(application.value, prefix, add) } }