Validate metadata queries against semantic path shapes
This commit is contained in:
84
metadata.go
84
metadata.go
@@ -1,16 +1,20 @@
|
||||
package configkit
|
||||
|
||||
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 {
|
||||
fields map[string]FieldInfo
|
||||
history map[string][]Change
|
||||
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() {
|
||||
if !path.valid() || !matchesPathShape(path, m.effectivePaths[path.String()]) {
|
||||
return FieldInfo{}, false
|
||||
}
|
||||
info, ok := m.fields[path.String()]
|
||||
@@ -32,7 +36,7 @@ func (m Metadata) IsExplicit(path Path) bool {
|
||||
// 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 !path.valid() {
|
||||
if !m.hasHistoricalPath(path) {
|
||||
return nil
|
||||
}
|
||||
history, ok := m.history[path.String()]
|
||||
@@ -45,3 +49,75 @@ func (m Metadata) History(path Path) []Change {
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
// 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)
|
||||
walkDocumentPaths(final, Path{}, func(path Path) { m.effectivePaths[path.String()] = path.clone() })
|
||||
add := func(path Path) {
|
||||
pointer := path.String()
|
||||
for _, prior := range m.historicalPaths[pointer] {
|
||||
if matchesPathShape(path, prior) {
|
||||
return
|
||||
}
|
||||
}
|
||||
m.historicalPaths[pointer] = append(m.historicalPaths[pointer], path.clone())
|
||||
}
|
||||
walkDocumentPaths(final, Path{}, add)
|
||||
for _, tree := range earlier {
|
||||
walkDocumentPaths(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)
|
||||
}
|
||||
walkDocumentPaths(application.value, prefix, add)
|
||||
}
|
||||
}
|
||||
|
||||
func walkDocumentPaths(node *document.Node, path Path, visit func(Path)) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
visit(path)
|
||||
for _, entry := range node.Mapping {
|
||||
walkDocumentPaths(entry.Value, path.Key(entry.Key), visit)
|
||||
}
|
||||
for i, child := range node.Sequence {
|
||||
walkDocumentPaths(child, path.Index(i), visit)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user